diff --git a/plinth/app.py b/plinth/app.py new file mode 100644 index 000000000..c288e9846 --- /dev/null +++ b/plinth/app.py @@ -0,0 +1,130 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Base class for all Freedombox applications. +""" + +import collections + + +class App: + """Implement common functionality for an app. + + An app is composed of components which actually performs various tasks. App + itself delegates tasks for individual components. Applications can show a + variation their behavior by choosing which components to have and by + customizing the components themselves. + + """ + + def __init__(self): + """Initialize the app object.""" + self.components = collections.OrderedDict() + + def add(self, component): + """Add a component to an app.""" + self.components[component.component_id] = component + return self + + def enable(self): + """Enable all the components of the app.""" + for component in self.components.values(): + component.enable() + + def disable(self): + """Enable all the components of the app.""" + for component in self.components.values(): + component.disable() + + def is_enabled(self): + """Return whether all the leader components are enabled.""" + return all((component.is_enabled() + for component in self.components.values() + if component.is_leader)) + + def set_enabled(self, enabled): + """Update the status of all follower components. + + Do not query or update the status of the leader components. + + """ + for component in self.components.values(): + if not component.is_leader: + if enabled: + component.enable() + else: + component.disable() + + +class Component: + """Interface for an app component.""" + + is_leader = False + + def __init__(self, component_id): + """Intialize the component.""" + if not component_id: + raise ValueError('Invalid component ID') + + self.component_id = component_id + + def enable(self): + """Enable the component.""" + + def disable(self): + """Disable the component.""" + + +class FollowerComponent(Component): + """Interface for an app component that follows other components. + + These components of the app don't determine if the app is enabled or not. + + """ + + is_leader = False + + def __init__(self, component_id, is_enabled=False): + """Intialize the component.""" + super().__init__(component_id) + self._is_enabled = is_enabled + + def is_enabled(self): + """Return whether the component is enabled.""" + return self._is_enabled + + def enable(self): + """Enable the component.""" + self._is_enabled = True + + def disable(self): + """Disable the component.""" + self._is_enabled = False + + +class LeaderComponent(Component): + """Interface for an app component that decides the state of the app. + + These components determine if the app is enabled or not. + + """ + + is_leader = True + + def is_enabled(self): + """Return if the component is enabled.""" + raise NotImplementedError diff --git a/plinth/context_processors.py b/plinth/context_processors.py index 71be5a4d6..39f82ed21 100644 --- a/plinth/context_processors.py +++ b/plinth/context_processors.py @@ -22,8 +22,7 @@ Django context processors to provide common data to templates. from django.utils.translation import ugettext as _, ugettext_noop import re -from plinth import cfg -from plinth.menu import main_menu +from plinth import cfg, menu from plinth.utils import is_user_admin @@ -41,7 +40,7 @@ def common(request): active_menu_urls = [request.path[:index + 1] for index in slash_indices] return { 'cfg': cfg, - 'submenu': main_menu.active_item(request), + 'submenu': menu.main_menu.active_item(request), 'active_menu_urls': active_menu_urls, 'box_name': _(cfg.box_name), 'user_is_admin': is_user_admin(request, True) diff --git a/plinth/menu.py b/plinth/menu.py index 959730801..146073edc 100644 --- a/plinth/menu.py +++ b/plinth/menu.py @@ -17,106 +17,88 @@ from django.urls import reverse, reverse_lazy -from plinth.utils import format_lazy +from plinth import app -class Menu(object): - """One menu item.""" +class Menu(app.FollowerComponent): + """Component to manage a single menu item.""" - def __init__(self, name="", short_description="", icon="", url="#", - order=50): + _all_menus = {} + + def __init__(self, component_id, name=None, short_description=None, + icon=None, url_name=None, url_args=None, url_kwargs=None, + parent_url_name=None, order=50): """Initialize a new menu item with basic properties. - icon is the icon to be displayed for the menu item. - Choose from the Fork Awesome set: - https://forkawesome.github.io/Fork-Awesome/icons/ + name is the label of the menu item. - url is the url location that will be activated when the menu - item is selected. + short_description is an optional description shown on the menu item. - order is the numerical rank of this item within the menu. - Lower order items appear closest to the top/left of the menu. - By convention, we use the spectrum between 0 and 100 to rank - orders, but feel free to disregard that. If you need more - granularity, don't bother renumbering things. Feel free to - use fractional orders. + icon is the icon to be displayed for the menu item. Choose from the + Fork Awesome set: https://forkawesome.github.io/Fork-Awesome/icons/ + + url_name is the name of url location that will be activated when the + menu item is selected. This is not optional. url_args and url_kwargs + are sent to reverse() when resolving url from url_name. + + parent_url_name optionally specifies the menu item under which this + menu item should become a child. + + order is the numerical rank of this item within the menu. Lower order + items appear closest to the top/left of the menu. By convention, we use + the spectrum between 0 and 100 to rank orders, but feel free to + disregard that. If you need more granularity, don't bother renumbering + things. Feel free to use fractional orders. """ + super().__init__(component_id) + if not url_name: + raise ValueError('Valid url_name is expected') + + url = reverse_lazy(url_name, args=url_args, kwargs=url_kwargs) + self.name = name self.short_description = short_description self.icon = icon self.url = url self.order = order - self.secondary = True - # TODO: With an ordered dictionary for self.items we could access the - # items by their URL directly instead of searching for them each time, - # which we do currently with the 'get' method self.items = [] - def get(self, urlname, url_args=None, url_kwargs=None): + # Add self to parent menu item + if parent_url_name: + parent_menu = self.get(parent_url_name) + parent_menu.items.append(self) + + # Add self to global list of menu items + self._all_menus[url] = self + + @classmethod + def get(cls, urlname, url_args=None, url_kwargs=None): """Return a menu item with given URL name.""" url = reverse(urlname, args=url_args, kwargs=url_kwargs) - for item in self.items: - if str(item.url) == url: - return item - - raise KeyError('Menu item not found') + return cls._all_menus[url] def sorted_items(self): """Return menu items in sorted order according to current locale.""" return sorted(self.items, key=lambda x: (x.order, x.name.lower())) - def add_urlname(self, name, icon, urlname, short_description="", order=50, - url_args=None, url_kwargs=None): - """Add a named URL to the menu (via add_item). - - url_args and url_kwargs will be passed on to Django reverse(). - - """ - url = reverse_lazy(urlname, args=url_args, kwargs=url_kwargs) - item = Menu(name=name, short_description=short_description, icon=icon, - url=url, order=order) - self.items.append(item) - - return item - def active_item(self, request): """Return the first active item (e.g. submenu) that is found.""" for item in self.items: if request.path.startswith(str(item.url)): return item - def promote_item(self, urlname, url_args=None, url_kwargs=None): - """Promote a secondary item to an item.""" - found_item = None - url = reverse(urlname, args=url_args, kwargs=url_kwargs) - for item in self.items: - if str(item.url) == url: - found_item = item - - if found_item: - found_item.secondary = False - else: - raise KeyError('Menu item not found') - - def demote_item(self, urlname, url_args=None, url_kwargs=None): - """Demote an item to a secondary item.""" - found_item = None - url = reverse(urlname, args=url_args, kwargs=url_kwargs) - for item in self.items: - if str(item.url) == url: - found_item = item - - if found_item: - found_item.secondary = True - else: - raise KeyError('Menu item not found') + return None -main_menu = Menu() +main_menu = None def init(): """Create main menu and other essential menus.""" - main_menu.add_urlname('', 'fa-download', 'apps') - main_menu.add_urlname('', 'fa-cog', 'system') + global main_menu + main_menu = Menu('menu-index', url_name='index') + Menu('menu-apps', icon='fa-download', url_name='apps', + parent_url_name='index') + Menu('menu-system', icon='fa-cog', url_name='system', + parent_url_name='index') diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index b7cfeff15..2dd1bc15d 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -20,9 +20,10 @@ FreedomBox app for service discovery. from django.utils.translation import ugettext_lazy as _ +from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth import service as service_module -from plinth import actions, cfg -from plinth.menu import main_menu from plinth.utils import format_lazy from plinth.views import ServiceView @@ -55,11 +56,25 @@ service = None manual_page = 'ServiceDiscovery' +app = None + + +class AvahiApp(app_module.App): + """FreedomBox app for Avahi.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-avahi', name, None, 'fa-compass', + 'avahi:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the service discovery module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-compass', 'avahi:index') + global app + app = AvahiApp() + app.set_enabled(True) global service # pylint: disable=W0603 service = service_module.Service(managed_services[0], name, ports=['mdns'], diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py index 5f959b5de..f3c14749a 100644 --- a/plinth/modules/backups/__init__.py +++ b/plinth/modules/backups/__init__.py @@ -24,8 +24,9 @@ import os from django.utils.text import get_valid_filename from django.utils.translation import ugettext_lazy as _ -from plinth import actions, cfg -from plinth.menu import main_menu +from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth.utils import format_lazy from . import api @@ -46,24 +47,43 @@ manual_page = 'Backups' MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/' ROOT_REPOSITORY = '/var/lib/freedombox/borgbackup' -ROOT_REPOSITORY_NAME = format_lazy(_('{box_name} storage'), - box_name=cfg.box_name) +ROOT_REPOSITORY_NAME = format_lazy( + _('{box_name} storage'), box_name=cfg.box_name) ROOT_REPOSITORY_UUID = 'root' # session variable name that stores when a backup file should be deleted SESSION_PATH_VARIABLE = 'fbx-backups-upload-path' +app = None + + +class BackupsApp(app_module.App): + """FreedomBox app for backup and restore.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-backups', name, None, 'fa-files-o', + 'backups:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-files-o', 'backups:index') + global app + app = BackupsApp() + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + app.set_enabled(True) def setup(helper, old_version=None): """Install and configure the module.""" helper.install(managed_packages) - helper.call('post', actions.superuser_run, 'backups', ['setup', '--path', - ROOT_REPOSITORY]) + helper.call('post', actions.superuser_run, 'backups', + ['setup', '--path', ROOT_REPOSITORY]) + helper.call('post', app.enable) def _backup_handler(packet, encryption_passphrase=None): @@ -102,8 +122,8 @@ def _restore_exported_archive_handler(packet, encryption_passphrase=None): """Perform restore operation on packet.""" locations = {'directories': packet.directories, 'files': packet.files} locations_data = json.dumps(locations) - actions.superuser_run('backups', ['restore-exported-archive', '--path', - packet.path], + actions.superuser_run('backups', + ['restore-exported-archive', '--path', packet.path], input=locations_data.encode()) @@ -111,8 +131,9 @@ def restore_archive_handler(packet, encryption_passphrase=None): """Perform restore operation on packet.""" locations = {'directories': packet.directories, 'files': packet.files} locations_data = json.dumps(locations) - arguments = ['restore-archive', '--path', packet.path, '--destination', - '/'] + arguments = [ + 'restore-archive', '--path', packet.path, '--destination', '/' + ] if encryption_passphrase: arguments += ['--encryption-passphrase', encryption_passphrase] actions.superuser_run('backups', arguments, input=locations_data.encode()) diff --git a/plinth/modules/bind/__init__.py b/plinth/modules/bind/__init__.py index b90f04bdd..32f6f1c05 100644 --- a/plinth/modules/bind/__init__.py +++ b/plinth/modules/bind/__init__.py @@ -22,11 +22,10 @@ import re from django.utils.translation import ugettext_lazy as _ -from plinth import actions -from plinth import action_utils -from plinth import cfg +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy from .manifest import backup @@ -84,17 +83,32 @@ listen-on-v6 { any; }; }; ''' +app = None + + +class BindApp(app_module.App): + """FreedomBox app for Bind.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-bind', name, short_description, + 'fa-globe-w', 'bind:index', + parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the BIND module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-globe-w', 'bind:index', short_description) + global app + app = BindApp() global service setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup': service = service_module.Service(managed_services[0], name, ports=['dns'], is_external=False) + app.set_enabled(True) # XXX: Perform better check def setup(helper, old_version=None): @@ -107,6 +121,7 @@ def setup(helper, old_version=None): enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', actions.superuser_run, 'bind', ['setup']) + helper.call('post', app.enable) def force_upgrade(helper, _packages): @@ -117,11 +132,13 @@ def force_upgrade(helper, _packages): def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) + app.disable() def diagnose(): diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py index 0ec833c06..f0ee64a91 100644 --- a/plinth/modules/cockpit/__init__.py +++ b/plinth/modules/cockpit/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure Cockpit. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules import names from plinth.signals import domain_added, domain_removed, domainname_change from plinth.utils import format_lazy @@ -59,12 +60,25 @@ service = None manual_page = 'Cockpit' +app = None + + +class CockpitApp(app_module.App): + """FreedomBox app for Cockpit.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-cockpit', name, short_description, + 'fa-wrench', 'cockpit:index', + parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-wrench', 'cockpit:index', - short_description) + global app + app = CockpitApp() global service setup_helper = globals()['setup_helper'] @@ -76,6 +90,7 @@ def init(): if is_enabled(): add_shortcut() + app.set_enabled(True) domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) @@ -117,12 +132,14 @@ def enable(): """Enable the module.""" actions.superuser_run('cockpit', ['enable']) add_shortcut() + app.enable() def disable(): """Disable the module.""" actions.superuser_run('cockpit', ['disable']) frontpage.remove_shortcut('cockpit') + app.disable() def diagnose(): diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index 188f9f79d..a5e58b41e 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -25,7 +25,8 @@ import augeas from django.utils.translation import ugettext_lazy from plinth import actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu from plinth.modules import firewall from plinth.modules.names import SERVICES from plinth.signals import domain_added @@ -45,6 +46,20 @@ APACHE_HOMEPAGE_CONFIG = os.path.join(APACHE_CONF_ENABLED_DIR, FREEDOMBOX_APACHE_CONFIG = os.path.join(APACHE_CONF_ENABLED_DIR, 'freedombox.conf') +app = None + + +class ConfigApp(app_module.App): + """FreedomBox app for basic system configuration.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-config', ugettext_lazy('Configure'), None, + 'fa-cog', 'config:index', + parent_url_name='system') + self.add(menu_item) + def get_domainname(): """Return the domainname""" @@ -89,8 +104,9 @@ def get_home_page(): def init(): """Initialize the module""" - menu = main_menu.get('system') - menu.add_urlname(ugettext_lazy('Configure'), 'fa-cog', 'config:index') + global app + app = ConfigApp() + app.set_enabled(True) # Register domain with Name Services module. domainname = get_domainname() diff --git a/plinth/modules/coquelicot/__init__.py b/plinth/modules/coquelicot/__init__.py index fdd92bf15..6ec0c6db3 100644 --- a/plinth/modules/coquelicot/__init__.py +++ b/plinth/modules/coquelicot/__init__.py @@ -20,9 +20,10 @@ Plinth module to configure coquelicot. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from .manifest import backup, clients @@ -52,12 +53,25 @@ service = None manual_page = 'Coquelicot' +app = None + + +class CoquelicotApp(app_module.App): + """FreedomBox app for Coquelicot.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-coquelicot', name, short_description, + 'coquelicot', 'coquelicot:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'coquelicot', 'coquelicot:index', - short_description) + global app + app = CoquelicotApp() global service setup_helper = globals()['setup_helper'] @@ -70,7 +84,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('coquelicot:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -87,8 +101,7 @@ def setup(helper, old_version=None): is_running=is_running) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'coquelicot:index') + helper.call('post', app.enable) def add_shortcut(): @@ -113,16 +126,14 @@ def enable(): """Enable the module.""" actions.superuser_run('coquelicot', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('coquelicot:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('coquelicot', ['disable']) frontpage.remove_shortcut('coquelicot') - menu = main_menu.get('apps') - menu.demote_item('coquelicot:index') + app.disable() def get_current_max_file_size(): diff --git a/plinth/modules/datetime/__init__.py b/plinth/modules/datetime/__init__.py index afbcaaa04..d6b014f5b 100644 --- a/plinth/modules/datetime/__init__.py +++ b/plinth/modules/datetime/__init__.py @@ -22,8 +22,9 @@ import subprocess from django.utils.translation import ugettext_lazy as _ +from plinth import app as app_module +from plinth import menu from plinth import service as service_module -from plinth.menu import main_menu from .manifest import backup @@ -46,11 +47,25 @@ manual_page = 'DateTime' service = None +app = None + + +class DateTimeApp(app_module.App): + """FreedomBox app for date and time.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-datetime', name, None, 'fa-clock-o', + 'datetime:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the date/time module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-clock-o', 'datetime:index') + global app + app = DateTimeApp() + app.set_enabled(True) global service setup_helper = globals()['setup_helper'] @@ -67,6 +82,7 @@ def setup(helper, old_version=None): is_external=True) service.enable() helper.call('post', service.notify_enabled, None, True) + helper.call('post', app.enable) def diagnose(): diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index f9eee3c51..f203be251 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure a Deluge web client. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from plinth.modules.users import register_group from .manifest import backup, clients @@ -55,11 +56,24 @@ clients = clients manual_page = 'Deluge' +app = None + + +class DelugeApp(app_module.App): + """FreedomBox app for Deluge.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-deluge', name, short_description, 'deluge', + 'deluge:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the Deluge module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'deluge', 'deluge:index', short_description) + global app + app = DelugeApp() register_group(group) global service @@ -71,7 +85,7 @@ def init(): disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('deluge:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -86,8 +100,7 @@ def setup(helper, old_version=None): disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'deluge:index') + helper.call('post', app.enable) def add_shortcut(): @@ -105,16 +118,14 @@ def enable(): """Enable the module.""" actions.superuser_run('deluge', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('deluge:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('deluge', ['disable']) frontpage.remove_shortcut('deluge') - menu = main_menu.get('apps') - menu.demote_item('deluge:index') + app.disable() def diagnose(): diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index a0d6b8c9f..b29e0e6fe 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -21,7 +21,8 @@ FreedomBox app for system diagnostics. from django.utils.translation import ugettext_lazy as _ from plinth import action_utils -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu from .manifest import backup @@ -39,11 +40,25 @@ description = [ manual_page = 'Diagnostics' +app = None + + +class DiagnosticsApp(app_module.App): + """FreedomBox app for diagnostics.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-diagnostics', name, None, 'fa-heartbeat', + 'diagnostics:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the module""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-heartbeat', 'diagnostics:index') + global app + app = DiagnosticsApp() + app.set_enabled(True) def diagnose(): diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py index a2e1f7389..edddadf07 100644 --- a/plinth/modules/diaspora/__init__.py +++ b/plinth/modules/diaspora/__init__.py @@ -16,13 +16,14 @@ import os +import augeas from django.utils.translation import ugettext_lazy as _ -import augeas +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage from plinth.errors import DomainNotRegisteredError -from plinth.menu import main_menu from plinth.utils import format_lazy domain_name_file = "/etc/diaspora/domain_name" @@ -72,23 +73,37 @@ description = [ from .manifest import clients # isort:skip clients = clients +app = None + + +class DiasporaApp(app_module.App): + """FreedomBox app for Diaspora.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-diaspora', name, short_description, + 'diaspora', 'diaspora:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the Diaspora module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'fa-diaspora', 'diaspora:index', short_description) + global app + app = DiasporaApp() global service setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup': - service = service_module.Service( - managed_services[0], name, ports=['http', 'https'], - is_external=True, is_enabled=is_enabled, enable=enable, - disable=disable) + service = service_module.Service(managed_services[0], name, ports=[ + 'http', 'https' + ], is_external=True, is_enabled=is_enabled, enable=enable, + disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('diaspora:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -97,8 +112,7 @@ def setup(helper, old_version=None): helper.install(managed_packages) helper.call('custom_config', actions.superuser_run, 'diaspora', ['disable-ssl']) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'diaspora:index') + helper.call('post', app.enable) def setup_domain_name(domain_name): @@ -131,16 +145,14 @@ def enable(): """Enable the module.""" actions.superuser_run('diaspora', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('diaspora:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('diaspora', ['disable']) frontpage.remove_shortcut('diaspora') - menu = main_menu.get('apps') - menu.demote_item('diaspora:index') + app.disable() def is_user_registrations_enabled(): @@ -172,8 +184,9 @@ def diagnose(): action_utils.diagnose_url('http://diaspora.localhost', kind='6', check_certificate=False)) results.append( - action_utils.diagnose_url('http://diaspora.{}'.format( - get_configured_domain_name()), kind='4', check_certificate=False)) + action_utils.diagnose_url( + 'http://diaspora.{}'.format(get_configured_domain_name()), + kind='4', check_certificate=False)) return results diff --git a/plinth/modules/dynamicdns/__init__.py b/plinth/modules/dynamicdns/__init__.py index 5755bce47..c8963f26b 100644 --- a/plinth/modules/dynamicdns/__init__.py +++ b/plinth/modules/dynamicdns/__init__.py @@ -20,8 +20,9 @@ FreedomBox app to configure ez-ipupdate client. from django.utils.translation import ugettext_lazy as _ -from plinth import actions, cfg -from plinth.menu import main_menu +from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth.modules import firewall from plinth.modules.names import SERVICES from plinth.signals import domain_added @@ -57,11 +58,24 @@ reserved_usernames = ['ez-ipupd'] manual_page = 'DynamicDNS' +app = None + + +class DynamicDNSApp(app_module.App): + """FreedomBox app for Dynamic DNS.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-dynamicdns', name, None, 'fa-refresh', + 'dynamicdns:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-refresh', 'dynamicdns:index') + global app + app = DynamicDNSApp() current_status = get_status() if current_status['enabled']: services = get_enabled_services(current_status['dynamicdns_domain']) @@ -69,6 +83,7 @@ def init(): sender='dynamicdns', domain_type='dynamicdnsservice', name=current_status['dynamicdns_domain'], description=_('Dynamic DNS Service'), services=services) + app.set_enabled(True) def setup(helper, old_version=None): diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 0da746558..020b10957 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -23,9 +23,10 @@ import logging from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules import config from plinth.signals import (domainname_change, post_hostname_change, pre_hostname_change) @@ -72,11 +73,25 @@ port_forwarding_info = [ logger = logging.getLogger(__name__) +app = None + + +class EjabberdApp(app_module.App): + """FreedomBox app for ejabberd.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-ejabberd', name, short_description, + 'ejabberd', 'ejabberd:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the ejabberd module""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'ejabberd', 'ejabberd:index', short_description) + global app + app = EjabberdApp() global service setup_helper = globals()['setup_helper'] @@ -88,7 +103,7 @@ def init(): enable=enable, disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('ejabberd:index') + app.set_enabled(True) pre_hostname_change.connect(on_pre_hostname_change) post_hostname_change.connect(on_post_hostname_change) @@ -113,8 +128,7 @@ def setup(helper, old_version=None): enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'ejabberd:index') + helper.call('post', app.enable) def add_shortcut(): @@ -133,16 +147,14 @@ def enable(): """Enable the module.""" actions.superuser_run('ejabberd', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('ejabberd:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('ejabberd', ['disable']) frontpage.remove_shortcut('ejabberd') - menu = main_menu.get('apps') - menu.demote_item('ejabberd:index') + app.disable() def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs): diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index 7eabc6c79..1076cfa5f 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -23,8 +23,9 @@ import logging from django.utils.translation import ugettext_lazy as _ import plinth.service as service_module -from plinth import actions, cfg -from plinth.menu import main_menu +from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth.signals import service_enabled from plinth.utils import Version, format_lazy @@ -52,11 +53,25 @@ LOGGER = logging.getLogger(__name__) _port_details = {} +app = None + + +class FirewallApp(app_module.App): + """FreedomBox app for Firewall.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-firewall', name, None, 'fa-shield', + 'firewall:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initailze firewall module""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-shield', 'firewall:index') + global app + app = FirewallApp() + app.set_enabled(True) service_enabled.connect(on_service_enabled) diff --git a/plinth/modules/help/help.py b/plinth/modules/help/help.py index d007b1499..5b01b9dd8 100644 --- a/plinth/modules/help/help.py +++ b/plinth/modules/help/help.py @@ -28,21 +28,43 @@ from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy -from plinth import __version__, actions, cfg -from plinth.menu import main_menu +from plinth import __version__, actions +from plinth import app as app_module +from plinth import cfg, menu + +app = None + + +class HelpApp(app_module.App): + """FreedomBox app for showing help.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-help', ugettext_lazy('Documentation'), + None, 'fa-book', 'help:index', + parent_url_name='index') + self.add(menu_item) + menu_item = menu.Menu('menu-help-manual', ugettext_lazy('Manual'), + None, 'fa-info-circle', 'help:manual', + parent_url_name='help:index', order=10) + self.add(menu_item) + menu_item = menu.Menu('menu-help-download-manual', + ugettext_lazy('Download Manual'), None, + 'fa-download', 'help:download-manual', + parent_url_name='help:index', order=15) + self.add(menu_item) + menu_item = menu.Menu('menu-help-about', ugettext_lazy('About'), None, + 'fa-star', 'help:about', + parent_url_name='help:index', order=100) + self.add(menu_item) def init(): """Initialize the Help module""" - menu = main_menu.add_urlname( - ugettext_lazy('Documentation'), 'fa-book', 'help:index') - menu.add_urlname( - ugettext_lazy('Manual'), 'fa-info-circle', 'help:manual', order=10) - menu.add_urlname( - ugettext_lazy('Download Manual'), 'fa-download', - 'help:download-manual', order=15) - menu.add_urlname( - ugettext_lazy('About'), 'fa-star', 'help:about', order=100) + global app + app = HelpApp() + app.set_enabled(True) def index(request): diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index 09513a2e3..a7d551624 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure I2P. from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules.i2p.resources import FAVORITES from plinth.modules.users import register_group @@ -72,11 +73,24 @@ tunnels_to_manage = { 'Irc2P': 'i2p-irc-freedombox' } +app = None + + +class I2PApp(app_module.App): + """FreedomBox app for I2P.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-i2p', name, short_description, 'i2p', + 'i2p:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'i2p', 'i2p:index', short_description) + global app + app = I2PApp() register_group(group) global service, proxies_service @@ -93,7 +107,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('i2p:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -118,7 +132,6 @@ def setup(helper, old_version=None): helper.call('post', actions.superuser_run, 'i2p', args) - # Tunnels to all interfaces for tunnel in tunnels_to_manage: helper.call('post', actions.superuser_run, 'i2p', [ @@ -140,8 +153,7 @@ def setup(helper, old_version=None): helper.call('post', service.notify_enabled, None, True) helper.call('post', proxies_service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'i2p:index') + helper.call('post', app.enable) def add_shortcut(): @@ -166,16 +178,14 @@ def enable(): """Enable the module.""" actions.superuser_run('i2p', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('i2p:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('i2p', ['disable']) frontpage.remove_shortcut('i2p') - menu = main_menu.get('apps') - menu.demote_item('i2p:index') + app.disable() def diagnose(): diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index 2187815d6..3fc827a8f 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure ikiwiki. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules.users import register_group from plinth.utils import format_lazy @@ -63,11 +64,25 @@ group = ('wiki', _('View and edit wiki applications')) manual_page = 'Ikiwiki' +app = None + + +class IkiwikiApp(app_module.App): + """FreedomBox app for Ikiwiki.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-ikiwiki', name, short_description, + 'ikiwiki', 'ikiwiki:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the ikiwiki module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'ikiwiki', 'ikiwiki:index', short_description) + global app + app = IkiwikiApp() register_group(group) global service @@ -78,7 +93,7 @@ def init(): is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): add_shortcuts() - menu.promote_item('ikiwiki:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -92,8 +107,7 @@ def setup(helper, old_version=None): is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcuts) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'ikiwiki:index') + helper.call('post', app.enable) def add_shortcuts(): @@ -114,16 +128,14 @@ def enable(): """Enable the module.""" actions.superuser_run('ikiwiki', ['enable']) add_shortcuts() - menu = main_menu.get('apps') - menu.promote_item('ikiwiki:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('ikiwiki', ['disable']) frontpage.remove_shortcut('ikiwiki*') - menu = main_menu.get('apps') - menu.demote_item('ikiwiki:index') + app.disable() def diagnose(): diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py index 38d930952..f2caae1a7 100644 --- a/plinth/modules/infinoted/__init__.py +++ b/plinth/modules/infinoted/__init__.py @@ -21,14 +21,13 @@ FreedomBox app for infinoted. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import actions -from plinth import action_utils -from plinth import cfg -from plinth import frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy from plinth.views import ServiceView + from .manifest import backup, clients version = 1 @@ -56,11 +55,25 @@ clients = clients port_forwarding_info = [('TCP', 6523)] +app = None + + +class InfinotedApp(app_module.App): + """FreedomBox app for infinoted.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-infinoted', name, short_description, + 'infinoted', 'infinoted:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the infinoted module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'infinoted', 'infinoted:index', short_description) + global app + app = InfinotedApp() global service setup_helper = globals()['setup_helper'] @@ -70,7 +83,7 @@ def init(): ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): add_shortcut() - menu.promote_item('infinoted:index') + app.set_enabled(True) class InfinotedServiceView(ServiceView): @@ -93,8 +106,7 @@ def setup(helper, old_version=None): helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'infinoted:index') + helper.call('post', app.enable) def add_shortcut(): @@ -108,16 +120,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('infinoted:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('infinoted') - menu = main_menu.get('apps') - menu.demote_item('infinoted:index') + app.disable() def diagnose(): diff --git a/plinth/modules/jsxc/__init__.py b/plinth/modules/jsxc/__init__.py index e2b9c02b2..e633bff58 100644 --- a/plinth/modules/jsxc/__init__.py +++ b/plinth/modules/jsxc/__init__.py @@ -23,9 +23,9 @@ import logging from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import frontpage +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from .manifest import backup, clients @@ -48,11 +48,24 @@ service = None logger = logging.getLogger(__name__) +app = None + + +class JSXCApp(app_module.App): + """FreedomBox app for JSXC.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-jsxc', name, short_description, 'jsxc', + 'jsxc:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the JSXC module""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'jsxc', 'jsxc:index', short_description) + global app + app = JSXCApp() global service setup_helper = globals()['setup_helper'] @@ -62,7 +75,7 @@ def init(): is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('jsxc:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -76,8 +89,7 @@ def setup(helper, old_version=None): is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'jsxc:index') + helper.call('post', app.enable) def add_shortcut(): @@ -94,11 +106,9 @@ def is_enabled(): def enable(): add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('jsxc:index') + app.enable() def disable(): frontpage.remove_shortcut('jsxc') - menu = main_menu.get('apps') - menu.demote_item('jsxc:index') + app.disable() diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 9c9fd5ea4..3d88cdc51 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -23,9 +23,10 @@ import logging from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, module_loader +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, menu, module_loader from plinth.errors import ActionError -from plinth.menu import main_menu from plinth.modules import config, names from plinth.signals import domain_added, domain_removed, domainname_change from plinth.utils import format_lazy @@ -67,11 +68,27 @@ MODULES_WITH_HOOKS = ['ejabberd', 'matrixsynapse'] LIVE_DIRECTORY = '/etc/letsencrypt/live/' logger = logging.getLogger(__name__) +app = None + + +class LetsEncryptApp(app_module.App): + """FreedomBox app for Let's Encrypt.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-letsencrypt', name, short_description, + 'fa-lock', 'letsencrypt:index', + parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-lock', 'letsencrypt:index', short_description) + global app + app = LetsEncryptApp() + app.set_enabled(True) + domainname_change.connect(on_domainname_change) domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) @@ -108,8 +125,8 @@ def enable_renewal_management(domain): try: actions.superuser_run('letsencrypt', ['manage_hooks', 'enable']) logger.info( - _('Certificate renewal management enabled for {domain}.') - .format(domain=domain)) + _('Certificate renewal management enabled for {domain}.'). + format(domain=domain)) except ActionError as exception: logger.error( _('Failed to enable certificate renewal management for ' @@ -188,8 +205,8 @@ def on_domain_removed(sender, domain_type, name='', **kwargs): return True except ActionError as exception: logger.warn( - ('Failed to revoke certificate for domain {domain}: {error}') - .format(domain=name, error=exception.args[2])) + ('Failed to revoke certificate for domain {domain}: {error}' + ).format(domain=name, error=exception.args[2])) return False diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index 5d809a8ed..3e477aef6 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -25,9 +25,10 @@ from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ from ruamel.yaml.util import load_yaml_guess_indent -from plinth import action_utils, actions, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from .manifest import backup, clients @@ -68,12 +69,25 @@ logger = logging.getLogger(__name__) SERVER_NAME_PATH = "/etc/matrix-synapse/conf.d/server_name.yaml" CONFIG_FILE_PATH = '/etc/matrix-synapse/homeserver.yaml' +app = None + + +class MatrixSynapseApp(app_module.App): + """FreedomBox app for Matrix Synapse.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-matrixsynapse', name, short_description, + 'matrixsynapse', 'matrixsynapse:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the matrix-synapse module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'matrixsynapse', 'matrixsynapse:index', - short_description) + global app + app = MatrixSynapseApp() global service setup_helper = globals()['setup_helper'] @@ -84,7 +98,7 @@ def init(): disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('matrixsynapse:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -101,8 +115,7 @@ def setup(helper, old_version=None): ['post-install']) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'matrixsynapse:index') + helper.call('post', app.enable) def add_shortcut(): @@ -127,16 +140,14 @@ def enable(): """Enable the module.""" actions.superuser_run('matrixsynapse', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('matrixsynapse:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('matrixsynapse', ['disable']) frontpage.remove_shortcut('matrixsynapse') - menu = main_menu.get('apps') - menu.demote_item('matrixsynapse:index') + app.disable() def diagnose(): diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index bb7884511..7d8ab3b93 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure MediaWiki. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from .manifest import backup, clients @@ -55,11 +56,25 @@ manual_page = 'MediaWiki' clients = clients +app = None + + +class MediaWikiApp(app_module.App): + """FreedomBox app for MediaWiki.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-mediawiki', name, short_description, + 'mediawiki', 'mediawiki:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'mediawiki', 'mediawiki:index', short_description) + global app + app = MediaWikiApp() global service setup_helper = globals()['setup_helper'] @@ -69,7 +84,7 @@ def init(): is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): add_shortcut() - menu.promote_item('mediawiki:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -91,8 +106,7 @@ def setup(helper, old_version=None): ) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'mediawiki:index') + helper.call('post', app.enable) def add_shortcut(): @@ -111,16 +125,14 @@ def enable(): """Enable the module.""" actions.superuser_run('mediawiki', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('mediawiki:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('mediawiki', ['disable']) frontpage.remove_shortcut('mediawiki') - menu = main_menu.get('apps') - menu.demote_item('mediawiki:index') + app.disable() def diagnose(): diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py index e3010f220..3f75e5daa 100644 --- a/plinth/modules/minetest/__init__.py +++ b/plinth/modules/minetest/__init__.py @@ -22,9 +22,10 @@ import augeas from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, cfg, frontpage -from plinth.menu import main_menu from plinth.utils import format_lazy from .manifest import backup, clients @@ -73,11 +74,25 @@ reserved_usernames = ['Debian-minetest'] CONFIG_FILE = '/etc/minetest/minetest.conf' AUG_PATH = '/files' + CONFIG_FILE + '/.anon' +app = None + + +class MinetestApp(app_module.App): + """FreedomBox app for Minetest.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-minetest', name, short_description, + 'minetest', 'minetest:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'minetest', 'minetest:index', short_description) + global app + app = MinetestApp() global service setup_helper = globals()['setup_helper'] @@ -87,7 +102,7 @@ def init(): ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): add_shortcut() - menu.promote_item('minetest:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -100,8 +115,7 @@ def setup(helper, old_version=None): ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'minetest:index') + helper.call('post', app.enable) def add_shortcut(): @@ -115,16 +129,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('minetest:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('minetest') - menu = main_menu.get('apps') - menu.demote_item('minetest:index') + app.disable() def diagnose(): diff --git a/plinth/modules/mldonkey/__init__.py b/plinth/modules/mldonkey/__init__.py index 11b103429..9ba19e0a6 100644 --- a/plinth/modules/mldonkey/__init__.py +++ b/plinth/modules/mldonkey/__init__.py @@ -20,9 +20,10 @@ FreedomBox app for mldonkey. from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules.users import register_group from plinth.utils import format_lazy @@ -61,11 +62,25 @@ service = None manual_page = 'MLDonkey' +app = None + + +class MLDonkeyApp(app_module.App): + """FreedomBox app for MLDonkey.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-mldonkey', name, short_description, + 'mldonkey', 'mldonkey:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the MLDonkey module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'mldonkey', 'mldonkey:index', short_description) + global app + app = MLDonkeyApp() register_group(group) global service @@ -79,7 +94,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('mldonkey:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -96,8 +111,7 @@ def setup(helper, old_version=None): is_running=is_running) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'mldonkey:index') + helper.call('post', app.enable) def add_shortcut(): @@ -122,16 +136,14 @@ def enable(): """Enable the module.""" actions.superuser_run('mldonkey', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('mldonkey:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('mldonkey', ['disable']) frontpage.remove_shortcut('mldonkey') - menu = main_menu.get('apps') - menu.demote_item('mldonkey:index') + app.disable() def diagnose(): diff --git a/plinth/modules/monkeysphere/__init__.py b/plinth/modules/monkeysphere/__init__.py index 22e579943..cd406504d 100644 --- a/plinth/modules/monkeysphere/__init__.py +++ b/plinth/modules/monkeysphere/__init__.py @@ -20,7 +20,8 @@ FreedomBox app for monkeysphere. from django.utils.translation import ugettext_lazy as _ -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu from .manifest import backup @@ -53,12 +54,26 @@ manual_page = "Monkeysphere" reserved_usernames = ['monkeysphere'] +app = None + + +class MonkeysphereApp(app_module.App): + """FreedomBox app for Monkeysphere.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-monkeysphere', name, None, + 'fa-certificate', 'monkeysphere:index', + parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the monkeysphere module.""" - menu = main_menu.get('system') - menu.add_urlname( - _('Monkeysphere'), 'fa-certificate', 'monkeysphere:index') + global app + app = MonkeysphereApp() + app.set_enabled(True) def setup(helper, old_version=None): diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index a315939c3..5612a2ef7 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure Mumble server. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from plinth.views import ServiceView from .manifest import backup, clients @@ -59,11 +60,24 @@ port_forwarding_info = [ ('UDP', 64738), ] +app = None + + +class MumbleApp(app_module.App): + """FreedomBox app for Mumble.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-mumble', name, short_description, 'mumble', + 'mumble:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the Mumble module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'mumble', 'mumble:index', short_description) + global app + app = MumbleApp() global service setup_helper = globals()['setup_helper'] @@ -74,7 +88,7 @@ def init(): if service.is_enabled(): add_shortcut() - menu.promote_item('mumble:index') + app.set_enabled(True) class MumbleServiceView(ServiceView): @@ -96,8 +110,7 @@ def setup(helper, old_version=None): ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'mumble:index') + helper.call('post', app.enable) def add_shortcut(): @@ -111,16 +124,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('mumble:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('mumble') - menu = main_menu.get('apps') - menu.demote_item('mumble:index') + app.disable() def diagnose(): diff --git a/plinth/modules/names/__init__.py b/plinth/modules/names/__init__.py index 4de490e40..c244ba7aa 100644 --- a/plinth/modules/names/__init__.py +++ b/plinth/modules/names/__init__.py @@ -22,8 +22,8 @@ import logging from django.utils.translation import ugettext_lazy as _ -from plinth import cfg -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import cfg, menu from plinth.signals import domain_added, domain_removed from plinth.utils import format_lazy @@ -57,11 +57,25 @@ description = [ 'connections through the given name.'), box_name=(cfg.box_name)) ] +app = None + + +class NamesApp(app_module.App): + """FreedomBox app for names.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-names', name, None, 'fa-tags', + 'names:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the names module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-tags', 'names:index') + global app + app = NamesApp() + app.set_enabled(True) domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) diff --git a/plinth/modules/networks/__init__.py b/plinth/modules/networks/__init__.py index 555cb9a13..02ee855a9 100644 --- a/plinth/modules/networks/__init__.py +++ b/plinth/modules/networks/__init__.py @@ -23,8 +23,9 @@ from logging import Logger from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, network -from plinth.menu import main_menu +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import menu, network version = 1 @@ -38,17 +39,32 @@ logger = Logger(__name__) manual_page = 'Networks' +app = None + + +class NetworksApp(app_module.App): + """FreedomBox app for Networks.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-networks', name, None, 'fa-signal', + 'networks:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the Networks module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-signal', 'networks:index') + global app + app = NetworksApp() + app.set_enabled(True) def setup(helper, old_version=None): """Install and configure the module.""" helper.install(managed_packages) actions.superuser_run('networks') + helper.call('post', app.enable) def diagnose(): diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index 028e0a4ff..1aade3a24 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure OpenVPN server. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy from .manifest import backup @@ -55,11 +56,25 @@ manual_page = 'OpenVPN' port_forwarding_info = [('UDP', 1194)] +app = None + + +class OpenVPNApp(app_module.App): + """FreedomBox app for OpenVPN.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-openvpn', name, short_description, + 'openvpn', 'openvpn:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the OpenVPN module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'openvpn', 'openvpn:index', short_description) + global app + app = OpenVPNApp() global service setup_helper = globals()['setup_helper'] @@ -69,7 +84,7 @@ def init(): if service.is_enabled() and is_setup(): add_shortcut() - menu.promote_item('openvpn:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -84,8 +99,7 @@ def setup(helper, old_version=None): if service.is_enabled() and is_setup(): add_shortcut() - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'openvpn:index') + helper.call('post', app.enable) def add_shortcut(): @@ -109,16 +123,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('openvpn:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('openvpn') - menu = main_menu.get('apps') - menu.demote_item('openvpn:index') + app.disable() def diagnose(): diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index bc92942f5..3304637da 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -20,8 +20,8 @@ FreedomBox app to configure PageKite. from django.utils.translation import ugettext_lazy as _ -from plinth import cfg -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import cfg, menu from plinth.utils import format_lazy from . import utils @@ -76,12 +76,30 @@ description = [ manual_page = 'PageKite' +app = None + + +class PagekiteApp(app_module.App): + """FreedomBox app for Pagekite.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-pagekite', name, short_description, + 'fa-flag', 'pagekite:index', + parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the PageKite module""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-flag', 'pagekite:index', - short_description) + global app + app = PagekiteApp() + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + app.set_enabled(True) # XXX: Perform more proper check # Register kite name with Name Services module. utils.update_names_module(initial_registration=True) @@ -90,3 +108,4 @@ def init(): def setup(helper, old_version=None): """Install and configure the module.""" helper.install(managed_packages) + helper.call('post', app.enable) diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index 7297c6c11..c82d06182 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure Privoxy. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, cfg, frontpage -from plinth.menu import main_menu from plinth.utils import format_lazy from plinth.views import ServiceView @@ -62,11 +63,25 @@ service = None manual_page = 'Privoxy' +app = None + + +class PrivoxyApp(app_module.App): + """FreedomBox app for Privoxy.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-privoxy', name, short_description, + 'privoxy', 'privoxy:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'privoxy', 'privoxy:index', short_description) + global app + app = PrivoxyApp() global service setup_helper = globals()['setup_helper'] @@ -77,7 +92,7 @@ def init(): if service.is_enabled(): add_shortcut() - menu.promote_item('privoxy:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -91,8 +106,7 @@ def setup(helper, old_version=None): enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'privoxy:index') + helper.call('post', app.enable) def add_shortcut(): @@ -106,16 +120,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('privoxy:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('privoxy') - menu = main_menu.get('apps') - menu.demote_item('privoxy:index') + app.disable() class PrivoxyServiceView(ServiceView): diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 69d3f1509..63494a018 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -21,9 +21,10 @@ FreedomBox app for Quassel. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, cfg, frontpage -from plinth.menu import main_menu from plinth.utils import format_lazy from plinth.views import ServiceView @@ -65,11 +66,25 @@ manual_page = 'Quassel' port_forwarding_info = [('TCP', 4242)] +app = None + + +class QuasselApp(app_module.App): + """FreedomBox app for Quassel.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-quassel', name, short_description, + 'quassel', 'quassel:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the quassel module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'quassel', 'quassel:index', short_description) + global app + app = QuasselApp() global service setup_helper = globals()['setup_helper'] @@ -80,7 +95,7 @@ def init(): if service.is_enabled(): add_shortcut() - menu.promote_item('quassel:index') + app.set_enabled(True) class QuasselServiceView(ServiceView): @@ -102,8 +117,7 @@ def setup(helper, old_version=None): ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'quassel:index') + helper.call('post', app.enable) def add_shortcut(): @@ -117,16 +131,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('quassel:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('quassel') - menu = main_menu.get('apps') - menu.demote_item('quassel:index') + app.disable() def diagnose(): diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py index 8f565650e..5d1e94b27 100644 --- a/plinth/modules/radicale/__init__.py +++ b/plinth/modules/radicale/__init__.py @@ -22,13 +22,14 @@ import logging import subprocess from distutils.version import LooseVersion as LV -from apt.cache import Cache import augeas +from apt.cache import Cache from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy from .manifest import backup, clients @@ -69,11 +70,25 @@ CONFIG_FILE = '/etc/radicale/config' VERSION_2 = LV('2') +app = None + + +class RadicaleApp(app_module.App): + """FreedomBox app for Radicale.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-radicale', name, short_description, + 'radicale', 'radicale:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the radicale module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'radicale', 'radicale:index', short_description) + global app + app = RadicaleApp() global service setup_helper = globals()['setup_helper'] @@ -86,7 +101,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('radicale:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -124,8 +139,7 @@ def setup(helper, old_version=None): is_running=is_running) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'radicale:index') + helper.call('post', app.enable) def add_shortcut(): @@ -182,16 +196,14 @@ def enable(): """Enable the module.""" actions.superuser_run('radicale', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('radicale:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('radicale', ['disable']) frontpage.remove_shortcut('radicale') - menu = main_menu.get('apps') - menu.demote_item('radicale:index') + app.disable() def load_augeas(): diff --git a/plinth/modules/repro/__init__.py b/plinth/modules/repro/__init__.py index 37d01d504..aebbfc434 100644 --- a/plinth/modules/repro/__init__.py +++ b/plinth/modules/repro/__init__.py @@ -21,9 +21,10 @@ FreedomBox app for repro. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from plinth.views import ServiceView from .manifest import backup, clients @@ -67,12 +68,24 @@ manual_page = 'Repro' port_forwarding_info = [('UDP', '1024-65535')] +app = None + + +class ReproApp(app_module.App): + """FreedomBox app for Repro.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-repro', name, short_description, 'repro', + 'repro:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the repro module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'repro', 'repro:index', - short_description) + global app + app = ReproApp() global service setup_helper = globals()['setup_helper'] @@ -83,7 +96,7 @@ def init(): if service.is_enabled(): add_shortcut() - menu.promote_item('repro:index') + app.set_enabled(True) class ReproServiceView(ServiceView): @@ -106,8 +119,7 @@ def setup(helper, old_version=None): ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'repro:index') + helper.call('post', app.enable) def add_shortcut(): @@ -121,16 +133,14 @@ def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('repro:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('repro') - menu = main_menu.get('apps') - menu.demote_item('repro:index') + app.disable() def diagnose(): diff --git a/plinth/modules/restore/__init__.py b/plinth/modules/restore/__init__.py index 70f1f2540..1af05bedb 100644 --- a/plinth/modules/restore/__init__.py +++ b/plinth/modules/restore/__init__.py @@ -20,12 +20,12 @@ FreedomBox app to configure reStore. from django.utils.translation import ugettext_lazy as _ -from plinth import cfg +from plinth import app as app_module +from plinth import cfg, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy -from .manifest import clients +from .manifest import clients version = 1 @@ -45,7 +45,6 @@ description = [ 'served from, the data can be stored on an unhosted storage ' 'server of user\'s choice. With reStore, your {box_name} becomes ' 'your unhosted storage server.'), box_name=_(cfg.box_name)), - _('You can create and edit accounts in the ' 'reStore web-interface.') ] @@ -56,18 +55,32 @@ reserved_usernames = ['node-restore'] service = None +app = None + + +class RestoreApp(app_module.App): + """FreedomBox app for Restore.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-restore', name, short_description, + 'fa-hdd-o', 'restore:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the reStore module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'fa-hdd-o', 'restore:index', short_description) + global app + app = RestoreApp() global service setup_helper = globals()['setup_helper'] if setup_helper.get_state() != 'needs-setup': - service = service_module.Service( - managed_services[0], name, ports=['http', 'https'], - is_external=False) + service = service_module.Service(managed_services[0], name, + ports=['http', + 'https'], is_external=False) def setup(helper, old_version=None): @@ -75,6 +88,6 @@ def setup(helper, old_version=None): helper.install(managed_packages) global service if service is None: - service = service_module.Service( - managed_services[0], name, ports=['http', 'https'], - is_external=False) + service = service_module.Service(managed_services[0], name, + ports=['http', + 'https'], is_external=False) diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 618ba9c18..0011278b7 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure Roundcube. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from .manifest import backup, clients @@ -60,11 +61,25 @@ service = None manual_page = 'Roundcube' +app = None + + +class RoundcubeApp(app_module.App): + """FreedomBox app for Roundcube.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-roundcube', name, short_description, + 'roundcube', 'roundcube:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'roundcube', 'roundcube:index', short_description) + global app + app = RoundcubeApp() global service setup_helper = globals()['setup_helper'] @@ -75,7 +90,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('roundcube:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -89,8 +104,7 @@ def setup(helper, old_version=None): service = service_module.Service( 'roundcube', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'roundcube:index') + helper.call('post', app.enable) def add_shortcut(): @@ -108,16 +122,14 @@ def enable(): """Enable the module.""" actions.superuser_run('roundcube', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('roundcube:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('roundcube', ['disable']) frontpage.remove_shortcut('roundcube') - menu = main_menu.get('apps') - menu.demote_item('roundcube:index') + app.disable() def diagnose(): diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py index b559b1e4b..342c4552c 100644 --- a/plinth/modules/searx/__init__.py +++ b/plinth/modules/searx/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure Searx. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from plinth.modules.users import register_group from .manifest import backup, clients @@ -52,11 +53,24 @@ service = None manual_page = 'Searx' +app = None + + +class SearxApp(app_module.App): + """FreedomBox app for Searx.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-searx', name, short_description, 'searx', + 'searx:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'searx', 'searx:index', short_description) + global app + app = SearxApp() register_group(group) global service @@ -69,7 +83,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('searx:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -86,8 +100,7 @@ def setup(helper, old_version=None): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'searx:index') + helper.call('post', app.enable) def add_shortcut(): @@ -113,16 +126,14 @@ def enable(): """Enable the module.""" actions.superuser_run('searx', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('searx:index') + app.enable() def disable(): """Disable the module.""" actions.superuser_run('searx', ['disable']) frontpage.remove_shortcut('searx') - menu = main_menu.get('apps') - menu.demote_item('searx:index') + app.disable() def diagnose(): diff --git a/plinth/modules/security/__init__.py b/plinth/modules/security/__init__.py index 4eea8e386..2e5a7f4a9 100644 --- a/plinth/modules/security/__init__.py +++ b/plinth/modules/security/__init__.py @@ -21,7 +21,8 @@ FreedomBox app for security configuration. from django.utils.translation import ugettext_lazy as _ from plinth import actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu from .manifest import backup @@ -43,11 +44,25 @@ ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx plinth (admin) (sudo):ALL' OLD_ACCESS_CONF_SNIPPET = '-:ALL EXCEPT root fbx (admin) (sudo):ALL' ACCESS_CONF_SNIPPETS = [OLD_ACCESS_CONF_SNIPPET, ACCESS_CONF_SNIPPET] +app = None + + +class SecurityApp(app_module.App): + """FreedomBox app for security.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-security', name, None, 'fa-lock', + 'security:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the module""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-lock', 'security:index') + global app + app = SecurityApp() + app.set_enabled(True) def setup(helper, old_version=None): diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index 1193a0633..0016c34dc 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure Shaarli. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from .manifest import clients @@ -48,12 +49,25 @@ service = None manual_page = 'Shaarli' +app = None + + +class ShaarliApp(app_module.App): + """FreedomBox app for Shaarli.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-shaarli', name, short_description, + 'shaarli', 'shaarli:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'fa-bookmark', 'shaarli:index', - short_description) + global app + app = ShaarliApp() global service setup_helper = globals()['setup_helper'] @@ -64,7 +78,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('shaarli:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -77,8 +91,7 @@ def setup(helper, old_version=None): is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'shaarli:index') + helper.call('post', app.enable) def add_shortcut(): @@ -96,13 +109,11 @@ def enable(): """Enable the module.""" actions.superuser_run('shaarli', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('shaarli:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('shaarli', ['disable']) frontpage.remove_shortcut('shaarli') - menu = main_menu.get('apps') - menu.demote_item('shaarli:index') + app.disable() diff --git a/plinth/modules/shadowsocks/__init__.py b/plinth/modules/shadowsocks/__init__.py index 9686c4e82..7ef04a8e7 100644 --- a/plinth/modules/shadowsocks/__init__.py +++ b/plinth/modules/shadowsocks/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure Shadowsocks. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, cfg, frontpage -from plinth.menu import main_menu from plinth.utils import format_lazy from .manifest import backup @@ -56,12 +57,25 @@ description = [ manual_page = 'Shadowsocks' +app = None + + +class ShadowsocksApp(app_module.App): + """FreedomBox app for Shadowsocks.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-shadowsocks', name, short_description, + 'shadowsocks', 'shadowsocks:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'shadowsocks', 'shadowsocks:index', - short_description) + global app + app = ShadowsocksApp() global service setup_helper = globals()['setup_helper'] @@ -73,7 +87,7 @@ def init(): if service.is_enabled(): add_shortcut() - menu.promote_item('shadowsocks:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -87,8 +101,7 @@ def setup(helper, old_version=None): ], is_external=False, is_enabled=is_enabled, is_running=is_running, enable=enable, disable=disable) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'shadowsocks:index') + helper.call('post', app.enable) def add_shortcut(): @@ -113,16 +126,14 @@ def enable(): """Enable service.""" actions.superuser_run('service', ['enable', managed_services[0]]) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('shadowsocks:index') + app.enable() def disable(): """Disable service.""" actions.superuser_run('service', ['disable', managed_services[0]]) frontpage.remove_shortcut('shadowsocks') - menu = main_menu.get('apps') - menu.demote_item('shadowsocks:index') + app.disable() def diagnose(): diff --git a/plinth/modules/sharing/__init__.py b/plinth/modules/sharing/__init__.py index dce73954f..5398ab2eb 100644 --- a/plinth/modules/sharing/__init__.py +++ b/plinth/modules/sharing/__init__.py @@ -22,8 +22,9 @@ import json from django.utils.translation import ugettext_lazy as _ -from plinth import actions, cfg -from plinth.menu import main_menu +from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth.utils import format_lazy from .manifest import backup @@ -35,16 +36,29 @@ name = _('Sharing') description = [ format_lazy( _('Sharing allows you to share files and folders on your {box_name} ' - 'over the web with chosen groups of users.'), box_name=_( - cfg.box_name)) + 'over the web with chosen groups of users.'), + box_name=_(cfg.box_name)) ] +app = None + + +class SharingApp(app_module.App): + """FreedomBox app for sharing files.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-sharing', name, None, 'sharing', + 'sharing:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'sharing', 'sharing:index') - menu.promote_item('sharing:index') + global app + app = SharingApp() + app.set_enabled(True) def list_shares(): diff --git a/plinth/modules/snapshot/__init__.py b/plinth/modules/snapshot/__init__.py index 324b25e15..cac33421f 100644 --- a/plinth/modules/snapshot/__init__.py +++ b/plinth/modules/snapshot/__init__.py @@ -24,7 +24,8 @@ import augeas from django.utils.translation import ugettext_lazy as _ from plinth import actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu from plinth.modules import storage from .manifest import backup @@ -57,11 +58,29 @@ DEFAULT_FILE = '/etc/default/snapper' fs_types_supported = ['btrfs'] +app = None + + +class SnapshotApp(app_module.App): + """FreedomBox app for snapshots.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-snapshot', name, None, 'fa-film', + 'snapshot:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-film', 'snapshot:index') + global app + app = SnapshotApp() + + global service + setup_helper = globals()['setup_helper'] + if setup_helper.get_state() != 'needs-setup': + app.set_enabled(True) # XXX: Perform better checks def is_supported(): @@ -77,12 +96,13 @@ def setup(helper, old_version=None): helper.call('post', actions.superuser_run, 'snapshot', ['setup', '--old-version', str(old_version)]) + helper.call('post', app.enable) def load_augeas(): """Initialize Augeas.""" - aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + - augeas.Augeas.NO_MODL_AUTOLOAD) + aug = augeas.Augeas( + flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) # shell-script config file lens aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns') diff --git a/plinth/modules/ssh/__init__.py b/plinth/modules/ssh/__init__.py index d4d274fc2..b876b2985 100644 --- a/plinth/modules/ssh/__init__.py +++ b/plinth/modules/ssh/__init__.py @@ -21,8 +21,9 @@ FreedomBox app for OpenSSH server. from django.utils.translation import ugettext_lazy as _ from plinth import actions +from plinth import app as app_module +from plinth import cfg, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.views import ServiceView from .manifest import backup @@ -48,15 +49,29 @@ service = None port_forwarding_info = [('TCP', 22)] +app = None + + +class SSHApp(app_module.App): + """FreedomBox app for SSH.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-ssh', name, None, 'fa-terminal', + 'ssh:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the ssh module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-terminal', 'ssh:index') + global app + app = SSHApp() + app.set_enabled(True) global service - service = service_module.Service( - managed_services[0], name, ports=['ssh'], is_external=True) + service = service_module.Service(managed_services[0], name, ports=['ssh'], + is_external=True) def setup(helper, old_version=None): diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py index 2b1245bdd..653af48f7 100644 --- a/plinth/modules/storage/__init__.py +++ b/plinth/modules/storage/__init__.py @@ -23,11 +23,12 @@ import subprocess import psutil from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, menu from plinth import service as service_module from plinth import utils from plinth.errors import PlinthError -from plinth.menu import main_menu from plinth.utils import format_lazy, import_from_gi version = 3 @@ -54,11 +55,25 @@ manual_page = 'Storage' is_essential = True +app = None + + +class StorageApp(app_module.App): + """FreedomBox app for storage.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-storage', name, None, 'fa-hdd-o', + 'storage:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-hdd-o', 'storage:index') + global app + app = StorageApp() + app.set_enabled(True) def get_disks(): @@ -267,11 +282,13 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('udiskie', ['enable']) + app.enable() def disable(): """Disable the module.""" actions.superuser_run('udiskie', ['disable']) + app.disable() def setup(helper, old_version=None): diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index 8070b399f..97443bd8f 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -20,9 +20,10 @@ FreedomBox app to configure Syncthing. from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, cfg, frontpage -from plinth.menu import main_menu from plinth.modules.users import register_group from plinth.utils import format_lazy @@ -64,11 +65,25 @@ service = None manual_page = 'Syncthing' +app = None + + +class SyncthingApp(app_module.App): + """FreedomBox app for Syncthing.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-syncthing', name, short_description, + 'syncthing', 'syncthing:index', + parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'syncthing', 'syncthing:index', short_description) + global app + app = SyncthingApp() register_group(group) global service @@ -82,7 +97,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('syncthing:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -98,8 +113,7 @@ def setup(helper, old_version=None): is_running=is_running) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'syncthing:index') + helper.call('post', app.enable) def add_shortcut(): @@ -124,16 +138,14 @@ def enable(): """Enable the module.""" actions.superuser_run('syncthing', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('syncthing:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('syncthing', ['disable']) frontpage.remove_shortcut('syncthing') - menu = main_menu.get('apps') - menu.demote_item('syncthing:index') + app.disable() def diagnose(): diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py index a4b83c8ad..514ba68b6 100644 --- a/plinth/modules/tahoe/__init__.py +++ b/plinth/modules/tahoe/__init__.py @@ -23,9 +23,10 @@ import os from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.utils import format_lazy from .errors import TahoeConfigurationError @@ -57,6 +58,20 @@ introducers_file = os.path.join( introducer_furl_file = os.path.join( tahoe_home, '{0}/private/{0}.furl'.format(introducer_name)) +app = None + + +class TahoeApp(app_module.App): + """FreedomBox app for Tahoe LAFS.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-tahoe', name, short_description, + 'tahoe-lafs', 'tahoe:index', + parent_url_name='apps') + self.add(menu_item) + def is_setup(): """Check whether Tahoe-LAFS is setup""" @@ -88,8 +103,8 @@ description = [ def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'tahoe-lafs', 'tahoe:index', short_description) + global app + app = TahoeApp() global service setup_helper = globals()['setup_helper'] @@ -102,7 +117,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('tahoe:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -130,8 +145,7 @@ def post_setup(configured_domain_name): is_running=is_running) service.notify_enabled(None, True) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('tahoe:index') + app.enable() def add_shortcut(): @@ -158,16 +172,14 @@ def enable(): """Enable the module.""" actions.superuser_run('tahoe-lafs', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('tahoe:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('tahoe-lafs', ['disable']) frontpage.remove_shortcut('tahoe-lafs') - menu = main_menu.get('apps') - menu.demote_item('tahoe:index') + app.disable() def diagnose(): diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index e290e3689..376eff7d2 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -22,9 +22,10 @@ import json from django.utils.translation import ugettext_lazy as _ -from plinth import service as service_module from plinth import action_utils, actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu +from plinth import service as service_module from plinth.modules.names import SERVICES from plinth.signals import domain_added, domain_removed @@ -61,18 +62,31 @@ bridge_service = None manual_page = 'Tor' +app = None + + +class TorApp(app_module.App): + """FreedomBox app for Tor.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-tor', name, short_description, 'tor', + 'tor:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'tor', 'tor:index', short_description) + global app + app = TorApp() setup_helper = globals()['setup_helper'] needs_setup = setup_helper.get_state() == 'needs-setup' if not needs_setup: if utils.is_enabled(): - menu.promote_item('tor:index') + app.set_enabled(True) global socks_service socks_service = service_module.Service( @@ -137,8 +151,7 @@ def setup(helper, old_version=None): helper.call('post', bridge_service.notify_enabled, None, True) helper.call('post', update_hidden_service_domain) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'tor:index') + helper.call('post', app.enable) def enable(): @@ -148,8 +161,7 @@ def enable(): elsewhere. """ - menu = main_menu.get('apps') - menu.promote_item('tor:index') + app.enable() def disable(): @@ -159,8 +171,7 @@ def disable(): elsewhere. """ - menu = main_menu.get('apps') - menu.demote_item('tor:index') + app.disable() def update_hidden_service_domain(status=None): diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index ec19c9b05..7db829e7e 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -22,9 +22,10 @@ import json from django.utils.translation import ugettext_lazy as _ +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import frontpage, menu from plinth import service as service_module -from plinth import action_utils, actions, frontpage -from plinth.menu import main_menu from plinth.modules.users import register_group from .manifest import backup, clients @@ -56,12 +57,25 @@ service = None manual_page = 'Transmission' +app = None + + +class TransmissionApp(app_module.App): + """FreedomBox app for Transmission.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-transmission', name, short_description, + 'transmission', 'transmission:index', + parent_url_name='apps') + self.add(menu_item) + def init(): - """Intialize the Transmission module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'transmission', 'transmission:index', - short_description) + """Initialize the Transmission module.""" + global app + app = TransmissionApp() register_group(group) global service @@ -74,7 +88,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('transmission:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -98,8 +112,7 @@ def setup(helper, old_version=None): disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'transmission:index') + helper.call('post', app.enable) def add_shortcut(): @@ -118,16 +131,14 @@ def enable(): """Enable the module.""" actions.superuser_run('transmission', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('transmission:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('transmission', ['disable']) frontpage.remove_shortcut('transmission') - menu = main_menu.get('apps') - menu.demote_item('transmission:index') + app.disable() def diagnose(): diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py index 8068fbdd0..3dd64f80e 100644 --- a/plinth/modules/ttrss/__init__.py +++ b/plinth/modules/ttrss/__init__.py @@ -21,9 +21,10 @@ FreedomBox app to configure Tiny Tiny RSS. from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ -from plinth import action_utils, actions, cfg, frontpage +from plinth import action_utils, actions +from plinth import app as app_module +from plinth import cfg, frontpage, menu from plinth import service as service_module -from plinth.menu import main_menu from plinth.modules.users import register_group from plinth.utils import Version, format_lazy @@ -63,11 +64,24 @@ service = None manual_page = 'TinyTinyRSS' +app = None + + +class TTRSSApp(app_module.App): + """FreedomBox app for TT-RSS.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-ttrss', name, short_description, 'ttrss', + 'ttrss:index', parent_url_name='apps') + self.add(menu_item) + def init(): """Intialize the module.""" - menu = main_menu.get('apps') - menu.add_urlname(name, 'ttrss', 'ttrss:index', short_description) + global app + app = TTRSSApp() register_group(group) global service @@ -80,7 +94,7 @@ def init(): if is_enabled(): add_shortcut() - menu.promote_item('ttrss:index') + app.set_enabled(True) def setup(helper, old_version=None): @@ -97,8 +111,7 @@ def setup(helper, old_version=None): disable=disable) helper.call('post', service.notify_enabled, None, True) helper.call('post', add_shortcut) - menu = main_menu.get('apps') - helper.call('post', menu.promote_item, 'ttrss:index') + helper.call('post', app.enable) def force_upgrade(helper, packages): @@ -133,16 +146,14 @@ def enable(): """Enable the module.""" actions.superuser_run('ttrss', ['enable']) add_shortcut() - menu = main_menu.get('apps') - menu.promote_item('ttrss:index') + app.enable() def disable(): """Enable the module.""" actions.superuser_run('ttrss', ['disable']) frontpage.remove_shortcut('ttrss') - menu = main_menu.get('apps') - menu.demote_item('ttrss:index') + app.disable() def diagnose(): diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index d9e9607ec..b12876e4a 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -20,9 +20,10 @@ FreedomBox app for upgrades. from django.utils.translation import ugettext_lazy as _ -from plinth import service as service_module from plinth import actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu +from plinth import service as service_module from .manifest import backup @@ -42,11 +43,26 @@ service = None manual_page = 'Upgrades' +app = None + + +class UpgradesApp(app_module.App): + """FreedomBox app for software upgrades.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-upgrades', name, None, 'fa-refresh', + 'upgrades:index', parent_url_name='system') + self.add(menu_item) + def init(): """Initialize the module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-refresh', 'upgrades:index') + global app + app = UpgradesApp() + app.set_enabled(True) + global service service = service_module.Service('auto-upgrades', name, is_external=False, is_enabled=is_enabled, enable=enable, diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index 6d17657e7..3c42867c8 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -23,7 +23,8 @@ import subprocess from django.utils.translation import ugettext_lazy as _ from plinth import action_utils, actions -from plinth.menu import main_menu +from plinth import app as app_module +from plinth import menu version = 2 @@ -47,11 +48,25 @@ name = _('Users and Groups') # All FreedomBox user groups groups = dict() +app = None + + +class UsersApp(app_module.App): + """FreedomBox app for users and groups management.""" + + def __init__(self): + """Create components for the app.""" + super().__init__() + menu_item = menu.Menu('menu-users', name, None, 'fa-users', + 'users:index', parent_url_name='system') + self.add(menu_item) + def init(): """Intialize the user module.""" - menu = main_menu.get('system') - menu.add_urlname(name, 'fa-users', 'users:index') + global app + app = UsersApp() + app.set_enabled(True) def setup(helper, old_version=None): diff --git a/plinth/templates/cards.html b/plinth/templates/cards.html index 9180d3425..014b648fb 100644 --- a/plinth/templates/cards.html +++ b/plinth/templates/cards.html @@ -34,7 +34,7 @@
{% for item in submenu.sorted_items %} - {% if not show_secondary or not item.secondary %} + {% if not show_disabled or item.is_enabled %} -
{{ item.short_description}}
+
{{ item.short_description|default:'' }}
{% endif %} @@ -54,13 +54,13 @@
- {% if show_secondary %} + {% if show_disabled %}
{% trans "Disabled" %}
-
+
{% for item in submenu.sorted_items %} - {% if item.secondary %} + {% if not item.is_enabled %} -
{{ item.short_description}}
+
{{ item.short_description|default:'' }}
{% endif %} diff --git a/plinth/templates/index.html b/plinth/templates/index.html index 3fb1a5111..a95144f04 100644 --- a/plinth/templates/index.html +++ b/plinth/templates/index.html @@ -87,7 +87,7 @@ {% endif %}
- {{ shortcut.short_description }} + {{ shortcut.short_description|default:'' }}
diff --git a/plinth/templates/setup.html b/plinth/templates/setup.html index c9c18e9cd..476b6a2f7 100644 --- a/plinth/templates/setup.html +++ b/plinth/templates/setup.html @@ -36,7 +36,7 @@ {% block content %} -

