diff --git a/actions/config b/actions/config index 7fe23fdb8..ae2fa44a9 100755 --- a/actions/config +++ b/actions/config @@ -53,7 +53,7 @@ def subcommand_set_home_page(arguments): conf_file_path = os.path.join('/etc/apache2/conf-available', APACHE_HOMEPAGE_CONF_FILE_NAME) - redirect_rule = 'RedirectMatch "^/$" "/{}"\n'.format(arguments.homepage) + redirect_rule = 'RedirectMatch "^/$" "{}"\n'.format(arguments.homepage) with open(conf_file_path, 'w') as conf_file: conf_file.write(redirect_rule) diff --git a/plinth/app.py b/plinth/app.py index c288e9846..cb9292d45 100644 --- a/plinth/app.py +++ b/plinth/app.py @@ -40,6 +40,16 @@ class App: self.components[component.component_id] = component return self + def remove(self, component_id): + """Remove a component from the app.""" + component = self.components[component_id] + del self.components[component_id] + return component + + def get(self, component_id): + """Return a component given the component's ID.""" + return self.components[component_id] + def enable(self): """Enable all the components of the app.""" for component in self.components.values(): diff --git a/plinth/frontpage.py b/plinth/frontpage.py index d5397b6aa..72b3ffab7 100644 --- a/plinth/frontpage.py +++ b/plinth/frontpage.py @@ -20,61 +20,149 @@ Manage application shortcuts on front page. import json import os -from plinth import cfg +from plinth import app, cfg from . import actions -shortcuts = {} +class Shortcut(app.FollowerComponent): + """An application component for handling shortcuts.""" -def get_shortcuts(username=None, web_apps_only=False, sort_by='label'): - """Return menu items in sorted order according to current locale.""" - shortcuts_to_return = {} - if username: + _all_shortcuts = {} + + def __init__(self, component_id, name, short_description=None, icon=None, + url=None, description=None, configure_url=None, clients=None, + login_required=False, allowed_groups=None): + """Initialize the frontpage shortcut component for an app. + + When a user visits this web interface, they are first shown the + frontpage. It's primary contents are the list of shortcuts to services + that user may use on the server. If a service requires logging in, it + does not show up to anonymous users. If a service requires a user to be + part of a group, that service is only shown to those users. + + 'component_id' must be a unique string across all apps and components + of a app. Conventionally starts with 'shortcut-'. + + 'name' is the mandatory title for the shortcut. + + 'short_description' is an optional secondary title for the shortcut. + + 'icon' is used to find a suitable image to represent the shortcut. + + 'url' is link to which the user is redirected when the shortcut is + activated. This is typically the web interface for a particular service + provided by the app. For shortcuts that should simply additional + information this value must be None. + + 'details' are additional information that the user is shown when the + shortcut is activated. This must be provided instead of 'url' and must + be 'None' if 'url' is provided. + + 'configure_url' is the page to which the user may be redirected if they + wish to change the settings for the app or one of its services. This is + only used when 'url' is 'None'. It is optionally provided along with + 'details'. + + 'clients' is a list of clients software that can used to access the + service offered by the shortcut. This should be a valid client + information structure as validated by clients.py:validate(). + + If 'login_required' is true, only logged-in users will be shown this + shortcut. Anonymous users visiting the frontpage won't be shown this + shortcut. + + 'allowed_groups' specifies a list of user groups to whom this shortcut + must be shown. All other user groups will not be shown this shortcut on + the frontpage. + + """ + super().__init__(component_id) + + if not url: + url = '?selected={id}'.format(id=component_id) + + self.name = name + self.short_description = short_description + self.url = url + self.icon = icon + self.description = description + self.configure_url = configure_url + self.clients = clients + self.login_required = login_required + self.allowed_groups = set(allowed_groups) if allowed_groups else None + + self._all_shortcuts[self.component_id] = self + + def remove(self): + """Remove this shortcut from global list of shortcuts.""" + del self._all_shortcuts[self.component_id] + + @classmethod + def list(cls, username=None, web_apps_only=False, sort_by='name'): + """Return menu items in sorted order according to current locale.""" + shortcuts_to_return = cls._list_for_user(username) + if web_apps_only: + shortcuts_to_return = { + _id: shortcut + for _id, shortcut in shortcuts_to_return.items() + if not shortcut.url.startswith('?selected=') + } + + return sorted(shortcuts_to_return.values(), + key=lambda item: getattr(item, sort_by).lower()) + + @classmethod + def _list_for_user(cls, username=None): + """Return menu items for a particular user or anonymous user.""" + if not username: + return cls._all_shortcuts + + # XXX: Turn this into an API call in users module and cache output = actions.superuser_run('users', ['get-user-groups', username]) user_groups = set(output.strip().split('\n')) if 'admin' in user_groups: # Admin has access to all services - shortcuts_to_return = shortcuts - else: - for shortcut_id, shortcut in shortcuts.items(): - if shortcut['allowed_groups']: - if not user_groups.isdisjoint(shortcut['allowed_groups']): - shortcuts_to_return[shortcut_id] = shortcut - else: - shortcuts_to_return[shortcut_id] = shortcut - else: - shortcuts_to_return = shortcuts + return cls._all_shortcuts - if web_apps_only: - shortcuts_to_return = { - _id: shortcut - for _id, shortcut in shortcuts_to_return.items() - if not shortcut['url'].startswith('?selected=') - } + shortcuts = {} + for shortcut_id, shortcut in cls._all_shortcuts.items(): + if shortcut.allowed_groups and \ + user_groups.isdisjoint(shortcut.allowed_groups): + continue - return sorted(shortcuts_to_return.values(), key=lambda item: item[sort_by]) + shortcuts[shortcut_id] = shortcut + + return shortcuts def add_custom_shortcuts(): custom_shortcuts = get_custom_shortcuts() + if not custom_shortcuts: + return - if custom_shortcuts: - for shortcut in custom_shortcuts['shortcuts']: - web_app_url = _extract_web_app_url(shortcut) - if web_app_url: - add_shortcut(None, shortcut['name'], - shortcut['short_description'], - icon=shortcut['icon_url'], url=web_app_url) + for shortcut in custom_shortcuts['shortcuts']: + web_app_url = _extract_web_app_url(shortcut) + if not web_app_url: + continue + + Shortcut(None, shortcut['name'], shortcut['short_description'], + icon=shortcut['icon_url'], url=web_app_url) def _extract_web_app_url(custom_shortcut): - if custom_shortcut.get('clients'): - for client in custom_shortcut['clients']: - if client.get('platforms'): - for platform in client['platforms']: - if platform['type'] == 'web': - return platform['url'] + if not custom_shortcut.get('clients'): + return None + + for client in custom_shortcut['clients']: + if not client.get('platforms'): + continue + + for platform in client['platforms']: + if platform['type'] == 'web': + return platform['url'] + + return None def get_custom_shortcuts(): @@ -85,58 +173,3 @@ def get_custom_shortcuts(): custom_shortcuts = json.load(shortcuts) return custom_shortcuts return None - - -def add_shortcut(shortcut_id, name, short_description="", login_required=False, - icon=None, url=None, details=None, configure_url=None, - allowed_groups=None): - """Add shortcut to front page.""" - - if not url: - url = '?selected={id}'.format(id=shortcut_id) - - if not icon: - icon = shortcut_id - - label = '{0}\n({1})'.format(short_description, name) if short_description \ - else name - - shortcuts[shortcut_id] = { - 'id': shortcut_id, - 'name': name, - 'short_description': short_description, - 'label': label, - 'url': url, - 'icon': icon, - 'login_required': login_required, - 'details': details, - 'configure_url': configure_url, - 'hidden': False, - 'allowed_groups': allowed_groups - } - - -def remove_shortcut(shortcut_id): - """ - Remove shortcut from front page. - - If shortcut_id ends with *, remove all shortcuts with that prefix. - """ - - def match(item): - if shortcut_id[-1] == '*': - return item['id'].startswith(shortcut_id[:-1]) - - return item['id'] == shortcut_id - - global shortcuts - shortcuts = { - shortcut_id: shortcut - for shortcut_id, shortcut in shortcuts.items() if not match(shortcut) - } - - -def hide_shortcut(shortcut_id, hide=True): - """Mark a shortcut as hidden or not hidden.""" - global shortcuts - shortcuts[shortcut_id]['hidden'] = hide diff --git a/plinth/modules/api/views.py b/plinth/modules/api/views.py index 2ddbc5032..6402d36d1 100644 --- a/plinth/modules/api/views.py +++ b/plinth/modules/api/views.py @@ -25,7 +25,7 @@ from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse from django.templatetags.static import static -from plinth import frontpage, module_loader +from plinth import frontpage from plinth.modules import names @@ -52,8 +52,9 @@ def shortcuts(request, **kwargs): def get_shortcuts_as_json(username=None): shortcuts = [ - _get_shortcut_data(shortcut['id'].split('_')[0], shortcut) - for shortcut in frontpage.get_shortcuts(username) if shortcut['id'] + _get_shortcut_data(shortcut) + for shortcut in frontpage.Shortcut.list(username) + if shortcut.component_id ] custom_shortcuts = frontpage.get_custom_shortcuts() if custom_shortcuts: @@ -61,17 +62,17 @@ def get_shortcuts_as_json(username=None): return {'shortcuts': shortcuts} -def _get_shortcut_data(module_name, shortcut): +def _get_shortcut_data(shortcut): """Return detailed information about a shortcut.""" - module = module_loader.loaded_modules[module_name] shortcut_data = { - 'name': shortcut['name'], - 'short_description': shortcut['short_description'], - 'description': shortcut['details'], - 'icon_url': _get_icon_url(shortcut['icon']), - 'clients': copy.deepcopy(getattr(module, 'clients', None)) + 'name': shortcut.name, + 'short_description': shortcut.short_description, + 'description': shortcut.description, + 'icon_url': _get_icon_url(shortcut.icon), + 'clients': copy.deepcopy(shortcut.clients), } - if module_name == 'ikiwiki': + # XXX: Fix the hardcoding + if shortcut.name.startswith('shortcut-ikiwiki-'): shortcut_data['clients'][0]['platforms'][0]['url'] += '/{}'.format( shortcut['name']) return shortcut_data diff --git a/plinth/modules/cockpit/__init__.py b/plinth/modules/cockpit/__init__.py index f0ee64a91..3dc50ef80 100644 --- a/plinth/modules/cockpit/__init__.py +++ b/plinth/modules/cockpit/__init__.py @@ -74,6 +74,12 @@ class CockpitApp(app_module.App): parent_url_name='system') self.add(menu_item) + shortcut = frontpage.Shortcut('shortcut-cockpit', name, + short_description=short_description, + icon='cockpit', url='/_cockpit/', + clients=clients, login_required=True) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -89,7 +95,6 @@ def init(): disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) domain_added.connect(on_domain_added) @@ -112,14 +117,7 @@ def setup(helper, old_version=None): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) - - -def add_shortcut(): - """Add a shortcut the frontpage.""" - frontpage.add_shortcut('cockpit', name, - short_description=short_description, - url='/_cockpit/', login_required=True) + helper.call('post', app.enable) def is_enabled(): @@ -131,14 +129,12 @@ def is_enabled(): 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() diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index a5e58b41e..fc0ecc9f9 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -26,7 +26,7 @@ from django.utils.translation import ugettext_lazy from plinth import actions from plinth import app as app_module -from plinth import menu +from plinth import frontpage, menu from plinth.modules import firewall from plinth.modules.names import SERVICES from plinth.signals import domain_added @@ -72,7 +72,7 @@ def get_hostname(): return socket.gethostname() -def get_home_page(): +def _get_home_page_url(): """Get the default application for the domain.""" aug = augeas.Augeas( flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) @@ -84,22 +84,46 @@ def get_home_page(): aug.defvar('conf', '/files' + conf_file) - app_path = 'plinth' - for match in aug.match('/files' + conf_file + '/directive["RedirectMatch"]'): if aug.get(match + "/arg[1]") == '''"^/$"''': - app_path = aug.get(match + "/arg[2]") + return aug.get(match + "/arg[2]").strip('"') - # match this against the app_id in the entries of frontpage.get_shortcuts() - # The underscore is to handle Ikiwiki app_ids - app = app_path.strip('/"').replace('/', '_') - if app == 'index.html': + return None + + +def get_home_page(): + """Return the shortcut ID that is set as current home page.""" + url = _get_home_page_url() + if url in ['/plinth/', '/plinth', 'plinth']: + return 'plinth' + + if url == '/index.html': return 'apache-default' - elif app.endswith('jsxc'): - return 'jsxc' + + shortcuts = frontpage.Shortcut.list() + for shortcut in shortcuts: + if shortcut.url == url: + return shortcut.component_id + + return None + + +def change_home_page(shortcut_id): + """Change the FreedomBox's default redirect to URL of the shortcut specified.""" + if shortcut_id == 'plinth': + url = '/plinth/' + elif shortcut_id == 'apache-default': + url = '/index.html' else: - return app + shortcuts = frontpage.Shortcut.list() + url = [ + shortcut.url for shortcut in shortcuts + if shortcut.component_id == shortcut_id + ][0] + + # URL may be a reverse_lazy() proxy + actions.superuser_run('config', ['set-home-page', str(url)]) def init(): diff --git a/plinth/modules/config/forms.py b/plinth/modules/config/forms.py index f35cd2d12..eda39a29a 100644 --- a/plinth/modules/config/forms.py +++ b/plinth/modules/config/forms.py @@ -43,12 +43,13 @@ def domain_label_validator(domainname): def get_homepage_choices(): - shortcuts = frontpage.get_shortcuts(web_apps_only=True, sort_by='name') - apps = [(shortcut['id'], shortcut['name']) for shortcut in shortcuts - if shortcut['id']] + """Return list of drop down choices for home page.""" + shortcuts = frontpage.Shortcut.list(web_apps_only=True) + shortcut_choices = [(shortcut.component_id, shortcut.name) + for shortcut in shortcuts if shortcut.is_enabled()] apache_default = ('apache-default', _('Apache Default')) plinth = ('plinth', _('FreedomBox Service (Plinth)')) - return [apache_default, plinth] + apps + return [apache_default, plinth] + shortcut_choices class ConfigurationForm(forms.Form): diff --git a/plinth/modules/config/views.py b/plinth/modules/config/views.py index d15c6e8f1..6bb9efd61 100644 --- a/plinth/modules/config/views.py +++ b/plinth/modules/config/views.py @@ -24,7 +24,7 @@ from django.contrib import messages from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ -from plinth import actions, frontpage +from plinth import actions from plinth.modules import config, firewall from plinth.modules.names import SERVICES from plinth.signals import (domain_added, domain_removed, domainname_change, @@ -75,8 +75,8 @@ def _apply_changes(request, old_status, new_status): except Exception as exception: messages.error( request, - _('Error setting hostname: {exception}') - .format(exception=exception)) + _('Error setting hostname: {exception}').format( + exception=exception)) else: messages.success(request, _('Hostname set')) @@ -86,39 +86,23 @@ def _apply_changes(request, old_status, new_status): except Exception as exception: messages.error( request, - _('Error setting domain name: {exception}') - .format(exception=exception)) + _('Error setting domain name: {exception}').format( + exception=exception)) else: messages.success(request, _('Domain name set')) if old_status['homepage'] != new_status['homepage']: try: - change_home_page(new_status['homepage']) + config.change_home_page(new_status['homepage']) except Exception as exception: messages.error( request, - _('Error setting webserver home page: {exception}') - .format(exception=exception)) + _('Error setting webserver home page: {exception}').format( + exception=exception)) else: messages.success(request, _('Webserver home page set')) -def change_home_page(app_id): - """Changes the FreedomBox's default app to the app specified by app_id.""" - if app_id == 'plinth': - url = '/plinth' - elif app_id == 'apache-default': - url = '/index.html' - else: - shortcuts = frontpage.get_shortcuts() - url = [ - shortcut['url'] for shortcut in shortcuts - if shortcut['id'] == app_id - ][0] - - actions.superuser_run('config', ['set-home-page', url.strip('/')]) - - def set_hostname(hostname): """Sets machine hostname to hostname""" old_hostname = config.get_hostname() diff --git a/plinth/modules/coquelicot/__init__.py b/plinth/modules/coquelicot/__init__.py index 6ec0c6db3..d8e13ba6e 100644 --- a/plinth/modules/coquelicot/__init__.py +++ b/plinth/modules/coquelicot/__init__.py @@ -67,6 +67,12 @@ class CoquelicotApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut('shortcut-coquelicot', name, + short_description=short_description, + icon='coquelicot', url='/coquelicot', + clients=clients, login_required=True) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -83,7 +89,6 @@ def init(): is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -100,17 +105,9 @@ def setup(helper, old_version=None): disable=disable, is_running=is_running) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut('coquelicot', name, - short_description=short_description, - url='/coquelicot', login_required=True) - - def is_running(): """Return whether the service is running.""" return action_utils.service_is_running('coquelicot') @@ -125,14 +122,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('coquelicot', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('coquelicot', ['disable']) - frontpage.remove_shortcut('coquelicot') app.disable() diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index f203be251..7df379187 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -69,6 +69,12 @@ class DelugeApp(app_module.App): 'deluge:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-deluge', name, short_description=short_description, + url='/deluge', icon='deluge', clients=clients, login_required=True, + allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Initialize the Deluge module.""" @@ -84,7 +90,6 @@ def init(): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -99,15 +104,9 @@ def setup(helper, old_version=None): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('deluge', name, short_description, url='/deluge', - login_required=True, allowed_groups=[group[0]]) - - def is_enabled(): """Return whether the module is enabled.""" return (action_utils.webserver_is_enabled('deluge-plinth') @@ -117,14 +116,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('deluge', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('deluge', ['disable']) - frontpage.remove_shortcut('deluge') app.disable() diff --git a/plinth/modules/diaspora/__init__.py b/plinth/modules/diaspora/__init__.py index edddadf07..ab32ac417 100644 --- a/plinth/modules/diaspora/__init__.py +++ b/plinth/modules/diaspora/__init__.py @@ -87,6 +87,20 @@ class DiasporaApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = Shortcut( + 'shortcut-diaspora', name, short_description=short_description, + icon='diaspora', url=None, clients=clients, login_required=True) + self.add(shortcut) + + +class Shortcut(frontpage.Shortcut): + """Frontpage shortcut to use configured domain name for URL.""" + + def enable(self): + """Set the proper shortcut URL when enabled.""" + super().enable() + self.url = 'https://diaspora.{}'.format(get_configured_domain_name()) + def init(): """Initialize the Diaspora module.""" @@ -102,7 +116,6 @@ def init(): disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -112,7 +125,6 @@ def setup(helper, old_version=None): helper.install(managed_packages) helper.call('custom_config', actions.superuser_run, 'diaspora', ['disable-ssl']) - helper.call('post', app.enable) def setup_domain_name(domain_name): @@ -124,16 +136,7 @@ def setup_domain_name(domain_name): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) service.notify_enabled(None, True) - add_shortcut() - - -def add_shortcut(): - """Add shortcut to diaspora on the homepage.""" - if is_setup(): - frontpage.add_shortcut( - 'diaspora', name, short_description, - url='https://diaspora.{}'.format(get_configured_domain_name()), - login_required=True) + app.enable() def is_enabled(): @@ -144,14 +147,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('diaspora', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('diaspora', ['disable']) - frontpage.remove_shortcut('diaspora') app.disable() diff --git a/plinth/modules/ejabberd/__init__.py b/plinth/modules/ejabberd/__init__.py index 020b10957..7d73857a6 100644 --- a/plinth/modules/ejabberd/__init__.py +++ b/plinth/modules/ejabberd/__init__.py @@ -87,6 +87,13 @@ class EjabberdApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-ejabberd', name, short_description=short_description, + icon='ejabberd', description=description, + configure_url=reverse_lazy('ejabberd:index'), clients=clients, + login_required=True) + self.add(shortcut) + def init(): """Initialize the ejabberd module""" @@ -102,7 +109,6 @@ def init(): 'xmpp-bosh'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) pre_hostname_change.connect(on_pre_hostname_change) @@ -127,17 +133,9 @@ def setup(helper, old_version=None): 'xmpp-bosh'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'ejabberd', name=name, short_description=short_description, - details=description, configure_url=reverse_lazy('ejabberd:index'), - login_required=True) - - def is_enabled(): """Return whether the module is enabled.""" return action_utils.service_is_enabled('ejabberd') @@ -146,14 +144,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('ejabberd', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('ejabberd', ['disable']) - frontpage.remove_shortcut('ejabberd') app.disable() diff --git a/plinth/modules/i2p/__init__.py b/plinth/modules/i2p/__init__.py index a7d551624..9dca9a227 100644 --- a/plinth/modules/i2p/__init__.py +++ b/plinth/modules/i2p/__init__.py @@ -86,6 +86,12 @@ class I2PApp(app_module.App): 'i2p:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-i2p', name, short_description=short_description, + icon='i2p', url='/i2p/', clients=clients, login_required=True, + allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -106,7 +112,6 @@ def init(): is_external=False, is_enabled=is_enabled, is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -152,17 +157,9 @@ 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) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut('i2p', name, short_description=short_description, - url='/i2p/', login_required=True, - allowed_groups=[group[0]]) - - def is_running(): """Return whether the service is running.""" return action_utils.service_is_running('i2p') @@ -177,14 +174,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('i2p', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('i2p', ['disable']) - frontpage.remove_shortcut('i2p') app.disable() diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index 3fc827a8f..bbccae098 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -78,6 +78,24 @@ class IkiwikiApp(app_module.App): parent_url_name='apps') self.add(menu_item) + sites = actions.run('ikiwiki', ['get-sites']).split('\n') + sites = [name for name in sites if name != ''] + for site in sites: + self.add_shortcut(site) + + def add_shortcut(self, site): + """Add an ikiwiki shortcut to frontpage.""" + shortcut = frontpage.Shortcut('shortcut-ikiwiki-' + site, site, + icon='ikiwiki', url='/ikiwiki/' + site, + clients=clients) + self.add(shortcut) + return shortcut + + def remove_shortcut(self, site): + """Remove an ikiwiki shortcut from frontpage.""" + component = self.remove('shortcut-ikiwiki-' + site) + component.remove() # Remove from global list. + def init(): """Initialize the ikiwiki module.""" @@ -92,7 +110,6 @@ def init(): 'ikiwiki', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcuts() app.set_enabled(True) @@ -106,19 +123,9 @@ def setup(helper, old_version=None): 'ikiwiki', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcuts) helper.call('post', app.enable) -def add_shortcuts(): - sites = actions.run('ikiwiki', ['get-sites']).split('\n') - sites = [name for name in sites if name != ''] - for site in sites: - frontpage.add_shortcut('ikiwiki_' + site, site, url='/ikiwiki/' + site, - login_required=False, icon='ikiwiki', - allowed_groups=[group[0]]) - - def is_enabled(): """Return whether the module is enabled.""" return action_utils.webserver_is_enabled('ikiwiki-plinth') @@ -127,14 +134,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('ikiwiki', ['enable']) - add_shortcuts() app.enable() def disable(): """Enable the module.""" actions.superuser_run('ikiwiki', ['disable']) - frontpage.remove_shortcut('ikiwiki*') app.disable() diff --git a/plinth/modules/ikiwiki/views.py b/plinth/modules/ikiwiki/views.py index 95614d3b8..2a4ee0399 100644 --- a/plinth/modules/ikiwiki/views.py +++ b/plinth/modules/ikiwiki/views.py @@ -25,7 +25,7 @@ from django.urls import reverse_lazy from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy -from plinth import actions, frontpage, views +from plinth import actions, views from plinth.modules import ikiwiki from .forms import IkiwikiCreateForm @@ -93,9 +93,8 @@ def create(request): form.cleaned_data['admin_password']) site = form.cleaned_data['name'].replace(' ', '') - frontpage.add_shortcut('ikiwiki_' + site, site, - url='/ikiwiki/' + site, - login_required=False, icon='ikiwiki') + shortcut = ikiwiki.app.add_shortcut(site) + shortcut.enable() return redirect(reverse_lazy('ikiwiki:manage')) else: @@ -147,8 +146,8 @@ def delete(request, name): if request.method == 'POST': try: actions.superuser_run('ikiwiki', ['delete', '--name', name]) + ikiwiki.app.remove_shortcut(name) messages.success(request, _('{name} deleted.').format(name=name)) - frontpage.remove_shortcut('ikiwiki_' + name) except actions.ActionError as error: messages.error( request, @@ -157,8 +156,7 @@ def delete(request, name): return redirect(reverse_lazy('ikiwiki:manage')) - return TemplateResponse( - request, 'ikiwiki_delete.html', { - 'title': ikiwiki.name, - 'name': name - }) + return TemplateResponse(request, 'ikiwiki_delete.html', { + 'title': ikiwiki.name, + 'name': name + }) diff --git a/plinth/modules/infinoted/__init__.py b/plinth/modules/infinoted/__init__.py index f2caae1a7..596594fd8 100644 --- a/plinth/modules/infinoted/__init__.py +++ b/plinth/modules/infinoted/__init__.py @@ -69,6 +69,13 @@ class InfinotedApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-infinoted', name, short_description=short_description, + icon='infinoted', description=description, + configure_url=reverse_lazy('infinoted:index'), clients=clients, + login_required=False) + self.add(shortcut) + def init(): """Initialize the infinoted module.""" @@ -82,7 +89,6 @@ def init(): 'infinoted-plinth' ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -105,28 +111,18 @@ 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) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'infinoted', name, short_description=short_description, url=None, - details=description, configure_url=reverse_lazy('infinoted:index'), - login_required=False) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('infinoted') app.disable() diff --git a/plinth/modules/jsxc/__init__.py b/plinth/modules/jsxc/__init__.py index e633bff58..3532b128e 100644 --- a/plinth/modules/jsxc/__init__.py +++ b/plinth/modules/jsxc/__init__.py @@ -61,6 +61,11 @@ class JSXCApp(app_module.App): 'jsxc:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-jsxc', name=name, short_description=short_description, + icon='jsxc', url=reverse_lazy('jsxc:jsxc'), clients=clients) + self.add(shortcut) + def init(): """Initialize the JSXC module""" @@ -74,7 +79,6 @@ def init(): 'jsxc', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -88,16 +92,9 @@ def setup(helper, old_version=None): 'jsxc', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('jsxc', name=name, - short_description=short_description, - url=reverse_lazy('jsxc:jsxc'), login_required=True) - - def is_enabled(): """Return whether the module is enabled.""" setup_helper = globals()['setup_helper'] @@ -105,10 +102,8 @@ def is_enabled(): def enable(): - add_shortcut() app.enable() def disable(): - frontpage.remove_shortcut('jsxc') app.disable() diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index 3e477aef6..7ced635d3 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -83,6 +83,14 @@ class MatrixSynapseApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-matrixsynapse', name, + short_description=short_description, icon='matrixsynapse', + description=description, + configure_url=reverse_lazy('matrixsynapse:index'), clients=clients, + login_required=True) + self.add(shortcut) + def init(): """Initialize the matrix-synapse module.""" @@ -97,7 +105,6 @@ def init(): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -114,18 +121,9 @@ def setup(helper, old_version=None): helper.call('post', actions.superuser_run, 'matrixsynapse', ['post-install']) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Add a shortcut to the frontpage.""" - frontpage.add_shortcut('matrixsynapse', name, details=description, - short_description=short_description, - configure_url=reverse_lazy('matrixsynapse:index'), - login_required=True) - - def is_setup(): """Return whether the Matrix Synapse server is setup.""" return os.path.exists(SERVER_NAME_PATH) @@ -139,14 +137,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('matrixsynapse', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('matrixsynapse', ['disable']) - frontpage.remove_shortcut('matrixsynapse') app.disable() diff --git a/plinth/modules/mediawiki/__init__.py b/plinth/modules/mediawiki/__init__.py index 7d8ab3b93..24636c9c5 100644 --- a/plinth/modules/mediawiki/__init__.py +++ b/plinth/modules/mediawiki/__init__.py @@ -65,11 +65,28 @@ class MediaWikiApp(app_module.App): def __init__(self): """Create components for the app.""" super().__init__() + self._private_mode = True + menu_item = menu.Menu('menu-mediawiki', name, short_description, 'mediawiki', 'mediawiki:index', parent_url_name='apps') self.add(menu_item) + shortcut = Shortcut('shortcut-mediawiki', name, + short_description=short_description, + icon='mediawiki', url='/mediawiki', + clients=clients, login_required=True) + self.add(shortcut) + + +class Shortcut(frontpage.Shortcut): + """Frontpage shortcut for only logged users when in private mode.""" + + def enable(self): + """When enabled, check if MediaWiki is in private mode.""" + super().enable() + self.login_required = is_private_mode_enabled() + def init(): """Intialize the module.""" @@ -83,7 +100,6 @@ def init(): 'mediawiki', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -105,17 +121,9 @@ def setup(helper, old_version=None): ports=['http', 'https'], ) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut( - 'mediawiki', name, short_description=short_description, - url='/mediawiki', login_required=is_private_mode_enabled()) - - def is_enabled(): """Return whether the module is enabled.""" return action_utils.webserver_is_enabled('mediawiki') @@ -124,14 +132,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('mediawiki', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('mediawiki', ['disable']) - frontpage.remove_shortcut('mediawiki') app.disable() diff --git a/plinth/modules/mediawiki/views.py b/plinth/modules/mediawiki/views.py index bb431e9ec..491e6143b 100644 --- a/plinth/modules/mediawiki/views.py +++ b/plinth/modules/mediawiki/views.py @@ -26,7 +26,7 @@ from django.utils.translation import ugettext as _ from plinth import actions, views from plinth.modules import mediawiki -from . import (add_shortcut, is_enabled, is_private_mode_enabled, +from . import (is_enabled, is_private_mode_enabled, is_public_registration_enabled) from .forms import MediaWikiForm @@ -110,6 +110,8 @@ class MediaWikiServiceView(views.ServiceView): else: actions.superuser_run('mediawiki', ['private-mode', 'disable']) messages.success(self.request, _('Private mode disabled')) - if is_enabled(): - add_shortcut() + + mediawiki.app.get('shortcut-mediawiki').login_required = \ + new_config['enable_private_mode'] + return super().form_valid(form) diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py index 3f75e5daa..9f584332d 100644 --- a/plinth/modules/minetest/__init__.py +++ b/plinth/modules/minetest/__init__.py @@ -88,6 +88,13 @@ class MinetestApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-minetest', name, short_description=short_description, + icon='minetest', description=description, + configure_url=reverse_lazy('minetest:index'), clients=clients, + login_required=False) + self.add(shortcut) + def init(): """Initialize the module.""" @@ -101,7 +108,6 @@ def init(): 'minetest-plinth' ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -114,28 +120,18 @@ def setup(helper, old_version=None): 'minetest-plinth' ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'minetest', name, url=None, short_description=short_description, - details=description, configure_url=reverse_lazy('minetest:index'), - login_required=False) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('minetest') app.disable() diff --git a/plinth/modules/mldonkey/__init__.py b/plinth/modules/mldonkey/__init__.py index 9ba19e0a6..62f175c94 100644 --- a/plinth/modules/mldonkey/__init__.py +++ b/plinth/modules/mldonkey/__init__.py @@ -76,6 +76,12 @@ class MLDonkeyApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcuts = frontpage.Shortcut( + 'shortcut-mldonkey', name, short_description=short_description, + icon='mldonkey', url='/mldonkey/', login_required=True, + clients=clients, allowed_groups=[group[0]]) + self.add(shortcuts) + def init(): """Initialize the MLDonkey module.""" @@ -93,7 +99,6 @@ def init(): is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -110,17 +115,9 @@ def setup(helper, old_version=None): disable=disable, is_running=is_running) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut( - 'mldonkey', name, short_description=short_description, - url='/mldonkey/', login_required=True, allowed_groups=[group[0]]) - - def is_running(): """Return whether the service is running.""" return action_utils.service_is_running('mldonkey-server') @@ -135,14 +132,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('mldonkey', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('mldonkey', ['disable']) - frontpage.remove_shortcut('mldonkey') app.disable() diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index 5612a2ef7..8897b659c 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -73,6 +73,12 @@ class MumbleApp(app_module.App): 'mumble:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-mumble', name, short_description=short_description, + icon='mumble', description=description, + configure_url=reverse_lazy('mumble:index'), clients=clients) + self.add(shortcut) + def init(): """Intialize the Mumble module.""" @@ -87,7 +93,6 @@ def init(): ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -109,28 +114,18 @@ def setup(helper, old_version=None): 'mumble-plinth' ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('mumble', name, short_description=short_description, - details=description, - configure_url=reverse_lazy('mumble:index'), - login_required=False) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('mumble') app.disable() diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index 1aade3a24..7a035511c 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -70,6 +70,16 @@ class OpenVPNApp(app_module.App): parent_url_name='apps') self.add(menu_item) + download_profile = \ + format_lazy(_('' + 'Download Profile'), + link=reverse_lazy('openvpn:profile')) + shortcut = frontpage.Shortcut( + 'shortcut-openvpn', name, short_description=short_description, + icon='openvpn', description=description + [download_profile], + configure_url=reverse_lazy('openvpn:index'), login_required=True) + self.add(shortcut) + def init(): """Initialize the OpenVPN module.""" @@ -83,7 +93,6 @@ def init(): ports=['openvpn'], is_external=True) if service.is_enabled() and is_setup(): - add_shortcut() app.set_enabled(True) @@ -98,22 +107,9 @@ def setup(helper, old_version=None): enable=enable, disable=disable) if service.is_enabled() and is_setup(): - add_shortcut() helper.call('post', app.enable) -def add_shortcut(): - """Add shortcut in frontpage.""" - download_profile = \ - format_lazy(_('' - 'Download Profile'), - link=reverse_lazy('openvpn:profile')) - frontpage.add_shortcut( - 'openvpn', name, short_description=short_description, - details=description + [download_profile], - configure_url=reverse_lazy('openvpn:index'), login_required=True) - - def is_setup(): """Return whether the service is running.""" return actions.superuser_run('openvpn', ['is-setup']).strip() == 'true' @@ -122,14 +118,12 @@ def is_setup(): def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('openvpn') app.disable() diff --git a/plinth/modules/openvpn/views.py b/plinth/modules/openvpn/views.py index ecb260030..49b01ae85 100644 --- a/plinth/modules/openvpn/views.py +++ b/plinth/modules/openvpn/views.py @@ -77,7 +77,7 @@ def setup(request): setup_process = actions.superuser_run('openvpn', ['setup'], run_in_background=True) - openvpn.add_shortcut() + openvpn.enable() return redirect('openvpn:index') diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index c82d06182..d920fa3e9 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -77,6 +77,12 @@ class PrivoxyApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-privoxy', name, short_description=short_description, + icon='privoxy', description=description, + configure_url=reverse_lazy('privoxy:index'), login_required=True) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -91,7 +97,6 @@ def init(): enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -105,28 +110,18 @@ def setup(helper, old_version=None): ports=['privoxy'], is_external=False, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'privoxy', name, short_description=short_description, - details=description, configure_url=reverse_lazy('privoxy:index'), - login_required=True) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('privoxy') app.disable() diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 63494a018..d289d81b5 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -80,6 +80,13 @@ class QuasselApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-quassel', name, short_description=short_description, + icon='quassel', description=description, + configure_url=reverse_lazy('quassel:index'), clients=clients, + login_required=True) + self.add(shortcut) + def init(): """Initialize the quassel module.""" @@ -94,7 +101,6 @@ def init(): ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -116,28 +122,18 @@ def setup(helper, old_version=None): 'quassel-plinth' ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'quassel', name, short_description=short_description, - details=description, configure_url=reverse_lazy('quassel:index'), - login_required=True) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('quassel') app.disable() diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py index 5d1e94b27..181f475df 100644 --- a/plinth/modules/radicale/__init__.py +++ b/plinth/modules/radicale/__init__.py @@ -84,6 +84,12 @@ class RadicaleApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut('shortcut-radicale', name, + short_description=short_description, + icon='radicale', url='/radicale/', + clients=clients, login_required=True) + self.add(shortcut) + def init(): """Initialize the radicale module.""" @@ -100,7 +106,6 @@ def init(): is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -138,16 +143,9 @@ def setup(helper, old_version=None): disable=disable, is_running=is_running) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('radicale', name, - short_description=short_description, - url='/radicale/', login_required=True) - - def get_package_version(): try: proc = subprocess.run(['radicale', '--version'], @@ -195,14 +193,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('radicale', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('radicale', ['disable']) - frontpage.remove_shortcut('radicale') app.disable() diff --git a/plinth/modules/repro/__init__.py b/plinth/modules/repro/__init__.py index aebbfc434..8a7d17742 100644 --- a/plinth/modules/repro/__init__.py +++ b/plinth/modules/repro/__init__.py @@ -81,6 +81,13 @@ class ReproApp(app_module.App): 'repro:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-repro', name, short_description=short_description, + icon='repro', description=description, + configure_url=reverse_lazy('repro:index'), clients=clients, + login_required=True) + self.add(shortcut) + def init(): """Initialize the repro module.""" @@ -95,7 +102,6 @@ def init(): ], is_external=True, enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -118,28 +124,18 @@ def setup(helper, old_version=None): 'sip', 'sips', 'rtp-plinth' ], is_external=True, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('repro', name, short_description=short_description, - details=description, - configure_url=reverse_lazy('repro:index'), - login_required=True) - - def enable(): """Enable the module.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('repro') app.disable() diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 0011278b7..60785b7e3 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -75,6 +75,12 @@ class RoundcubeApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut('shortcut-roundcube', name, + short_description=short_description, + icon='roundcube', url='/roundcube/', + clients=clients, login_required=True) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -89,7 +95,6 @@ def init(): is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -98,7 +103,6 @@ def setup(helper, old_version=None): helper.call('pre', actions.superuser_run, 'roundcube', ['pre-install']) helper.install(managed_packages) helper.call('post', actions.superuser_run, 'roundcube', ['setup']) - helper.call('post', add_shortcut) global service if service is None: service = service_module.Service( @@ -107,12 +111,6 @@ def setup(helper, old_version=None): helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('roundcube', name, - short_description=short_description, - url='/roundcube', login_required=True) - - def is_enabled(): """Return whether the module is enabled.""" return action_utils.webserver_is_enabled('roundcube') @@ -121,14 +119,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('roundcube', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('roundcube', ['disable']) - frontpage.remove_shortcut('roundcube') app.disable() diff --git a/plinth/modules/searx/__init__.py b/plinth/modules/searx/__init__.py index 342c4552c..d997b2c94 100644 --- a/plinth/modules/searx/__init__.py +++ b/plinth/modules/searx/__init__.py @@ -66,6 +66,12 @@ class SearxApp(app_module.App): 'searx:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-searx', name, short_description=short_description, + icon='searx', url='/searx/', clients=clients, login_required=True, + allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -82,7 +88,6 @@ def init(): disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -99,17 +104,9 @@ def setup(helper, old_version=None): 'http', 'https' ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut('searx', name, short_description=short_description, - url='/searx/', login_required=True, - allowed_groups=[group[0]]) - - def get_safe_search_setting(): """Get the current value of the safe search setting for Seax.""" value = actions.superuser_run('searx', ['get-safe-search']) @@ -125,14 +122,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('searx', ['enable']) - add_shortcut() app.enable() def disable(): """Disable the module.""" actions.superuser_run('searx', ['disable']) - frontpage.remove_shortcut('searx') app.disable() diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index 0016c34dc..d2e30c59b 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -63,6 +63,12 @@ class ShaarliApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut('shortcut-shaarli', name, + short_description=short_description, + icon='shaarli', url='/shaarli', + clients=clients, login_required=True) + self.add(shortcut) + def init(): """Initialize the module.""" @@ -77,7 +83,6 @@ def init(): is_enabled=is_enabled, enable=enable, disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -90,16 +95,9 @@ def setup(helper, old_version=None): 'shaarli', name, ports=['http', 'https'], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut('shaarli', name, - short_description=short_description, url='/shaarli', - login_required=True) - - def is_enabled(): """Return whether the module is enabled.""" return action_utils.webserver_is_enabled('shaarli') @@ -108,12 +106,10 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('shaarli', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('shaarli', ['disable']) - frontpage.remove_shortcut('shaarli') app.disable() diff --git a/plinth/modules/shadowsocks/__init__.py b/plinth/modules/shadowsocks/__init__.py index 7ef04a8e7..6ec65a5ce 100644 --- a/plinth/modules/shadowsocks/__init__.py +++ b/plinth/modules/shadowsocks/__init__.py @@ -71,6 +71,13 @@ class ShadowsocksApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-shadowsocks', name, short_description=short_description, + icon='shadowsocks', description=description, + configure_url=reverse_lazy('shadowsocks:index'), + login_required=True) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -86,7 +93,6 @@ def init(): enable=enable, disable=disable) if service.is_enabled(): - add_shortcut() app.set_enabled(True) @@ -104,14 +110,6 @@ def setup(helper, old_version=None): helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut( - 'shadowsocks', name, short_description=short_description, - details=description, configure_url=reverse_lazy('shadowsocks:index'), - login_required=False) - - def is_enabled(): """Return whether service is enabled.""" return action_utils.service_is_enabled(managed_services[0]) @@ -125,14 +123,12 @@ def is_running(): def enable(): """Enable service.""" actions.superuser_run('service', ['enable', managed_services[0]]) - add_shortcut() app.enable() def disable(): """Disable service.""" actions.superuser_run('service', ['disable', managed_services[0]]) - frontpage.remove_shortcut('shadowsocks') app.disable() diff --git a/plinth/modules/syncthing/__init__.py b/plinth/modules/syncthing/__init__.py index 97443bd8f..f5a1b61ab 100644 --- a/plinth/modules/syncthing/__init__.py +++ b/plinth/modules/syncthing/__init__.py @@ -79,6 +79,12 @@ class SyncthingApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-syncthing', name, short_description=short_description, + icon='syncthing', url='/syncthing/', clients=clients, + login_required=True, allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -96,7 +102,6 @@ def init(): is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -112,17 +117,9 @@ def setup(helper, old_version=None): disable=disable, is_running=is_running) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - """Helper method to add a shortcut to the frontpage.""" - frontpage.add_shortcut( - 'syncthing', name, short_description=short_description, - url='/syncthing/', login_required=True, allowed_groups=[group[0]]) - - def is_running(): """Return whether the service is running.""" return action_utils.service_is_running('syncthing@syncthing') @@ -137,14 +134,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('syncthing', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('syncthing', ['disable']) - frontpage.remove_shortcut('syncthing') app.disable() diff --git a/plinth/modules/tahoe/__init__.py b/plinth/modules/tahoe/__init__.py index 514ba68b6..4f1acdbe7 100644 --- a/plinth/modules/tahoe/__init__.py +++ b/plinth/modules/tahoe/__init__.py @@ -72,6 +72,20 @@ class TahoeApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-tahoe', name, short_description=short_description, + icon='tahoe-lafs', url=None, login_required=True) + self.add(shortcut) + + +class Shortcut(frontpage.Shortcut): + """Frontpage shortcut to use configured domain name for URL.""" + + def enable(self): + """Set the proper shortcut URL when enabled.""" + super().enable() + self.url = 'https://{}:5678'.format(get_configured_domain_name()) + def is_setup(): """Check whether Tahoe-LAFS is setup""" @@ -116,7 +130,6 @@ def init(): is_running=is_running) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -144,19 +157,9 @@ def post_setup(configured_domain_name): disable=disable, is_running=is_running) service.notify_enabled(None, True) - add_shortcut() app.enable() -def add_shortcut(): - """Helper method to add a shortcut to the front page.""" - # BUG: Current logo appears squashed on front page. - frontpage.add_shortcut( - 'tahoe-lafs', name, short_description=short_description, - url='https://{}:5678'.format(get_configured_domain_name()), - login_required=True) - - def is_running(): """Return whether the service is running.""" return action_utils.service_is_running(managed_services[0]) @@ -171,14 +174,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('tahoe-lafs', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('tahoe-lafs', ['disable']) - frontpage.remove_shortcut('tahoe-lafs') app.disable() diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index 7db829e7e..1778d546a 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -71,6 +71,12 @@ class TransmissionApp(app_module.App): parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-transmission', name, short_description=short_description, + icon='transmission', url='/transmission', clients=clients, + login_required=True, allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Initialize the Transmission module.""" @@ -87,7 +93,6 @@ def init(): disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -111,16 +116,9 @@ def setup(helper, old_version=None): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) -def add_shortcut(): - frontpage.add_shortcut( - 'transmission', name, short_description=short_description, - url='/transmission', login_required=True, allowed_groups=[group[0]]) - - def is_enabled(): """Return whether the module is enabled.""" return (action_utils.service_is_enabled('transmission-daemon') @@ -130,14 +128,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('transmission', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('transmission', ['disable']) - frontpage.remove_shortcut('transmission') app.disable() diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py index 3dd64f80e..0b6245b8b 100644 --- a/plinth/modules/ttrss/__init__.py +++ b/plinth/modules/ttrss/__init__.py @@ -77,6 +77,12 @@ class TTRSSApp(app_module.App): 'ttrss:index', parent_url_name='apps') self.add(menu_item) + shortcut = frontpage.Shortcut( + 'shortcut-ttrss', name, short_description=short_description, + icon='ttrss', url='/tt-rss', clients=clients, login_required=True, + allowed_groups=[group[0]]) + self.add(shortcut) + def init(): """Intialize the module.""" @@ -93,7 +99,6 @@ def init(): disable=disable) if is_enabled(): - add_shortcut() app.set_enabled(True) @@ -110,7 +115,6 @@ def setup(helper, old_version=None): ], is_external=True, is_enabled=is_enabled, enable=enable, disable=disable) helper.call('post', service.notify_enabled, None, True) - helper.call('post', add_shortcut) helper.call('post', app.enable) @@ -129,13 +133,6 @@ def force_upgrade(helper, packages): actions.superuser_run('ttrss', ['setup']) -def add_shortcut(): - """Add a shortcut to the front page.""" - frontpage.add_shortcut('ttrss', name, short_description=short_description, - url='/tt-rss', login_required=True, - allowed_groups=[group[0]]) - - def is_enabled(): """Return whether the module is enabled.""" return (action_utils.service_is_enabled('tt-rss') @@ -145,14 +142,12 @@ def is_enabled(): def enable(): """Enable the module.""" actions.superuser_run('ttrss', ['enable']) - add_shortcut() app.enable() def disable(): """Enable the module.""" actions.superuser_run('ttrss', ['disable']) - frontpage.remove_shortcut('ttrss') app.disable() diff --git a/plinth/templates/index.html b/plinth/templates/index.html index a95144f04..dcad14631 100644 --- a/plinth/templates/index.html +++ b/plinth/templates/index.html @@ -26,7 +26,7 @@ {% block container %} - {% if messages or details or not shortcuts %} + {% if messages or selected_shortcut or not shortcuts %}
{% include 'messages.html' %} @@ -41,21 +41,21 @@ {% endif %} - {% if details %} + {% if selected_shortcut.description %} {% block pagetitle %} -

