Turn frontpage shortcut into an app component

- Turn frontpage shortcut into an App component. Add tests and full
  documentation.

- Overridden implementations for tahoe, diaspora, mediawiki shortcuts to handle
  special cases. Special handling for ikiwiki.

- Extend App API for removing and retrieving a component.

- Add clients information into shortcuts to avoid hacks when presenting
  shortcuts to Mobile devices via API.

- Fixed unnecessary stripping and adding of '/' when setting home page redirect
  URLs. This fixes problem with setting Cockpit as home page.

- Replaced the use of term 'app' in favor of 'shortcut' as the term when setting
  frontpage shortcuts as home page.

- JSXC shortcut does not require login.

- Don't show shadowsocks for anonymous users.

- Simplify showing selected shortcut details.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Sunil Mohan Adapa 2019-05-17 15:39:39 -07:00
parent b96d901071
commit 75c57d3e00
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
41 changed files with 601 additions and 479 deletions

View File

@ -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)

View File

@ -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():

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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():

View File

@ -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):

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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
})

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -70,6 +70,16 @@ class OpenVPNApp(app_module.App):
parent_url_name='apps')
self.add(menu_item)
download_profile = \
format_lazy(_('<a class="btn btn-primary btn-sm" href="{link}">'
'Download Profile</a>'),
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(_('<a class="btn btn-primary btn-sm" href="{link}">'
'Download Profile</a>'),
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()

View File

@ -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')

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -26,7 +26,7 @@
{% block container %}
{% if messages or details or not shortcuts %}
{% if messages or selected_shortcut or not shortcuts %}
<div class="container content-container">
{% include 'messages.html' %}
@ -41,21 +41,21 @@
</h4>
{% endif %}
{% if details %}
{% if selected_shortcut.description %}
{% block pagetitle %}
<h2>{{ details_label }}</h2>
<h2>{{ selected_shortcut.name }}</h2>
{% endblock %}
{% block description %}
{% for paragraph in details %}
{% for paragraph in selected_shortcut.description %}
<p>{{ paragraph|safe }}</p>
{% endfor %}
{% endblock %}
{% include "clients.html" with clients=clients enabled=service.is_enabled %}
{% if user.is_authenticated and user_is_admin and configure_url %}
<a class="btn btn-primary" href="{{ configure_url }}">
{% if user.is_authenticated and user_is_admin and selected_shortcut.configure_url %}
<a class="btn btn-primary" href="{{ selected_shortcut.configure_url }}">
{% trans "Configure &raquo;" %}</a>
{% endif %}
{% endif %}
@ -68,10 +68,10 @@
<div class="row">
<div class="card-list">
{% for shortcut in shortcuts %}
{% if not shortcut.hidden %}
{% if shortcut.is_enabled %}
{% if user.is_authenticated or not shortcut.login_required %}
<div class="card thumbnail">
{% if selected_id == shortcut.id %}
{% if selected_shortcut.component_id == shortcut.component_id %}
<a href="{{ shortcut.url }}" class="nav-link active" data-turbolinks="false">
{% else %}
<a href="{{ shortcut.url }}" class="nav-link" data-turbolinks="false">

View File

@ -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()

View File

@ -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 <http://www.gnu.org/licenses/>.
#
"""
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]]

View File

@ -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,
})