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;