{{ details_label }}

+

{{ selected_shortcut.name }}

{% endblock %} {% block description %} - {% for paragraph in details %} + {% for paragraph in selected_shortcut.description %}

{{ paragraph|safe }}

{% endfor %} {% endblock %} {% include "clients.html" with clients=clients enabled=service.is_enabled %} - {% if user.is_authenticated and user_is_admin and configure_url %} - + {% if user.is_authenticated and user_is_admin and selected_shortcut.configure_url %} + {% trans "Configure »" %} {% endif %} {% endif %} @@ -68,10 +68,10 @@
{% for shortcut in shortcuts %} - {% if not shortcut.hidden %} + {% if shortcut.is_enabled %} {% if user.is_authenticated or not shortcut.login_required %}
- {% if selected_id == shortcut.id %} + {% if selected_shortcut.component_id == shortcut.component_id %} {% else %} diff --git a/plinth/tests/test_app.py b/plinth/tests/test_app.py index 12cfd7a47..f714138cf 100644 --- a/plinth/tests/test_app.py +++ b/plinth/tests/test_app.py @@ -58,6 +58,23 @@ def test_app_add(): assert return_value == app +def test_app_remove(app_with_components): + """Test removing a component from an App.""" + app = app_with_components + component = app.components['test-leader-1'] + assert app.remove('test-leader-1') == component + assert 'test-leader-1' not in app.components + + +def test_get(app_with_components): + """Test retrieving a component from an App.""" + app = app_with_components + component = app.components['test-leader-1'] + assert app.get('test-leader-1') == component + with pytest.raises(KeyError): + app.get('x-invalid-component') + + def test_app_enable(app_with_components): """Test that enabling an app enables components.""" app_with_components.disable() diff --git a/plinth/tests/test_frontpage.py b/plinth/tests/test_frontpage.py new file mode 100644 index 000000000..68b97e706 --- /dev/null +++ b/plinth/tests/test_frontpage.py @@ -0,0 +1,143 @@ +# +# 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 frontpage. +""" + +from unittest.mock import patch + +import pytest + +from plinth.frontpage import Shortcut + +# pylint: disable=protected-access + + +@pytest.fixture(name='clean_global_shotcuts', autouse=True) +def fixture_clean_global_shortcuts(): + """Ensure that global list of shortcuts is clean.""" + Shortcut._all_shortcuts = {} + + +def test_shortcut_init_with_arguments(): + """Test initializing shortcut component without arguments.""" + with pytest.raises(ValueError): + Shortcut(None, None) + + shortcut = Shortcut('test-component', 'test-name') + assert shortcut.component_id == 'test-component' + assert shortcut.name == 'test-name' + assert shortcut.short_description is None + assert shortcut.url == '?selected=test-component' + assert shortcut.icon is None + assert shortcut.description is None + assert shortcut.configure_url is None + assert shortcut.clients is None + assert not shortcut.login_required + assert shortcut.allowed_groups is None + assert Shortcut._all_shortcuts['test-component'] == shortcut + + +def test_shortcut_init(): + """Test initializing shortcut component.""" + clients = ['client1', 'client2'] + allowed_groups = ['group1', 'group2'] + shortcut = Shortcut('test-component', name='test-name', + short_description='test-short-description', + url='test-url', icon='test-icon', + description='test-description', + configure_url='test-configure-url', clients=clients, + login_required=True, allowed_groups=allowed_groups) + assert shortcut.short_description == 'test-short-description' + assert shortcut.url == 'test-url' + assert shortcut.icon == 'test-icon' + assert shortcut.description == 'test-description' + assert shortcut.configure_url == 'test-configure-url' + assert shortcut.clients == clients + assert shortcut.login_required + assert shortcut.allowed_groups == set(allowed_groups) + + +def test_shortcut_remove(): + """Test removing a shortcut global list of shortcuts.""" + shortcut = Shortcut('test-component', None) + shortcut.remove() + with pytest.raises(KeyError): + del Shortcut._all_shortcuts['test-component'] + + +@pytest.fixture(name='common_shortcuts') +def fixture_common_shortcuts(clean_global_shotcuts): + """Create some common shortcuts.""" + shortcuts = [ + Shortcut('anon-web-app-component-1', 'name1', 'short4', url='url1'), + Shortcut('group1-web-app-component-1', 'Name2', 'Short3', url='url2', + allowed_groups=['group1']), + Shortcut('group2-web-app-component-1', 'name3', 'short2', url='url3', + allowed_groups=['group2']), + Shortcut('anon-non-web-app-component-1', 'name4', 'short1', url=None), + ] + return shortcuts + + +def test_shortcut_list_sorting(common_shortcuts): + """Test listing shortcuts in sorted order.""" + cuts = common_shortcuts + + return_list = Shortcut.list() + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] + + return_list = Shortcut.list(sort_by='name') + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] + + return_list = Shortcut.list(sort_by='short_description') + assert return_list == [cuts[3], cuts[2], cuts[1], cuts[0]] + + +def test_shortcut_list_web_apps_only(common_shortcuts): + """Test listing only web app shortcuts.""" + cuts = common_shortcuts + + return_list = Shortcut.list() + assert Shortcut.list() == [cuts[0], cuts[1], cuts[2], cuts[3]] + + return_list = Shortcut.list(web_apps_only=False) + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] + + return_list = Shortcut.list(web_apps_only=True) + assert return_list == [cuts[0], cuts[1], cuts[2]] + + +@patch('plinth.actions.superuser_run') +def test_shortcut_list_with_username(superuser_run, common_shortcuts): + """Test listing for particular users.""" + cuts = common_shortcuts + + return_list = Shortcut.list() + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] + + superuser_run.return_value = 'admin' + return_list = Shortcut.list(username='admin') + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] + + superuser_run.return_value = 'group1' + return_list = Shortcut.list(username='user1') + assert return_list == [cuts[0], cuts[1], cuts[3]] + + superuser_run.return_value = 'group1\ngroup2' + return_list = Shortcut.list(username='user2') + assert return_list == [cuts[0], cuts[1], cuts[2], cuts[3]] diff --git a/plinth/views.py b/plinth/views.py index 3ef58ae40..b949e2f5d 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -44,14 +44,15 @@ REDIRECT_FIELD_NAME = 'next' def index(request): """Serve the main index page.""" username = str(request.user) if request.user.is_authenticated else None - shortcuts = frontpage.get_shortcuts(username) - selection = request.GET.get('selected') + shortcuts = frontpage.Shortcut.list(username) + selected = request.GET.get('selected') - details, details_label, configure_url = None, None, None - if selection in frontpage.shortcuts: - details = frontpage.shortcuts[selection]['details'] - details_label = frontpage.shortcuts[selection]['label'] - configure_url = frontpage.shortcuts[selection]['configure_url'] + selected_shortcut = [ + shortcut + for shortcut in shortcuts + if shortcut.component_id == selected + ] + selected_shortcut = selected_shortcut[0] if selected_shortcut else None disk_views.warn_about_low_disk_space(request) @@ -59,10 +60,7 @@ def index(request): request, 'index.html', { 'title': _('FreedomBox'), 'shortcuts': shortcuts, - 'selected_id': selection, - 'details': details, - 'details_label': details_label, - 'configure_url': configure_url + 'selected_shortcut': selected_shortcut, })