{% trans "Installation" %}: {{ setup_helper.module.short_description }} ({{ setup_helper.module.name }})

+

{% trans "Installation" %}: {{ setup_helper.module.short_description|default:'' }} ({{ setup_helper.module.name }})

{% for paragraph in setup_helper.module.description %}

{{ paragraph|safe }}

diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py new file mode 100644 index 000000000..12cfd7a47 --- /dev/null +++ b/plinth/tests/test_app.py @@ -0,0 +1,163 @@ +# +# This file is part of FreedomBox. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +""" +Test module for App, base class for all applications. +""" + +import collections + +import pytest + +from plinth.app import App, Component, FollowerComponent, LeaderComponent + + +class LeaderTest(FollowerComponent): + """Test class for using LeaderComponent in tests.""" + is_leader = True + + +@pytest.fixture(name='app_with_components') +def fixture_app_with_components(): + """Setup an app with some components.""" + app = App() + app.add(FollowerComponent('test-follower-1')) + app.add(FollowerComponent('test-follower-2')) + app.add(LeaderTest('test-leader-1')) + app.add(LeaderTest('test-leader-2')) + return app + + +def test_app_instantiation(): + """Test that App is instantiated properly.""" + app = App() + assert isinstance(app.components, collections.OrderedDict) + assert not app.components + + +def test_app_add(): + """Test adding a components to an App.""" + app = App() + component = Component('test-component') + return_value = app.add(component) + assert len(app.components) == 1 + assert app.components['test-component'] == component + assert return_value == app + + +def test_app_enable(app_with_components): + """Test that enabling an app enables components.""" + app_with_components.disable() + app_with_components.enable() + for component in app_with_components.components.values(): + assert component.is_enabled() + + +def test_app_disable(app_with_components): + """Test that disabling an app disables components.""" + app_with_components.enable() + app_with_components.disable() + for component in app_with_components.components.values(): + assert not component.is_enabled() + + +def test_app_is_enabled(app_with_components): + """Test checking for app enabled.""" + app = app_with_components + app.disable() + + # Disabling the components disables that app + assert not app.is_enabled() + + # Enabling followers will not enable the app + app.components['test-follower-1'].enable() + assert not app.is_enabled() + app.components['test-follower-2'].enable() + assert not app.is_enabled() + + # Enabling both leaders will enable the app + app.components['test-leader-1'].enable() + assert not app.is_enabled() + app.components['test-leader-2'].enable() + assert app.is_enabled() + + # Disabling followers has no effect + app.components['test-follower-1'].disable() + assert app.is_enabled() + + +def test_app_set_enabled(app_with_components): + """Test that setting enabled effects only followers.""" + app = app_with_components + + app.disable() + app.set_enabled(True) + assert app.components['test-follower-1'].is_enabled() + assert not app.components['test-leader-1'].is_enabled() + + app.enable() + app.set_enabled(False) + assert not app.components['test-follower-1'].is_enabled() + assert app.components['test-leader-1'].is_enabled() + + +def test_component_initialization(): + """Test that component is initialized properly.""" + with pytest.raises(ValueError): + Component(None) + + component = Component('test-component') + assert component.component_id == 'test-component' + assert not component.is_leader + + +def test_follower_component_initialization(): + """Test that follower component is initialized properly.""" + component = FollowerComponent('test-follower-1') + assert not component.is_enabled() + + component = FollowerComponent('test-follower-2', False) + assert not component.is_enabled() + + component = FollowerComponent('test-follower-3', True) + assert component.is_enabled() + + +def test_follower_component_enable(): + """Test enabling a follower component.""" + component = FollowerComponent('test-follower-1', False) + component.enable() + assert component.is_enabled() + + +def test_follower_component_disable(): + """Test disabling a follower component.""" + component = FollowerComponent('test-follower-1', True) + component.disable() + assert not component.is_enabled() + + +def test_leader_component_initialization(): + """Test that leader component is initialized properly.""" + component = LeaderComponent('test-leader-1') + assert component.is_leader + + +def test_leader_component_is_enabled(): + """Test getting enabled state is not implemented in leader component.""" + component = LeaderComponent('test-leader-1') + with pytest.raises(NotImplementedError): + assert component.is_enabled() diff --git a/plinth/tests/test_context_processors.py b/plinth/tests/test_context_processors.py index c61394989..dfc7bd712 100644 --- a/plinth/tests/test_context_processors.py +++ b/plinth/tests/test_context_processors.py @@ -21,10 +21,17 @@ Test module for custom context processors. from unittest.mock import MagicMock, Mock +import pytest from django.http import HttpRequest from plinth import cfg from plinth import context_processors as cp +from plinth import menu as menu_module + +@pytest.fixture(name='menu', autouse=True) +def fixture_menu(): + """Initialized menu module.""" + menu_module.init() def test_common(): diff --git a/plinth/tests/test_menu.py b/plinth/tests/test_menu.py index 53b1c1a1c..c449094aa 100644 --- a/plinth/tests/test_menu.py +++ b/plinth/tests/test_menu.py @@ -36,17 +36,25 @@ def build_menu(size=5): """Build a menu with the specified number of items.""" random.seed() - menu = Menu() + menu = Menu('menu-index', url_name='index') for index in range(1, size + 1): - args = [ - 'Name' + str(index), 'Icon' + str(index), 'test', - 'ShortDescription' + str(index), - random.randint(0, 1000) - ] - kwargs = {'url_kwargs': {'a': index, 'b': index, 'c': index}} + kwargs = { + 'component_id': 'menu-test-' + str(index), + 'name': 'Name' + str(index), + 'short_description': 'ShortDescription' + str(index), + 'icon': 'Icon' + str(index), + 'url_name': 'test', + 'url_kwargs': { + 'a': index, + 'b': index, + 'c': index + }, + 'parent_url_name': 'index', + 'order': random.randint(0, 1000), + } - menu.add_urlname(*args, **kwargs) + Menu(**kwargs) return menu @@ -74,9 +82,15 @@ def test_init(): def test_menu_creation_without_arguments(): """Verify the Menu state without initialization parameters.""" - menu = Menu() - assert menu.icon == '' - assert menu.url == '#' + with pytest.raises(ValueError): + Menu('menu-test') + + menu = Menu('menu-index', url_name='index') + assert menu.component_id == 'menu-index' + assert menu.name is None + assert menu.short_description is None + assert menu.icon is None + assert menu.url == '/' assert menu.order == 50 assert not menu.items @@ -86,11 +100,17 @@ def test_menu_creation_with_arguments(): expected_name = 'Name' expected_short_description = 'ShortDescription' expected_icon = 'Icon' - expected_url = '/aaa/bbb/ccc/' + url_name = 'test' + url_kwargs = {'a': 1, 'b': 2, 'c': 3} + expected_url = reverse(url_name, kwargs=url_kwargs) expected_order = 42 - menu = Menu(expected_name, expected_short_description, expected_icon, - expected_url, expected_order) + parent_menu = Menu('menu-index', url_name='index') + menu = Menu('menu-test', expected_name, expected_short_description, + expected_icon, url_name, url_kwargs=url_kwargs, + parent_url_name='index', order=expected_order) + assert len(parent_menu.items) == 1 + assert parent_menu.items[0] == menu assert expected_name == menu.name assert expected_short_description == menu.short_description assert expected_icon == menu.icon @@ -105,11 +125,11 @@ def test_get(): expected_short_description = 'ShortDescription2' expected_icon = 'Icon2' expected_url = 'index' - reversed_url = reverse(expected_url) + url_name = 'index' + reversed_url = reverse(url_name) expected_order = 2 - menu = Menu() - menu.add_urlname(expected_name, expected_icon, expected_url, - expected_short_description, expected_order) + menu = Menu('menu-test', expected_name, expected_short_description, + expected_icon, url_name, order=expected_order) actual_item = menu.get(expected_url) assert actual_item is not None @@ -126,11 +146,10 @@ def test_get_with_item_not_found(): expected_name = 'Name3' expected_short_description = 'ShortDescription3' expected_icon = 'Icon3' - expected_url = 'index' + url_name = 'index' expected_order = 3 - menu = Menu() - menu.add_urlname(expected_name, expected_icon, expected_url, - expected_short_description, expected_order) + menu = Menu('menu-test', expected_name, expected_short_description, + expected_icon, url_name, order=expected_order) with pytest.raises(KeyError): menu.get('apps') @@ -146,7 +165,7 @@ def test_sort_items(): # Verify that the order of every item is equal to or greater # than the order of the item preceding it and if the order is - # the same, the labels are considered. + # the same, the names are considered. items = menu.sorted_items() for index in range(1, size): assert items[index].order >= items[index - 1].order @@ -154,28 +173,6 @@ def test_sort_items(): assert items[index].name >= items[index - 1].name -def test_add_urlname(): - """Verify that a named URL can be added to a menu correctly.""" - expected_name = 'Name4' - expected_short_description = 'Description4' - expected_icon = 'Icon4' - expected_url = 'index' - reversed_url = reverse(expected_url) - expected_order = 4 - menu = Menu() - actual_item = menu.add_urlname(expected_name, expected_icon, expected_url, - expected_short_description, expected_order) - - assert len(menu.items) == 1 - assert actual_item is not None - assert actual_item == menu.items[0] - assert expected_name == actual_item.name - assert expected_icon == actual_item.icon - assert reversed_url == actual_item.url - assert expected_order == actual_item.order - assert not actual_item.items - - def test_active_item(): """Verify that an active menu item can be correctly retrieved.""" menu = build_menu() diff --git a/plinth/views.py b/plinth/views.py index 566cbcbb6..3ef58ae40 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -72,7 +72,7 @@ class AppsIndexView(TemplateView): def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) - context['show_secondary'] = True + context['show_disabled'] = True return context diff --git a/static/themes/default/css/plinth.css b/static/themes/default/css/plinth.css index 8b9f12a04..2410bc3d8 100644 --- a/static/themes/default/css/plinth.css +++ b/static/themes/default/css/plinth.css @@ -299,12 +299,12 @@ footer { } /* Disabled app icons */ -.apps-page .card-list-secondary .card-icon img { +.apps-page .card-list-disabled .card-icon img { transition: filter 0.3s; filter: grayscale(1) opacity(0.5); } -.apps-page .card-list-secondary .card:hover .card-icon img { +.apps-page .card-list-disabled .card:hover .card-icon img { filter: grayscale(1) opacity(1); } @@ -385,7 +385,7 @@ a.menu_link_active { } /* Disabled apps - grey glow */ -.card-list-secondary .card-icon { +.card-list-disabled .card-icon { background: radial-gradient(farthest-side at bottom, #f0f0f0, white); background-position: 50% 100%; background-repeat: no-repeat;