From 7f4c5f7410b08134bb737ea15a96d625ba62861a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 11 Feb 2016 09:45:09 +0530 Subject: [PATCH 01/45] Make app names as module identifiers - The last part of the module import path is the module name. This also becomes the Django app name. Apps names have to be unique. Hence, there is no scope for two different modules with same name but different load path to exist in the project. - Most uses of list of loaded modules are dealing with app names instead of full module load path. This is due to the fact that Django deals with app names and not module paths. - It is also somewhat clumsy to access a loaded module as we are re-importing every time to get access module. - Simplify all of the above by using app names are module identifiers and maintaing an ordered dictionary of app names to loadded modules. - Remove unused imports. - Minor styling fixes. --- plinth/__main__.py | 4 +-- plinth/module_loader.py | 38 ++++++++++++----------- plinth/modules/avahi/__init__.py | 4 +-- plinth/modules/config/__init__.py | 4 +-- plinth/modules/config/urls.py | 2 +- plinth/modules/datetime/__init__.py | 3 +- plinth/modules/deluge/__init__.py | 3 +- plinth/modules/diagnostics/__init__.py | 2 +- plinth/modules/diagnostics/diagnostics.py | 27 ++++++---------- plinth/modules/dynamicdns/__init__.py | 2 +- plinth/modules/firewall/__init__.py | 2 +- plinth/modules/ikiwiki/__init__.py | 3 +- plinth/modules/letsencrypt/__init__.py | 8 +---- plinth/modules/monkeysphere/__init__.py | 2 +- plinth/modules/mumble/__init__.py | 3 +- plinth/modules/names/__init__.py | 2 +- plinth/modules/networks/__init__.py | 2 +- plinth/modules/openvpn/__init__.py | 2 +- plinth/modules/owncloud/__init__.py | 2 +- plinth/modules/pagekite/__init__.py | 2 +- plinth/modules/power/__init__.py | 2 +- plinth/modules/privoxy/__init__.py | 7 ++--- plinth/modules/quassel/__init__.py | 2 +- plinth/modules/repro/__init__.py | 2 +- plinth/modules/restore/__init__.py | 2 +- plinth/modules/roundcube/__init__.py | 4 +-- plinth/modules/shaarli/__init__.py | 2 +- plinth/modules/tor/__init__.py | 2 +- plinth/modules/transmission/__init__.py | 3 +- plinth/modules/upgrades/__init__.py | 2 +- plinth/modules/users/__init__.py | 4 +-- plinth/modules/xmpp/__init__.py | 3 +- 32 files changed, 61 insertions(+), 91 deletions(-) diff --git a/plinth/__main__.py b/plinth/__main__.py index 37e62ea4b..b3f31ce3c 100644 --- a/plinth/__main__.py +++ b/plinth/__main__.py @@ -132,9 +132,7 @@ def setup_server(): cherrypy.tree.mount(None, manual_url, config) logger.debug('Serving manual images %s on %s', manual_dir, manual_url) - for module_import_path in module_loader.loaded_modules: - module = importlib.import_module(module_import_path) - module_name = module_import_path.split('.')[-1] + for module_name, module in module_loader.loaded_modules.items(): module_path = os.path.dirname(module.__file__) static_dir = os.path.join(module_path, 'static') if not os.path.isdir(static_dir): diff --git a/plinth/module_loader.py b/plinth/module_loader.py index e8cfd6dc9..c8e58cac1 100644 --- a/plinth/module_loader.py +++ b/plinth/module_loader.py @@ -19,6 +19,7 @@ Discover, load and manage Plinth modules """ +import collections import django import importlib import logging @@ -29,9 +30,9 @@ from plinth import cfg from plinth import urls from plinth.signals import pre_module_loading, post_module_loading -LOGGER = logging.getLogger(__name__) +logger = logging.getLogger(__name__) -loaded_modules = [] +loaded_modules = collections.OrderedDict() _modules_to_load = None @@ -42,16 +43,18 @@ def load_modules(): """ pre_module_loading.send_robust(sender="module_loader") modules = {} - for module_name in get_modules_to_load(): - LOGGER.info('Importing %s', module_name) + for module_import_path in get_modules_to_load(): + logger.info('Importing %s', module_import_path) + module_name = module_import_path.split('.')[-1] try: - modules[module_name] = importlib.import_module(module_name) + modules[module_name] = importlib.import_module(module_import_path) except Exception as exception: - LOGGER.exception('Could not import %s: %s', module_name, exception) + logger.exception('Could not import %s: %s', module_import_path, + exception) if cfg.debug: raise - _include_module_urls(module_name) + _include_module_urls(module_import_path, module_name) ordered_modules = [] remaining_modules = dict(modules) # Make a copy @@ -64,14 +67,14 @@ def load_modules(): _insert_modules(module_name, module, remaining_modules, ordered_modules) except KeyError: - LOGGER.error('Unsatified dependency for module - %s', + logger.error('Unsatified dependency for module - %s', module_name) - LOGGER.debug('Module load order - %s', ordered_modules) + logger.debug('Module load order - %s', ordered_modules) for module_name in ordered_modules: _initialize_module(modules[module_name]) - loaded_modules.append(module_name) + loaded_modules[module_name] = modules[module_name] post_module_loading.send_robust(sender="module_loader") @@ -94,7 +97,7 @@ def _insert_modules(module_name, module, remaining_modules, ordered_modules): try: module = remaining_modules.pop(dependency) except KeyError: - LOGGER.error('Not found or circular dependency - %s, %s', + logger.error('Not found or circular dependency - %s, %s', module_name, dependency) raise @@ -103,16 +106,15 @@ def _insert_modules(module_name, module, remaining_modules, ordered_modules): ordered_modules.append(module_name) -def _include_module_urls(module_name): +def _include_module_urls(module_import_path, module_name): """Include the module's URLs in global project URLs list""" - namespace = module_name.split('.')[-1] - url_module = module_name + '.urls' + url_module = module_import_path + '.urls' try: urls.urlpatterns += [ django.conf.urls.url( - r'', django.conf.urls.include(url_module, namespace))] + r'', django.conf.urls.include(url_module, module_name))] except ImportError: - LOGGER.debug('No URLs for %s', module_name) + logger.debug('No URLs for %s', module_name) if cfg.debug: raise @@ -122,13 +124,13 @@ def _initialize_module(module): try: init = module.init except AttributeError: - LOGGER.debug('No init() for module - %s', module.__name__) + logger.debug('No init() for module - %s', module.__name__) return try: init() except Exception as exception: - LOGGER.exception('Exception while running init for %s: %s', + logger.exception('Exception while running init for %s: %s', module, exception) if cfg.debug: raise diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index 7c8f5592c..84987cdc7 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -20,16 +20,14 @@ Plinth module for service discovery. """ from django.utils.translation import ugettext_lazy as _ -import subprocess -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module # pylint: disable=C0103 -depends = ['plinth.modules.system'] +depends = ['system'] service = None diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index 12cb341ff..f4f34c4e3 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -24,6 +24,4 @@ from .config import init __all__ = ['config', 'init'] -depends = ['plinth.modules.system', - 'plinth.modules.firewall', - 'plinth.modules.names'] +depends = ['system', 'firewall', 'names'] diff --git a/plinth/modules/config/urls.py b/plinth/modules/config/urls.py index 35f392f31..846f6e19f 100644 --- a/plinth/modules/config/urls.py +++ b/plinth/modules/config/urls.py @@ -19,7 +19,7 @@ URLs for the Configuration module """ -from django.conf.urls import patterns, url +from django.conf.urls import url from . import config as views diff --git a/plinth/modules/datetime/__init__.py b/plinth/modules/datetime/__init__.py index 147b95146..1ca8f6244 100644 --- a/plinth/modules/datetime/__init__.py +++ b/plinth/modules/datetime/__init__.py @@ -22,13 +22,12 @@ Plinth module to configure system date and time from django.utils.translation import ugettext_lazy as _ import subprocess -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.system'] +depends = ['system'] service = None diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index 7038d3696..40fc0fd40 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -21,13 +21,12 @@ Plinth module to configure a Deluge web client. from django.utils.translation import ugettext_lazy as _ -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index 8005882b3..294d3f884 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -25,7 +25,7 @@ from plinth import action_utils __all__ = ['diagnostics', 'init'] -depends = ['plinth.modules.system'] +depends = ['system'] def diagnose(): diff --git a/plinth/modules/diagnostics/diagnostics.py b/plinth/modules/diagnostics/diagnostics.py index 19ab23e7d..4ce058e09 100644 --- a/plinth/modules/diagnostics/diagnostics.py +++ b/plinth/modules/diagnostics/diagnostics.py @@ -60,19 +60,14 @@ def index(request): @require_POST def module(request, module_name): """Return diagnostics for a particular module.""" - found = False - for module_import_path in module_loader.loaded_modules: - if module_name == module_import_path.split('.')[-1]: - found = True - break - - if not found: + try: + module = module_loader.loaded_modules[module_name] + except KeyError: raise Http404('Module does not exist or not loaded') - loaded_module = importlib.import_module(module_import_path) results = [] - if hasattr(loaded_module, 'diagnose'): - results = loaded_module.diagnose() + if hasattr(module, 'diagnose'): + results = module.diagnose() return TemplateResponse(request, 'diagnostics_module.html', {'title': _('Diagnostic Test'), @@ -110,17 +105,15 @@ def run_on_all_modules(): 'progress_percentage': 0} modules = [] - for module_import_path in module_loader.loaded_modules: - loaded_module = importlib.import_module(module_import_path) - if not hasattr(loaded_module, 'diagnose'): + for module_name, module in module_loader.loaded_modules.items(): + if not hasattr(module, 'diagnose'): continue - module_name = module_import_path.split('.')[-1] - modules.append((module_name, loaded_module)) + modules.append((module_name, module)) current_results['results'][module_name] = None current_results['modules'] = modules - for current_index, (module_name, loaded_module) in enumerate(modules): - current_results['results'][module_name] = loaded_module.diagnose() + for current_index, (module_name, module) in enumerate(modules): + current_results['results'][module_name] = module.diagnose() current_results['progress_percentage'] = \ int((current_index + 1) * 100 / len(modules)) diff --git a/plinth/modules/dynamicdns/__init__.py b/plinth/modules/dynamicdns/__init__.py index 747482e9d..7b4bc398d 100644 --- a/plinth/modules/dynamicdns/__init__.py +++ b/plinth/modules/dynamicdns/__init__.py @@ -24,4 +24,4 @@ from .dynamicdns import init __all__ = ['dynamicdns', 'init'] -depends = ['plinth.modules.apps'] +depends = ['apps'] diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index 27daf85a0..3abc58c06 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -24,4 +24,4 @@ from .firewall import init __all__ = ['firewall', 'init'] -depends = ['plinth.modules.system'] +depends = ['system'] diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index 402f4b6ae..8830af010 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -21,13 +21,12 @@ Plinth module to configure ikiwiki from django.utils.translation import ugettext_lazy as _ -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 565d78bed..1375ed176 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -20,19 +20,13 @@ Plinth module for using Let's Encrypt. """ from django.utils.translation import ugettext_lazy as _ -import json -from plinth import actions from plinth import action_utils from plinth import cfg -from plinth import service as service_module from plinth.modules import names -depends = [ - 'plinth.modules.apps', - 'plinth.modules.names' -] +depends = ['apps', 'names'] service = None diff --git a/plinth/modules/monkeysphere/__init__.py b/plinth/modules/monkeysphere/__init__.py index d9501870d..b7a8329f3 100644 --- a/plinth/modules/monkeysphere/__init__.py +++ b/plinth/modules/monkeysphere/__init__.py @@ -23,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg -depends = ['plinth.modules.system'] +depends = ['system'] def init(): diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index 570a57cc8..846681b29 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -21,13 +21,12 @@ Plinth module to configure Mumble server from django.utils.translation import ugettext_lazy as _ -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/names/__init__.py b/plinth/modules/names/__init__.py index 841b95f7f..8f9fd8488 100644 --- a/plinth/modules/names/__init__.py +++ b/plinth/modules/names/__init__.py @@ -31,7 +31,7 @@ SERVICES = ( ('ssh', _('SSH'), 22), ) -depends = ['plinth.modules.system'] +depends = ['system'] domain_types = {} domains = {} diff --git a/plinth/modules/networks/__init__.py b/plinth/modules/networks/__init__.py index a3a679823..c3c93a17b 100644 --- a/plinth/modules/networks/__init__.py +++ b/plinth/modules/networks/__init__.py @@ -31,7 +31,7 @@ from plinth import network __all__ = ['networks', 'init'] -depends = ['plinth.modules.system'] +depends = ['system'] logger = Logger(__name__) diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index 20be7aabe..688b1507f 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -27,7 +27,7 @@ from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/owncloud/__init__.py b/plinth/modules/owncloud/__init__.py index e48fcae2d..543909e21 100644 --- a/plinth/modules/owncloud/__init__.py +++ b/plinth/modules/owncloud/__init__.py @@ -26,7 +26,7 @@ from plinth import action_utils __all__ = ['owncloud', 'init'] -depends = ['plinth.modules.apps'] +depends = ['apps'] def diagnose(): diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index b83c7e5ae..7e5f689ba 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -26,7 +26,7 @@ from . import utils __all__ = ['init'] -depends = ['plinth.modules.apps', 'plinth.modules.names'] +depends = ['apps', 'names'] def init(): diff --git a/plinth/modules/power/__init__.py b/plinth/modules/power/__init__.py index f8f4c4e56..32cd2af14 100644 --- a/plinth/modules/power/__init__.py +++ b/plinth/modules/power/__init__.py @@ -23,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg -depends = ['plinth.modules.system'] +depends = ['system'] def init(): diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index 545020a0f..4c3e07627 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -20,7 +20,6 @@ Plinth module to configure Privoxy. """ from django.utils.translation import ugettext_lazy as _ -import json from plinth import actions from plinth import action_utils @@ -28,7 +27,7 @@ from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None @@ -84,9 +83,7 @@ def diagnose_url_with_proxy(): result = action_utils.diagnose_url(url, kind=address['kind'], env=env) result[0] = _('Access {url} with proxy {proxy} on tcp{kind}') \ - .format(url=url, proxy=proxy, kind=address['kind']) + .format(url=url, proxy=proxy, kind=address['kind']) results.append(result) return results - - diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 3e8c1b03a..54a2ec45b 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -25,7 +25,7 @@ from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/repro/__init__.py b/plinth/modules/repro/__init__.py index 7517052f8..5cba93082 100644 --- a/plinth/modules/repro/__init__.py +++ b/plinth/modules/repro/__init__.py @@ -25,7 +25,7 @@ from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/restore/__init__.py b/plinth/modules/restore/__init__.py index 21bb030d4..45404a27a 100644 --- a/plinth/modules/restore/__init__.py +++ b/plinth/modules/restore/__init__.py @@ -27,7 +27,7 @@ service = None __all__ = ['init'] -depends = ['plinth.modules.apps'] +depends = ['apps'] def init(): diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index 5d9bd1e40..c170c6f2b 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -21,13 +21,11 @@ Plinth module to configure Roundcube. from django.utils.translation import ugettext_lazy as _ -from plinth import actions from plinth import action_utils from plinth import cfg -from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] def init(): diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index e23af9186..ac2e07df1 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -26,7 +26,7 @@ from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index bd7ef694e..6933e9348 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -32,7 +32,7 @@ from plinth.modules.names import SERVICES from plinth.signals import domain_added -depends = ['plinth.modules.apps', 'plinth.modules.names'] +depends = ['apps', 'names'] socks_service = None bridge_service = None diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index b57797b99..4206c1985 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -21,13 +21,12 @@ Plinth module to configure Transmission server from django.utils.translation import ugettext_lazy as _ -from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index 28b9f7c4a..2e5110a16 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -24,7 +24,7 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg -depends = ['plinth.modules.system'] +depends = ['system'] def init(): diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index 7232efaa1..7cb738071 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -20,14 +20,12 @@ Plinth module to manage users """ from django.utils.translation import ugettext_lazy as _ -import json import subprocess from plinth import cfg -from plinth import actions from plinth import action_utils -depends = ['plinth.modules.system'] +depends = ['system'] def init(): diff --git a/plinth/modules/xmpp/__init__.py b/plinth/modules/xmpp/__init__.py index d0851c24a..65e8a3c19 100644 --- a/plinth/modules/xmpp/__init__.py +++ b/plinth/modules/xmpp/__init__.py @@ -20,7 +20,6 @@ Plinth module to configure XMPP server """ from django.utils.translation import ugettext_lazy as _ -import json from plinth import actions from plinth import action_utils @@ -30,7 +29,7 @@ from plinth.signals import pre_hostname_change, post_hostname_change from plinth.signals import domainname_change -depends = ['plinth.modules.apps'] +depends = ['apps'] service = None From 1e8cc6f2904783bba3f2b4467269b4356459bd52 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 11 Feb 2016 09:51:39 +0530 Subject: [PATCH 02/45] Cleanup final use of urls.patterns() method - As deprecated in Django 1.9. --- plinth/urls.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plinth/urls.py b/plinth/urls.py index 5ab1c866e..44be0a509 100644 --- a/plinth/urls.py +++ b/plinth/urls.py @@ -19,10 +19,9 @@ Django URLconf file containing all urls """ -from django.conf.urls import patterns, url +from django.conf.urls import url +from . import views - -urlpatterns = patterns( # pylint: disable-msg=C0103 - 'plinth.views', - url(r'^$', 'index', name='index') -) +urlpatterns = [ + url(r'^$', views.index, name='index') +] From 20a97488d90a23be266af0d198c5330c8ec97912 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:43:15 +0530 Subject: [PATCH 03/45] package: Proper string casting of PackageException --- plinth/package.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plinth/package.py b/plinth/package.py index 75a8bfca0..5342d9f23 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -46,6 +46,11 @@ class PackageException(Exception): self.error_string = error_string self.error_details = error_details + def __str__(self): + """Return the strin representation of the exception.""" + return 'PackageException(error_string="{0}", error_details="{1}")' \ + .format(self.error_string, self.error_details) + class Transaction(object): """Information about an ongoing transaction.""" From 716ba5f1a02897c426114a879bf8c01b0d960729 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:45:24 +0530 Subject: [PATCH 04/45] package: Expose starting install without thread --- plinth/package.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plinth/package.py b/plinth/package.py index 5342d9f23..5a5f2ae3c 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -94,15 +94,15 @@ class Transaction(object): self.package_names, self.allow_cancel, self.status_string, self.percentage, self.package, self.item_progress) - def start_install(self): + def start_install_in_thread(self): """Start a PackageKit transaction to install given list of packages. This operation is non-blocking at it spawns a new thread. """ - thread = threading.Thread(target=self._install) + thread = threading.Thread(target=self.install) thread.start() - def _install(self): + def install(self): """Run a PackageKit transaction to install given packages.""" try: if self.before_install: @@ -319,4 +319,4 @@ def start_install(package_names, before_install=None, on_install=None): on_install=on_install) transactions[frozenset(package_names)] = transaction - transaction.start_install() + transaction.start_install_in_thread() From 2bba8b07fed49a984699285e3242cb9618a96fa0 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:46:42 +0530 Subject: [PATCH 05/45] models: New model to store module setup versions --- plinth/migrations/0002_modulestore.py | 22 ++++++++++++++++++++++ plinth/models.py | 6 ++++++ 2 files changed, 28 insertions(+) create mode 100644 plinth/migrations/0002_modulestore.py diff --git a/plinth/migrations/0002_modulestore.py b/plinth/migrations/0002_modulestore.py new file mode 100644 index 000000000..43b81c064 --- /dev/null +++ b/plinth/migrations/0002_modulestore.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-02-10 12:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('plinth', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Module', + fields=[ + ('name', models.TextField(primary_key=True, serialize=False)), + ('setup_version', models.IntegerField()), + ], + ), + ] diff --git a/plinth/models.py b/plinth/models.py index 8dc1cfd03..bf2bf3350 100644 --- a/plinth/models.py +++ b/plinth/models.py @@ -37,3 +37,9 @@ class KVStore(models.Model): def value(self, val): """Store the value of the key/value pair by JSON encoding it""" self.value_json = json.dumps(val) + + +class Module(models.Model): + """Model to store current setup versions of a module.""" + name = models.TextField(primary_key=True) + setup_version = models.IntegerField() From be6ccabec27fa9379ad744e4740dbde17ab90a4a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:54:25 +0530 Subject: [PATCH 06/45] setup: New module to handle application setup - There shall be a setup helper object in every module. The setup process will simply call methods in helper. - Helper is responsible for loading/storing/checking module setup versions. - Methods help in showing progress of setup process automatically. --- plinth/setup.py | 163 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 plinth/setup.py diff --git a/plinth/setup.py b/plinth/setup.py new file mode 100644 index 000000000..2ade51021 --- /dev/null +++ b/plinth/setup.py @@ -0,0 +1,163 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Plinth module with utilites for performing application setup operations. +""" + +import logging +import threading + +from . import package +import plinth + +logger = logging.getLogger(__name__) + + +class Helper(object): + """Helper routines for modules to show progress.""" + + def __init__(self, module_name, module): + """Initialize the object.""" + self.module_name = module_name + self.module = module + self.current_operation = None + self.is_finished = None + self.exception = None + + def run_in_thread(self): + """Execute the setup process in a thread.""" + thread = threading.Thread(target=self._run) + thread.start() + + def _run(self): + """Collect exceptions when running in a thread.""" + try: + self.run() + except Exception as exception: + self.exception = exception + + def collect_result(self): + """Return the exception if any.""" + exception = self.exception + self.exception = None + self.is_finished = None + return exception + + def run(self): + """Execute the setup process.""" + # Setup for the module is already running + if self.current_operation: + return + + current_version = self.get_setup_version() + if current_version >= self.module.version: + return + + self.exception = None + self.current_operation = None + self.is_finished = False + try: + if hasattr(self.module, 'setup'): + logger.info('Running module setup - %s', self.module_name) + self.module.setup(self, old_version=current_version) + else: + logger.info('Module does not require setup - %s', + self.module_name) + except Exception as exception: + logger.exception('Error running setup - %s', exception) + raise exception + else: + self.set_setup_version(self.module.version) + finally: + self.is_finished = True + self.current_operation = None + + def install(self, package_names): + """Install a set of packages marking progress.""" + logger.info('Running install for module - %s, packages - %s', + self.module_name, package_names) + transaction = package.Transaction(package_names) + self.current_operation = { + 'step': 'install', + 'transaction': transaction, + } + + transaction.install() + if transaction.exception: + logger.error('Error running install - %s', transaction.exception) + raise transaction.exception + + def call(self, step, method, *args, **kwargs): + """Call an arbitrary method during setup and note down its stage.""" + logger.info('Running step for module - %s, step - %s', + self.module_name, step) + self.current_operation = {'step': step} + return method(*args, **kwargs) + + def get_state(self): + """Return whether the module is not setup or needs upgrade.""" + current_version = self.get_setup_version() + if current_version and self.module.version <= current_version: + return 'up-to-date' + + # If a module need installing/updating but no setup method is + # available, then automatically set version. + # + # Minor violation of 'get' only discipline for convenience. + if not hasattr(self.module, 'setup'): + self.set_setup_version(self.module.version) + return 'up-to-date' + + if not current_version: + return 'needs-setup' + else: + return 'needs-update' + + def get_setup_version(self): + """Return the setup version of a module.""" + # XXX: Optimize version gets + from . import models + + try: + module_entry = models.Module.objects.get(pk=self.module_name) + return module_entry.setup_version + except models.Module.DoesNotExist: + return 0 + + def set_setup_version(self, version): + """Set a module's setup version.""" + from . import models + + models.Module.objects.update_or_create( + pk=self.module_name, defaults={'setup_version': version}) + + +def init(module_name, module): + """Create a setup helper for a module for later use.""" + if not hasattr(module, 'setup_helper'): + module.setup_helper = Helper(module_name, module) + + +def setup_all_modules(essential=False): + """Run setup on all essential modules and exit.""" + logger.info('Running setup for all modules, essential - %s', essential) + for module_name, module in plinth.module_loader.loaded_modules.items(): + if essential and not getattr(module, 'is_essential', False): + continue + + module.setup_helper.run() From 80d9f0d41e6df73945b439224ae128a0d4f259f9 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:57:20 +0530 Subject: [PATCH 07/45] module_loader: Add setup helper to every module - During initialization add a setup helper to every module. For use later. --- plinth/module_loader.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plinth/module_loader.py b/plinth/module_loader.py index c8e58cac1..272c1ae78 100644 --- a/plinth/module_loader.py +++ b/plinth/module_loader.py @@ -28,6 +28,7 @@ import re from plinth import cfg from plinth import urls +from plinth import setup from plinth.signals import pre_module_loading, post_module_loading logger = logging.getLogger(__name__) @@ -73,7 +74,7 @@ def load_modules(): logger.debug('Module load order - %s', ordered_modules) for module_name in ordered_modules: - _initialize_module(modules[module_name]) + _initialize_module(module_name, modules[module_name]) loaded_modules[module_name] = modules[module_name] post_module_loading.send_robust(sender="module_loader") @@ -119,8 +120,11 @@ def _include_module_urls(module_import_path, module_name): raise -def _initialize_module(module): +def _initialize_module(module_name, module): """Call initialization method in the module if it exists""" + # Perform setup related initialization on the module + setup.init(module_name, module) + try: init = module.init except AttributeError: From 7a4a8fbc990864e1ab29f73113037df2d8027cf4 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:58:28 +0530 Subject: [PATCH 08/45] views: New view to show setup need and progress --- plinth/templates/setup.html | 101 ++++++++++++++++++++++++++++++++++++ plinth/views.py | 26 ++++++++++ 2 files changed, 127 insertions(+) create mode 100644 plinth/templates/setup.html diff --git a/plinth/templates/setup.html b/plinth/templates/setup.html new file mode 100644 index 000000000..4cd25ee0f --- /dev/null +++ b/plinth/templates/setup.html @@ -0,0 +1,101 @@ +{% extends "base.html" %} +{% comment %} +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +{% endcomment %} + +{% load bootstrap %} +{% load i18n %} + +{% block page_head %} + + {% if setup_helper.current_operation %} + + {% endif %} + +{% endblock %} + + +{% block content %} + +

{% trans "Installation" %}: {{ setup_helper.module.title }}

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

{{ paragraph|safe }}

+ {% endfor %} + + {% if not setup_helper.current_operation %} + + {% if setup_helper.get_state == 'needs-setup' %} +

+ {% blocktrans trimmed %} + Install this application? + {% endblocktrans %} +

+ +
+ {% csrf_token %} + + +
+ {% elif setup_helper.get_state == 'needs-update' %} +

+ {% blocktrans trimmed %} + This application needs an update. Update now? + {% endblocktrans %} +

+ +
+ {% csrf_token %} + + +
+ {% endif %} + + {% else %} + + {% if setup_helper.current_operation.step == 'pre' %} + {% trans "Performing pre-install operation" %} + {% elif setup_helper.current_operation.step == 'post' %} + {% trans "Performing post-install operation" %} + {% elif setup_helper.current_operation.step == 'install' %} + {% with transaction=setup_helper.current_operation.transaction %} +
+ {% blocktrans trimmed with package_names=transaction.package_names|join:", " status=transaction.status_string %} + Installing {{ package_names }}: {{ status }} + {% endblocktrans %} +
+
+
+ + {% blocktrans trimmed with percentage=transaction.percentage %} + {{ percentage }}% complete + {% endblocktrans %} + +
+
+ {% endwith %} + {% endif %} + + {% endif %} + +{% endblock %} diff --git a/plinth/views.py b/plinth/views.py index cfbbd608b..8cee96adf 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -22,6 +22,7 @@ Main Plinth views from django.core.urlresolvers import reverse from django.http.response import HttpResponseRedirect from django.views.generic import TemplateView +import time from plinth import package as package_module @@ -61,3 +62,28 @@ class PackageInstallView(TemplateView): before_install=self.kwargs.get('before_install'), on_install=self.kwargs.get('on_install')) return self.render_to_response(self.get_context_data()) + + +class SetupView(TemplateView): + """View to prompt and setup applications.""" + template_name = 'setup.html' + + def get_context_data(self, **kwargs): + """Return the context data rendering the template.""" + context = super(SetupView, self).get_context_data(**kwargs) + context['setup_helper'] = self.kwargs['setup_helper'] + return context + + def post(self, *args, **kwargs): + """Handle installing/upgrading applications. + + Start the application setup, and refresh the page every few + seconds to keep displaying the status. + """ + self.kwargs['setup_helper'].run_in_thread() + + # Give a moment for the setup process to start and show + # meaningful status. + time.sleep(1) + + return self.render_to_response(self.get_context_data()) From 8dcafe3e0eea1b0a38eee7e41e5e3ac668e05e25 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 13:59:40 +0530 Subject: [PATCH 09/45] middleware: New middleware to check need for setup - Call the setup helper to check if a installation or update of a module is required. - Show installation and progress using the setup view. --- plinth/__main__.py | 1 + plinth/middleware.py | 75 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 plinth/middleware.py diff --git a/plinth/__main__.py b/plinth/__main__.py index b3f31ce3c..af4a82d5b 100644 --- a/plinth/__main__.py +++ b/plinth/__main__.py @@ -256,6 +256,7 @@ def configure_django(): 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'stronghold.middleware.LoginRequiredMiddleware', 'plinth.modules.first_boot.middleware.FirstBootMiddleware', + 'plinth.middleware.SetupMiddleware', ), ROOT_URLCONF='plinth.urls', SECURE_PROXY_SSL_HEADER=secure_proxy_ssl_header, diff --git a/plinth/middleware.py b/plinth/middleware.py new file mode 100644 index 000000000..b4ad0f0b4 --- /dev/null +++ b/plinth/middleware.py @@ -0,0 +1,75 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Django middleware to show pre-setup message and setup progress. +""" + +from django.contrib import messages +from django.core.urlresolvers import resolve +from django.utils.translation import ugettext_lazy as _ +import logging + +import plinth +from plinth.package import PackageException +from . import views + + +logger = logging.getLogger(__name__) + + +class SetupMiddleware(object): + """Show setup page or progress if setup is neccessary or running.""" + + @staticmethod + def process_request(request): + """Handle a request as Django middleware request handler.""" + # Perform a URL resolution. This is slightly inefficient as + # Django will do this resolution again. + resolver_match = resolve(request.path_info) + if not resolver_match.namespaces or not len(resolver_match.namespaces): + # Requested URL does not belong to any application + return + + module_name = resolver_match.namespaces[0] + module = plinth.module_loader.loaded_modules[module_name] + + # Collect errors from any previous operations and show them + if module.setup_helper.is_finished: + exception = module.setup_helper.collect_result() + if not exception: + messages.success(request, _('Application installed.')) + else: + if isinstance(exception, PackageException): + error_string = getattr(exception, 'error_string', + str(exception)) + error_details = getattr(exception, 'error_details', '') + message = _('Error installing application: {string} ' + '{details}').format( + string=error_string, details=error_details) + else: + message = _('Error installing application: {error}') \ + .format(error=exception) + + messages.error(request, message) + + # Check if application is up-to-date + if module.setup_helper.get_state() == 'up-to-date': + return + + view = views.SetupView.as_view() + return view(request, setup_helper=module.setup_helper) From 2c836046a61b4132d090848620307136caa06540 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:00:47 +0530 Subject: [PATCH 10/45] main: Add command argument to setup essential apps - The --setup argument sets up all applications that declare themselves as essential. - This is done synchronously. - Plinth exits after the setup is complete. - Plinth fails with an error in case any of the setup tasks fail. The process will be continued on next invocation or access of application from UI. --- plinth/__main__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plinth/__main__.py b/plinth/__main__.py index af4a82d5b..6b463d272 100644 --- a/plinth/__main__.py +++ b/plinth/__main__.py @@ -33,6 +33,7 @@ from cherrypy.process.plugins import Daemonizer from plinth import cfg from plinth import module_loader from plinth import service +from plinth import setup logger = logging.getLogger(__name__) @@ -57,6 +58,9 @@ def parse_arguments(): parser.add_argument( '--no-daemon', action='store_true', default=cfg.no_daemon, help='do not start as a daemon') + parser.add_argument( + '--setup', action='store_true', default=False, + help='run setup tasks on all essential modules and exit') parser.add_argument( '--diagnose', action='store_true', default=False, help='run diagnostic tests and exit') @@ -277,6 +281,18 @@ def configure_django(): os.chmod(cfg.store_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) +def run_setup_and_exit(): + """Run setup on all essential modules and exit.""" + error_code = 0 + try: + setup.setup_all_modules(essential=True) + except Exception as exception: + logger.error('Error running setup - %s', exception) + error_code = 1 + + sys.exit(error_code) + + def run_diagnostics_and_exit(): """Run diagostics on all modules and exit.""" module = importlib.import_module('plinth.modules.diagnostics.diagnostics') @@ -314,6 +330,9 @@ def main(): module_loader.load_modules() + if arguments.setup: + run_setup_and_exit() + if arguments.diagnose: run_diagnostics_and_exit() From 45a1bff51d3ef5daef5207569bc73c805205aa26 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:03:36 +0530 Subject: [PATCH 11/45] templates: Base template for all applications To show introduction and title of a module which will be available as properties of a module. This allows the setup process to show the introduction to the application instead of a blank page. --- plinth/templates/app.html | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 plinth/templates/app.html diff --git a/plinth/templates/app.html b/plinth/templates/app.html new file mode 100644 index 000000000..023171c08 --- /dev/null +++ b/plinth/templates/app.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% comment %} +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +{% endcomment %} + +{% load i18n %} + +{% block content %} +

{{ title }}

+ + {% for paragraph in description %} +

{{ paragraph|safe }}

+ {% endfor %} + + {% block configuration %} + {% endblock %} + +{% endblock %} From b112c828897a3d062632bd8bcea6ce5cf59cae39 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:06:43 +0530 Subject: [PATCH 12/45] privoxy: Use new setup mechanism --- plinth/modules/privoxy/__init__.py | 35 ++++++++++++++++--- plinth/modules/privoxy/templates/privoxy.html | 26 ++------------ plinth/modules/privoxy/views.py | 11 ++---- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py index 4c3e07627..eaf0d20d1 100644 --- a/plinth/modules/privoxy/__init__.py +++ b/plinth/modules/privoxy/__init__.py @@ -25,23 +25,50 @@ from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +from plinth.utils import format_lazy +version = 1 + +is_essential = False + depends = ['apps'] +title = _('Web Proxy (Privoxy)') + +description = [ + _('Privoxy is a non-caching web proxy with advanced filtering ' + 'capabilities for enhancing privacy, modifying web page data and ' + 'HTTP headers, controlling access, and removing ads and other ' + 'obnoxious Internet junk. '), + + format_lazy( + _('You can use Privoxy by modifying your browser proxy settings to ' + 'your {box_name} hostname (or IP address) with port 8118. ' + 'While using Privoxy, you can see its configuration details and ' + 'documentation at ' + 'http://config.privoxy.org/ ' + 'or http://p.p.'), box_name=_(cfg.box_name)) +] + service = None def init(): """Intialize the module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Web Proxy (Privoxy)'), 'glyphicon-cloud-upload', - 'privoxy:index', 1000) + menu.add_urlname(title, 'glyphicon-cloud-upload', 'privoxy:index', 1000) global service service = service_module.Service( - 'privoxy', _('Privoxy Web Proxy'), - is_external=False, enabled=is_enabled()) + 'privoxy', title, is_external=False, enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['privoxy']) + helper.call('post', actions.superuser_run, 'privoxy', ['setup']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/privoxy/templates/privoxy.html b/plinth/modules/privoxy/templates/privoxy.html index 4a4f82c00..d80099eb4 100644 --- a/plinth/modules/privoxy/templates/privoxy.html +++ b/plinth/modules/privoxy/templates/privoxy.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,29 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Web Proxy (Privoxy)" %}

- -

- {% blocktrans trimmed %} - Privoxy is a non-caching web proxy with advanced filtering - capabilities for enhancing privacy, modifying web page data and - HTTP headers, controlling access, and removing ads and other - obnoxious Internet junk. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - You can use Privoxy by modifying your browser proxy settings to - your {{ box_name }} hostname (or IP address) with port 8118. - While using Privoxy, you can see its configuration details and - documentation at - http://config.privoxy.org/ - or http://p.p. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/privoxy/views.py b/plinth/modules/privoxy/views.py index 47bb844c7..853f8709f 100644 --- a/plinth/modules/privoxy/views.py +++ b/plinth/modules/privoxy/views.py @@ -26,19 +26,11 @@ import logging from .forms import PrivoxyForm from plinth import actions -from plinth import package from plinth.modules import privoxy logger = logging.getLogger(__name__) -def on_install(): - """Notify that the service is now enabled.""" - actions.superuser_run('privoxy', ['setup']) - privoxy.service.notify_enabled(None, True) - - -@package.required(['privoxy'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -56,7 +48,8 @@ def index(request): form = PrivoxyForm(initial=status, prefix='privoxy') return TemplateResponse(request, 'privoxy.html', - {'title': _('Web Proxy (Privoxy)'), + {'title': privoxy.title, + 'description': privoxy.description, 'status': status, 'form': form}) From 83a56bba934fcff235ccb36864c15cddc1231847 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:09:57 +0530 Subject: [PATCH 13/45] apps: Use new setup mechanism --- plinth/modules/apps/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plinth/modules/apps/__init__.py b/plinth/modules/apps/__init__.py index 28d22ea0b..b7edfd994 100644 --- a/plinth/modules/apps/__init__.py +++ b/plinth/modules/apps/__init__.py @@ -23,3 +23,7 @@ from . import apps from .apps import init __all__ = ['apps', 'init'] + +version = 1 + +is_essential = 1 From ad8fea5eb2a8fe8ab67d435d36798c8e2dfc826b Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:18:20 +0530 Subject: [PATCH 14/45] avahi: Use new setup mechanism --- plinth/modules/avahi/__init__.py | 29 +++++++++++++++++++---- plinth/modules/avahi/templates/avahi.html | 18 ++------------ plinth/modules/avahi/views.py | 5 ++-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py index 84987cdc7..c4c0970bc 100644 --- a/plinth/modules/avahi/__init__.py +++ b/plinth/modules/avahi/__init__.py @@ -24,24 +24,45 @@ from django.utils.translation import ugettext_lazy as _ from plinth import action_utils from plinth import cfg from plinth import service as service_module +from plinth.utils import format_lazy # pylint: disable=C0103 +version = 1 + +is_essential = True + depends = ['system'] +title = _('Service Discovery') + +description = [ + format_lazy( + _('Service discovery allows other devices on the network to ' + 'discover your {{ box_name }} and services running on it. It ' + 'also allows {{ box_name }} to discover other devices and ' + 'services running on your local network. Service discovery is ' + 'not essential and works only on internal networks. It may be ' + 'disabled to improve security especially when connecting to a ' + 'hostile local network.'), box_name=_(cfg.box_name)) +] + service = None def init(): """Intialize the service discovery module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Service Discovery'), 'glyphicon-lamp', - 'avahi:index', 950) + menu.add_urlname(title, 'glyphicon-lamp', 'avahi:index', 950) global service # pylint: disable=W0603 service = service_module.Service( - 'avahi', _('Service Discovery'), ['mdns'], - is_external=False, enabled=is_enabled()) + 'avahi', title, ['mdns'], is_external=False, enabled=is_enabled()) + + +def setup(helper, old_version=False): + """Install and configure the module.""" + helper.install(['avahi-daemon']) def is_enabled(): diff --git a/plinth/modules/avahi/templates/avahi.html b/plinth/modules/avahi/templates/avahi.html index c3d1b98d7..193a1ac07 100644 --- a/plinth/modules/avahi/templates/avahi.html +++ b/plinth/modules/avahi/templates/avahi.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,21 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Service Discovery" %}

- -

- {% blocktrans trimmed %} - Service discovery allows other devices on the network to - discover your {{ box_name }} and services running on it. It - also allows {{ box_name }} to discover other devices and - services running on your local network. Service discovery is - not essential and works only on internal networks. It may be - disabled to improve security especially when connecting to a - hostile local network. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/avahi/views.py b/plinth/modules/avahi/views.py index e8fa4b61c..77751abca 100644 --- a/plinth/modules/avahi/views.py +++ b/plinth/modules/avahi/views.py @@ -26,14 +26,12 @@ import logging from .forms import ServiceDiscoveryForm from plinth import actions -from plinth import package from plinth.modules import avahi logger = logging.getLogger(__name__) # pylint: disable=C0103 -@package.required(['avahi-daemon']) def index(request): """Serve configuration page.""" status = get_status() @@ -50,7 +48,8 @@ def index(request): form = ServiceDiscoveryForm(initial=status, prefix='avahi') return TemplateResponse(request, 'avahi.html', - {'title': _('Service Discovery'), + {'title': avahi.title, + 'description': avahi.description, 'status': status, 'form': form}) From 9d1a940d7c843473cf56b16710797a5db9c5a90c Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:20:19 +0530 Subject: [PATCH 15/45] config: Use new setup mechanism --- plinth/modules/config/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plinth/modules/config/__init__.py b/plinth/modules/config/__init__.py index f4f34c4e3..c1bf7de04 100644 --- a/plinth/modules/config/__init__.py +++ b/plinth/modules/config/__init__.py @@ -24,4 +24,8 @@ from .config import init __all__ = ['config', 'init'] +version = 1 + +is_essential = True + depends = ['system', 'firewall', 'names'] From 0e0b8318d0e362bd7bad04941e1ab0a1adb9bc72 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:26:57 +0530 Subject: [PATCH 16/45] datetime: Use new setup mechanism --- plinth/modules/datetime/__init__.py | 23 +++++++++++++++---- .../modules/datetime/templates/datetime.html | 13 ++--------- plinth/modules/datetime/views.py | 10 ++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/plinth/modules/datetime/__init__.py b/plinth/modules/datetime/__init__.py index 1ca8f6244..0c200169a 100644 --- a/plinth/modules/datetime/__init__.py +++ b/plinth/modules/datetime/__init__.py @@ -27,21 +27,36 @@ from plinth import cfg from plinth import service as service_module +version = 1 + +is_essential = True + depends = ['system'] +title = _('Date & Time') + +description = [ + _('Network time server is a program that maintians the system time ' + 'in synchronization with servers on the Internet.') +] + service = None def init(): """Intialize the date/time module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Date & Time'), 'glyphicon-time', - 'datetime:index', 900) + menu.add_urlname(title, 'glyphicon-time', 'datetime:index', 900) global service service = service_module.Service( - 'ntp', _('Network Time Server'), - is_external=False, enabled=is_enabled()) + 'ntp', title, is_external=False, enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['ntp']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/datetime/templates/datetime.html b/plinth/modules/datetime/templates/datetime.html index bce9499f2..d0a7aa407 100644 --- a/plinth/modules/datetime/templates/datetime.html +++ b/plinth/modules/datetime/templates/datetime.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,16 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Date & Time" %}

- -

- {% blocktrans trimmed %} - Network time server is a program that maintians the system time - in synchronization with servers on the Internet. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/datetime/views.py b/plinth/modules/datetime/views.py index 51ea7e57c..f687e1506 100644 --- a/plinth/modules/datetime/views.py +++ b/plinth/modules/datetime/views.py @@ -26,18 +26,11 @@ import logging from .forms import DateTimeForm from plinth import actions -from plinth import package from plinth.modules import datetime logger = logging.getLogger(__name__) -def on_install(): - """Notify that the service is now enabled.""" - datetime.service.notify_enabled(None, True) - - -@package.required(['ntp'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -55,7 +48,8 @@ def index(request): form = DateTimeForm(initial=status, prefix='datetime') return TemplateResponse(request, 'datetime.html', - {'title': _('Date & Time'), + {'title': datetime.title, + 'description': datetime.description, 'status': status, 'form': form}) From 68881f720c7967385f7539f9a5c202592018224a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:34:13 +0530 Subject: [PATCH 17/45] deluge: Use new setup mechanism --- plinth/modules/deluge/__init__.py | 28 ++++++++++++++++++--- plinth/modules/deluge/templates/deluge.html | 17 ++----------- plinth/modules/deluge/views.py | 11 ++------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/plinth/modules/deluge/__init__.py b/plinth/modules/deluge/__init__.py index 40fc0fd40..f5c1b6f37 100644 --- a/plinth/modules/deluge/__init__.py +++ b/plinth/modules/deluge/__init__.py @@ -21,26 +21,46 @@ Plinth module to configure a Deluge web client. from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('BitTorrent Web Client (Deluge)') + +description = [ + _('Deluge is a BitTorrent client that features a Web UI.'), + + _('When enabled, the Deluge web client will be available from ' + '/deluge path on the web server. The ' + 'default password is \'deluge\', but you should log in and change ' + 'it immediately after enabling this service.') +] + service = None def init(): """Initialize the Deluge module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('BitTorrent (Deluge)'), 'glyphicon-magnet', - 'deluge:index', 200) + menu.add_urlname(title, 'glyphicon-magnet', 'deluge:index', 200) global service service = service_module.Service( - 'deluge', _('Deluge BitTorrent'), ['http', 'https'], - is_external=True, enabled=is_enabled()) + 'deluge', title, ['http', 'https'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['deluged', 'deluge-web']) + helper.call('post', actions.superuser_run, 'deluge', ['enable']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/deluge/templates/deluge.html b/plinth/modules/deluge/templates/deluge.html index 51b54c8ef..019c91b64 100644 --- a/plinth/modules/deluge/templates/deluge.html +++ b/plinth/modules/deluge/templates/deluge.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,20 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "BitTorrent Web Client (Deluge)" %}

- -

{% trans "Deluge is a BitTorrent client that features a Web UI." %}

- -

- {% blocktrans trimmed %} - When enabled, the Deluge web client will be available from - /deluge path on the web server. The - default password is 'deluge', but you should log in and change - it immediately after enabling this service. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/deluge/views.py b/plinth/modules/deluge/views.py index 475cffb30..0b48b2333 100644 --- a/plinth/modules/deluge/views.py +++ b/plinth/modules/deluge/views.py @@ -25,17 +25,9 @@ from django.utils.translation import ugettext as _ from .forms import DelugeForm from plinth import actions -from plinth import package from plinth.modules import deluge -def on_install(): - """Tasks to run after package install.""" - actions.superuser_run('deluge', ['enable']) - deluge.service.notify_enabled(None, True) - - -@package.required(['deluged', 'deluge-web'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -53,7 +45,8 @@ def index(request): form = DelugeForm(initial=status, prefix='deluge') return TemplateResponse(request, 'deluge.html', - {'title': _('BitTorrent (Deluge)'), + {'title': deluge.title, + 'description': deluge.description, 'status': status, 'form': form}) From 28a889c54b30d530a83206ac48c00b1abc4c7697 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 14:42:02 +0530 Subject: [PATCH 18/45] diagnostics: Use new setup mechanism --- plinth/modules/diagnostics/__init__.py | 25 ++++++++++++++++--- plinth/modules/diagnostics/diagnostics.py | 13 +++------- .../diagnostics/templates/diagnostics.html | 14 ++--------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/plinth/modules/diagnostics/__init__.py b/plinth/modules/diagnostics/__init__.py index 294d3f884..f532db3ee 100644 --- a/plinth/modules/diagnostics/__init__.py +++ b/plinth/modules/diagnostics/__init__.py @@ -19,15 +19,32 @@ Plinth module for system diagnostics """ -from . import diagnostics -from .diagnostics import init -from plinth import action_utils +from django.utils.translation import ugettext_lazy as _ -__all__ = ['diagnostics', 'init'] +from plinth import action_utils +from plinth import cfg + +version = 1 + +is_essential = True + +title = _('Diagnostics') + +description = [ + _('The system diagnostic test will run a number of checks on your ' + 'system to confirm that applications and services are working as ' + 'expected.') +] depends = ['system'] +def init(): + """Initialize the module""" + menu = cfg.main_menu.get('system:index') + menu.add_urlname(title, 'glyphicon-screenshot', 'diagnostics:index', 30) + + def diagnose(): """Run diagnostics and return the results.""" results = [] diff --git a/plinth/modules/diagnostics/diagnostics.py b/plinth/modules/diagnostics/diagnostics.py index 4ce058e09..a44539afe 100644 --- a/plinth/modules/diagnostics/diagnostics.py +++ b/plinth/modules/diagnostics/diagnostics.py @@ -24,12 +24,11 @@ from django.http import Http404 from django.template.response import TemplateResponse from django.views.decorators.http import require_POST from django.utils.translation import ugettext_lazy as _ -import importlib import logging import threading -from plinth import cfg from plinth import module_loader +from plinth.modules import diagnostics logger = logging.Logger(__name__) @@ -39,20 +38,14 @@ current_results = {} _running_task = None -def init(): - """Initialize the module""" - menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Diagnostics'), 'glyphicon-screenshot', - 'diagnostics:index', 30) - - def index(request): """Serve the index page""" if request.method == 'POST' and not _running_task: _start_task() return TemplateResponse(request, 'diagnostics.html', - {'title': _('System Diagnostics'), + {'title': diagnostics.title, + 'description': diagnostics.description, 'is_running': _running_task is not None, 'results': current_results}) diff --git a/plinth/modules/diagnostics/templates/diagnostics.html b/plinth/modules/diagnostics/templates/diagnostics.html index 2b369732e..066222347 100644 --- a/plinth/modules/diagnostics/templates/diagnostics.html +++ b/plinth/modules/diagnostics/templates/diagnostics.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'app.html' %} {% comment %} # # This file is part of Plinth. @@ -29,17 +29,7 @@ {% endblock %} -{% block content %} - -

{{ title }}

- -

- {% blocktrans trimmed %} - The system diagnostic test will run a number of checks on your - system to confirm that applications and services are working as - expected. - {% endblocktrans %} -

+{% block configuration %} {% if not is_running %}
Date: Fri, 12 Feb 2016 14:54:57 +0530 Subject: [PATCH 19/45] dynamicdns: Use new setup mechanism --- plinth/modules/dynamicdns/__init__.py | 39 +++++++++++++++++-- plinth/modules/dynamicdns/dynamicdns.py | 16 ++------ .../dynamicdns/templates/dynamicdns.html | 29 +------------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/plinth/modules/dynamicdns/__init__.py b/plinth/modules/dynamicdns/__init__.py index 7b4bc398d..c47942f6e 100644 --- a/plinth/modules/dynamicdns/__init__.py +++ b/plinth/modules/dynamicdns/__init__.py @@ -19,9 +19,42 @@ Plinth module to configure ez-ipupdate client """ -from . import dynamicdns -from .dynamicdns import init +from django.utils.translation import ugettext_lazy as _ -__all__ = ['dynamicdns', 'init'] +from plinth import cfg +from plinth.utils import format_lazy + +version = 1 depends = ['apps'] + +title = _('Dynamic DNS Client') + +description = [ + format_lazy( + _('If your internet provider changes your IP address periodic ' + '(i.e. every 24h) it may be hard for others to find you in the ' + 'WEB. And for this reason nobody may find the services which are ' + 'provided by {box_name}, such as ownCloud.'), + box_name=_(cfg.box_name)), + + _('The solution is to assign a DNS name to your IP address and ' + 'update the DNS name every time your IP is changed by your ' + 'Internet provider. Dynamic DNS allows you to push your current ' + 'public IP address to an ' + ' ' + 'gnudip server. Afterwards the Server will assign your DNS name ' + 'with the new IP and if someone from the Internet asks for your DNS ' + 'name he will get your current IP answered.') +] + + +def init(): + """Initialize the module.""" + menu = cfg.main_menu.get('apps:index') + menu.add_urlname(title, 'glyphicon-refresh', 'dynamicdns:index', 500) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['ez-ipupdate']) diff --git a/plinth/modules/dynamicdns/dynamicdns.py b/plinth/modules/dynamicdns/dynamicdns.py index 490ec98c6..b15e49b59 100644 --- a/plinth/modules/dynamicdns/dynamicdns.py +++ b/plinth/modules/dynamicdns/dynamicdns.py @@ -25,7 +25,7 @@ import logging from plinth import actions from plinth import cfg -from plinth import package +from plinth.modules import dynamicdns from plinth.utils import format_lazy logger = logging.getLogger(__name__) @@ -39,19 +39,11 @@ subsubmenu = [{'url': reverse_lazy('dynamicdns:index'), 'text': ugettext_lazy('Status')}] -def init(): - """Initialize the dynamicdns module""" - menu = cfg.main_menu.get('apps:index') - menu.add_urlname(ugettext_lazy('Dynamic DNS'), 'glyphicon-refresh', - 'dynamicdns:index', 500) - - -@package.required(['ez-ipupdate']) def index(request): """Serve Dynamic DNS page.""" - return TemplateResponse(request, 'dynamicdns.html', - {'title': _('Dynamic DNS'), + {'title': dynamicdns.title, + 'description': dynamicdns.description, 'subsubmenu': subsubmenu}) @@ -198,7 +190,6 @@ class ConfigureForm(forms.Form): raise forms.ValidationError(_('Please provide a password')) -@package.required(['ez-ipupdate']) def configure(request): """Serve the configuration form.""" status = get_status() @@ -219,7 +210,6 @@ def configure(request): 'subsubmenu': subsubmenu}) -@package.required(['ez-ipupdate']) def statuspage(request): """Serve the status page.""" check_nat = actions.run('dynamicdns', ['get-nat']) diff --git a/plinth/modules/dynamicdns/templates/dynamicdns.html b/plinth/modules/dynamicdns/templates/dynamicdns.html index 5ad1ec90b..1b3d1bfa7 100644 --- a/plinth/modules/dynamicdns/templates/dynamicdns.html +++ b/plinth/modules/dynamicdns/templates/dynamicdns.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -20,32 +20,7 @@ {% load i18n %} -{% block content %} - -

{% trans "Dynamic DNS Client" %}

- -

- {% blocktrans trimmed %} - If your internet provider changes your IP address periodic - (i.e. every 24h) it may be hard for others to find you in the - WEB. And for this reason nobody may find the services which are - provided by {{ box_name }}, such as ownCloud. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - The solution is to assign a DNS name to your IP address and - update the DNS name every time your IP is changed by your - Internet provider. Dynamic DNS allows you to push your current - public IP address to an - gnudip - server. Afterwards the Server will assign your DNS name with the - new IP and if someone from the Internet asks for your DNS name - he will get your current IP answered. - {% endblocktrans %} -

- +{% block configuration %}

{% blocktrans trimmed %} If you are looking for a free dynamic DNS account, you may find From 38bf4d4549d4fb1b24bb5816e63d0282dc93ab6b Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 15:09:44 +0530 Subject: [PATCH 20/45] firewall: Use new setup mechanism - Also reorganize module to separate out views. --- plinth/modules/config/config.py | 2 +- plinth/modules/firewall/__init__.py | 116 ++++++++++++++- plinth/modules/firewall/firewall.py | 137 ------------------ .../modules/firewall/templates/firewall.html | 15 +- plinth/modules/firewall/urls.py | 2 +- plinth/modules/firewall/views.py | 45 ++++++ 6 files changed, 162 insertions(+), 155 deletions(-) delete mode 100644 plinth/modules/firewall/firewall.py create mode 100644 plinth/modules/firewall/views.py diff --git a/plinth/modules/config/config.py b/plinth/modules/config/config.py index f730f83d1..54c687c2b 100644 --- a/plinth/modules/config/config.py +++ b/plinth/modules/config/config.py @@ -33,7 +33,7 @@ import socket from plinth import actions from plinth import cfg -from plinth.modules.firewall import firewall +from plinth.modules import firewall from plinth.modules.names import SERVICES from plinth.signals import pre_hostname_change, post_hostname_change from plinth.signals import domainname_change diff --git a/plinth/modules/firewall/__init__.py b/plinth/modules/firewall/__init__.py index 3abc58c06..96cb449a1 100644 --- a/plinth/modules/firewall/__init__.py +++ b/plinth/modules/firewall/__init__.py @@ -19,9 +19,119 @@ Plinth module to configure a firewall """ -from . import firewall -from .firewall import init +from django.utils.translation import ugettext_lazy as _ +import logging -__all__ = ['firewall', 'init'] +from plinth import actions +from plinth import cfg +from plinth.signals import service_enabled +import plinth.service as service_module +from plinth.utils import format_lazy + +version = 1 + +is_essential = True depends = ['system'] + +title = _('Firewall') + +description = [ + format_lazy( + _('Firewall is a security system that controls the incoming and ' + 'outgoing network traffic on your {box_name}. Keeping a ' + 'firewall enabled and properly configured reduces risk of ' + 'security threat from the Internet.'), box_name=cfg.box_name) +] + +LOGGER = logging.getLogger(__name__) + + +def init(): + """Initailze firewall module""" + menu = cfg.main_menu.get('system:index') + menu.add_urlname(title, 'glyphicon-fire', 'firewall:index', 50) + + service_enabled.connect(on_service_enabled) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['firewalld']) + + +def get_enabled_status(): + """Return whether firewall is enabled""" + output = _run(['get-status'], superuser=True) + return output.split()[0] == 'running' + + +def get_enabled_services(zone): + """Return the status of various services currently enabled""" + output = _run(['get-enabled-services', '--zone', zone], superuser=True) + return output.split() + + +def add_service(port, zone): + """Enable a service in firewall""" + _run(['add-service', port, '--zone', zone], superuser=True) + + +def remove_service(port, zone): + """Remove a service in firewall""" + _run(['remove-service', port, '--zone', zone], superuser=True) + + +def on_service_enabled(sender, service_id, enabled, **kwargs): + """ + Enable/disable firewall ports when a service is + enabled/disabled. + """ + del sender # Unused + del kwargs # Unused + + internal_enabled_services = get_enabled_services(zone='internal') + external_enabled_services = get_enabled_services(zone='external') + + LOGGER.info('Service enabled - %s, %s', service_id, enabled) + service = service_module.services[service_id] + for port in service.ports: + if enabled: + if port not in internal_enabled_services: + add_service(port, zone='internal') + + if (service.is_external and + port not in external_enabled_services): + add_service(port, zone='external') + else: + # service already configured. + pass + else: + if port in internal_enabled_services: + enabled_services_on_port = [ + service_.is_enabled() + for service_ in service_module.services.values() + if port in service_.ports and + service_id != service_.service_id] + if not any(enabled_services_on_port): + remove_service(port, zone='internal') + + if port in external_enabled_services: + enabled_services_on_port = [ + service_.is_enabled() + for service_ in service_module.services.values() + if port in service_.ports and + service_id != service_.service_id and + service_.is_external] + if not any(enabled_services_on_port): + remove_service(port, zone='external') + + +def _run(arguments, superuser=False): + """Run an given command and raise exception if there was an error""" + command = 'firewall' + + if superuser: + return actions.superuser_run(command, arguments) + else: + return actions.run(command, arguments) diff --git a/plinth/modules/firewall/firewall.py b/plinth/modules/firewall/firewall.py deleted file mode 100644 index dc693af90..000000000 --- a/plinth/modules/firewall/firewall.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# This file is part of Plinth. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# - -""" -Plinth module to configure a firewall -""" - -from django.template.response import TemplateResponse -from django.utils.translation import ugettext_lazy as _ -import logging - -from plinth import actions -from plinth import cfg -from plinth import package -from plinth.signals import service_enabled -import plinth.service as service_module - - -LOGGER = logging.getLogger(__name__) - - -def init(): - """Initailze firewall module""" - menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Firewall'), 'glyphicon-fire', 'firewall:index', 50) - - service_enabled.connect(on_service_enabled) - - -@package.required(['firewalld']) -def index(request): - """Serve introcution page""" - if not get_enabled_status(): - return TemplateResponse(request, 'firewall.html', - {'title': _('Firewall'), - 'firewall_status': 'not_running'}) - - internal_enabled_services = get_enabled_services(zone='internal') - external_enabled_services = get_enabled_services(zone='external') - - return TemplateResponse( - request, 'firewall.html', - {'title': _('Firewall'), - 'services': list(service_module.services.values()), - 'internal_enabled_services': internal_enabled_services, - 'external_enabled_services': external_enabled_services}) - - -def get_enabled_status(): - """Return whether firewall is enabled""" - output = _run(['get-status'], superuser=True) - return output.split()[0] == 'running' - - -def get_enabled_services(zone): - """Return the status of various services currently enabled""" - output = _run(['get-enabled-services', '--zone', zone], superuser=True) - return output.split() - - -def add_service(port, zone): - """Enable a service in firewall""" - _run(['add-service', port, '--zone', zone], superuser=True) - - -def remove_service(port, zone): - """Remove a service in firewall""" - _run(['remove-service', port, '--zone', zone], superuser=True) - - -def on_service_enabled(sender, service_id, enabled, **kwargs): - """ - Enable/disable firewall ports when a service is - enabled/disabled. - """ - del sender # Unused - del kwargs # Unused - - internal_enabled_services = get_enabled_services(zone='internal') - external_enabled_services = get_enabled_services(zone='external') - - LOGGER.info('Service enabled - %s, %s', service_id, enabled) - service = service_module.services[service_id] - for port in service.ports: - if enabled: - if port not in internal_enabled_services: - add_service(port, zone='internal') - - if (service.is_external and - port not in external_enabled_services): - add_service(port, zone='external') - else: - # service already configured. - pass - else: - if port in internal_enabled_services: - enabled_services_on_port = [ - service_.is_enabled() - for service_ in service_module.services.values() - if port in service_.ports and - service_id != service_.service_id] - if not any(enabled_services_on_port): - remove_service(port, zone='internal') - - if port in external_enabled_services: - enabled_services_on_port = [ - service_.is_enabled() - for service_ in service_module.services.values() - if port in service_.ports and - service_id != service_.service_id and - service_.is_external] - if not any(enabled_services_on_port): - remove_service(port, zone='external') - - -def _run(arguments, superuser=False): - """Run an given command and raise exception if there was an error""" - command = 'firewall' - - if superuser: - return actions.superuser_run(command, arguments) - else: - return actions.run(command, arguments) diff --git a/plinth/modules/firewall/templates/firewall.html b/plinth/modules/firewall/templates/firewall.html index e59d4052d..ec2440702 100644 --- a/plinth/modules/firewall/templates/firewall.html +++ b/plinth/modules/firewall/templates/firewall.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -20,18 +20,7 @@ {% load i18n %} -{% block content %} - -

{{ title }}

- -

- {% blocktrans trimmed %} - Firewall is a security system that controls the incoming and - outgoing network traffic on your {{ box_name }}. Keeping a - firewall enabled and properly configured reduces risk of - security threat from the Internet. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Current status:" %}

diff --git a/plinth/modules/firewall/urls.py b/plinth/modules/firewall/urls.py index 6448e89f6..8c6765af9 100644 --- a/plinth/modules/firewall/urls.py +++ b/plinth/modules/firewall/urls.py @@ -21,7 +21,7 @@ URLs for the Firewall module from django.conf.urls import url -from . import firewall as views +from . import views urlpatterns = [ diff --git a/plinth/modules/firewall/views.py b/plinth/modules/firewall/views.py new file mode 100644 index 000000000..3dab988ab --- /dev/null +++ b/plinth/modules/firewall/views.py @@ -0,0 +1,45 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Plinth module to configure a firewall +""" + +from django.template.response import TemplateResponse + +from plinth.modules import firewall +import plinth.service as service_module + + +def index(request): + """Serve introcution page""" + if not firewall.get_enabled_status(): + return TemplateResponse(request, 'firewall.html', + {'title': firewall.title, + 'description': firewall.description, + 'firewall_status': 'not_running'}) + + internal_enabled_services = firewall.get_enabled_services(zone='internal') + external_enabled_services = firewall.get_enabled_services(zone='external') + + return TemplateResponse( + request, 'firewall.html', + {'title': firewall.title, + 'description': firewall.description, + 'services': list(service_module.services.values()), + 'internal_enabled_services': internal_enabled_services, + 'external_enabled_services': external_enabled_services}) From cceddf5c0ae374fa74a3280041adfb6269572259 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 15:29:47 +0530 Subject: [PATCH 21/45] ikiwiki: Use new setup mechanism --- plinth/modules/ikiwiki/__init__.py | 30 ++++++++++++++++--- plinth/modules/ikiwiki/templates/ikiwiki.html | 11 ++----- plinth/modules/ikiwiki/views.py | 19 ++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py index 8830af010..05f105743 100644 --- a/plinth/modules/ikiwiki/__init__.py +++ b/plinth/modules/ikiwiki/__init__.py @@ -21,26 +21,48 @@ Plinth module to configure ikiwiki from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('Wiki and Blog (ikiwiki)') + +description = [ + _('When enabled, the blogs and wikis will be available ' + 'from /ikiwiki.') +] + service = None def init(): """Initialize the ikiwiki module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Wiki and Blog (ikiwiki)'), 'glyphicon-edit', - 'ikiwiki:index', 1100) + menu.add_urlname(title, 'glyphicon-edit', 'ikiwiki:index', 1100) global service service = service_module.Service( - 'ikiwiki', _('ikiwiki wikis and blogs'), ['http', 'https'], - is_external=True, enabled=is_enabled()) + 'ikiwiki', title, ['http', 'https'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['ikiwiki', + 'gcc', + 'libc6-dev', + 'libtimedate-perl', + 'libcgi-formbuilder-perl', + 'libcgi-session-perl', + 'libxml-writer-perl']) + helper.call('post', actions.superuser_run, 'ikiwiki', ['setup']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/ikiwiki/templates/ikiwiki.html b/plinth/modules/ikiwiki/templates/ikiwiki.html index 4cea868ad..e08738cf7 100644 --- a/plinth/modules/ikiwiki/templates/ikiwiki.html +++ b/plinth/modules/ikiwiki/templates/ikiwiki.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,14 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

- {% blocktrans trimmed %} - When enabled, the blogs and wikis will be available - from /ikiwiki. - {% endblocktrans %} -

+{% block configuration %} {% include "diagnostics_button.html" with module="ikiwiki" %} diff --git a/plinth/modules/ikiwiki/views.py b/plinth/modules/ikiwiki/views.py index 37bfaf5c3..27c1a855a 100644 --- a/plinth/modules/ikiwiki/views.py +++ b/plinth/modules/ikiwiki/views.py @@ -27,8 +27,6 @@ from django.utils.translation import ugettext as _, ugettext_lazy from .forms import IkiwikiForm, IkiwikiCreateForm from plinth import actions -from plinth import action_utils -from plinth import package from plinth.modules import ikiwiki @@ -40,20 +38,6 @@ subsubmenu = [{'url': reverse_lazy('ikiwiki:index'), 'text': ugettext_lazy('Create')}] -def on_install(): - """Enable ikiwiki on install.""" - actions.superuser_run('ikiwiki', ['setup']) - ikiwiki.service.notify_enabled(None, True) - - -@package.required(['ikiwiki', - 'gcc', - 'libc6-dev', - 'libtimedate-perl', - 'libcgi-formbuilder-perl', - 'libcgi-session-perl', - 'libxml-writer-perl'], - on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -70,7 +54,8 @@ def index(request): form = IkiwikiForm(initial=status, prefix='ikiwiki') return TemplateResponse(request, 'ikiwiki.html', - {'title': _('Wiki and Blog'), + {'title': ikiwiki.title, + 'description': ikiwiki.description, 'status': status, 'form': form, 'subsubmenu': subsubmenu}) From cef8f5c2c4a6ba27468ab2ebe1ebcbe9eeefaea0 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 15:37:02 +0530 Subject: [PATCH 22/45] letsencrypt: Use new setup mechanism --- plinth/modules/letsencrypt/__init__.py | 29 +++++++++++++++++++ .../letsencrypt/templates/letsencrypt.html | 27 ++--------------- plinth/modules/letsencrypt/views.py | 6 ++-- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 1375ed176..bfd630c16 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -24,10 +24,34 @@ from django.utils.translation import ugettext_lazy as _ from plinth import action_utils from plinth import cfg from plinth.modules import names +from plinth.utils import format_lazy +version = 1 + +is_essential = True + depends = ['apps', 'names'] +title = _('Certificates (Let\'s Encrypt)') + +description = [ + format_lazy( + _('A digital certficate allows users of a web service to verify the ' + 'identity of the service and to securely communicate with it. ' + '{box_name} can automatically obtain and setup digital ' + 'certificates for each available domain. It does so by proving ' + 'itself to be the owner of a domain to Let\'s Encrypt, a ' + 'certficate authority (CA).'), box_name=_(cfg.box_name)), + + _('Let\'s Encrypt is a free, automated, and open certificate ' + 'authority, run for the public’s benefit by the Internet Security ' + 'Research Group (ISRG). Please read and agree with the ' + 'Let\'s Encrypt ' + 'Subscriber Agreement before using this service.') +] + + service = None @@ -38,6 +62,11 @@ def init(): 'glyphicon-lock', 'letsencrypt:index', 20) +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['letsencrypt']) + + def diagnose(): """Run diagnostics and return the results.""" results = [] diff --git a/plinth/modules/letsencrypt/templates/letsencrypt.html b/plinth/modules/letsencrypt/templates/letsencrypt.html index 15043bdf2..1e3e29b2a 100644 --- a/plinth/modules/letsencrypt/templates/letsencrypt.html +++ b/plinth/modules/letsencrypt/templates/letsencrypt.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -33,30 +33,7 @@ {% endblock %} -{% block content %} - -

{% trans "Certificates (Let's Encrypt)" %}

- -

- {% blocktrans trimmed %} - A digital certficate allows users of a web service to verify the - identity of the service and to securely communicate with it. - {{ box_name }} can automatically obtain and setup digital - certificates for each available domain. It does so by proving - itself to be the owner of a domain to Let's Encrypt, a - certficate authority (CA). - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - Let's Encrypt is a free, automated, and open certificate - authority, run for the public’s benefit by the Internet Security - Research Group (ISRG). Please read and agree with the - Let's Encrypt - Subscriber Agreement before using this service. - {% endblocktrans %} -

+{% block configuration %}
diff --git a/plinth/modules/letsencrypt/views.py b/plinth/modules/letsencrypt/views.py index eb8660759..ecbdd5ffd 100644 --- a/plinth/modules/letsencrypt/views.py +++ b/plinth/modules/letsencrypt/views.py @@ -29,20 +29,20 @@ import json import logging from plinth import actions -from plinth import package from plinth.errors import ActionError +from plinth.modules import letsencrypt from plinth.modules import names logger = logging.getLogger(__name__) -@package.required(['letsencrypt']) def index(request): """Serve configuration page.""" status = get_status() return TemplateResponse(request, 'letsencrypt.html', - {'title': _('Certificates (Let\'s Encrypt)'), + {'title': letsencrypt.title, + 'description': letsencrypt.description, 'status': status}) From 608d19dc2b57c1591b11007d0b2b3bd0c411b4be Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 15:53:42 +0530 Subject: [PATCH 23/45] monkeysphere: Use new setup mechanism --- plinth/modules/monkeysphere/__init__.py | 20 +++++++++++++++++++ .../monkeysphere/templates/monkeysphere.html | 19 ++---------------- plinth/modules/monkeysphere/views.py | 6 +++--- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/plinth/modules/monkeysphere/__init__.py b/plinth/modules/monkeysphere/__init__.py index b7a8329f3..09be4a195 100644 --- a/plinth/modules/monkeysphere/__init__.py +++ b/plinth/modules/monkeysphere/__init__.py @@ -23,11 +23,31 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg +version = 1 + depends = ['system'] +title = _('Monkeysphere') + +description = [ + _('With Monkeysphere, a PGP key can be generated for each configured ' + 'domain serving SSH. The PGP public key can then be uploaded to the PGP ' + 'keyservers. Users connecting to this machine through SSH can verify ' + 'that they are connecting to the correct host. For users to trust the ' + 'key, at least one person (usually the machine owner) must sign the key ' + 'using the regular PGP key signing process. See the ' + ' ' + 'Monkeysphere SSH documentation for more details.') +] + def init(): """Initialize the monkeysphere module.""" menu = cfg.main_menu.get('system:index') menu.add_urlname(_('Monkeysphere'), 'glyphicon-certificate', 'monkeysphere:index', 970) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['monkeysphere']) diff --git a/plinth/modules/monkeysphere/templates/monkeysphere.html b/plinth/modules/monkeysphere/templates/monkeysphere.html index 2e67c5204..ebe048590 100644 --- a/plinth/modules/monkeysphere/templates/monkeysphere.html +++ b/plinth/modules/monkeysphere/templates/monkeysphere.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -30,22 +30,7 @@ {% endblock %} -{% block content %} - -

{% trans "Monkeysphere" %}

- -

- {% blocktrans trimmed %} - With Monkeysphere, a PGP key can be generated for each configured domain - serving SSH. The PGP public key can then be uploaded to the PGP - keyservers. Users connecting to this machine through SSH can verify that - they are connecting to the correct host. For users to trust the key, at - least one person (usually the machine owner) must sign the key using the - regular PGP key signing process. See the - - Monkeysphere SSH documentation for more details. - {% endblocktrans %} -

+{% block configuration %} {% if running %}

diff --git a/plinth/modules/monkeysphere/views.py b/plinth/modules/monkeysphere/views.py index 777c64b25..2ce31d22d 100644 --- a/plinth/modules/monkeysphere/views.py +++ b/plinth/modules/monkeysphere/views.py @@ -28,20 +28,20 @@ from django.views.decorators.http import require_POST import json from plinth import actions -from plinth import package +from plinth.modules import monkeysphere from plinth.modules import names publish_process = None -@package.required(['monkeysphere']) def index(request): """Serve configuration page.""" _collect_publish_result(request) status = get_status() return TemplateResponse( request, 'monkeysphere.html', - {'title': _('Monkeysphere'), + {'title': monkeysphere.title, + 'description': monkeysphere.description, 'status': status, 'running': bool(publish_process)}) From 1f8ccb6c933bb46f5e20dfaf410bda54147b027c Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 15:54:03 +0530 Subject: [PATCH 24/45] monkeysphere: Fix error with no host keys --- actions/monkeysphere | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actions/monkeysphere b/actions/monkeysphere index 6a8b0fc75..31ad6f937 100755 --- a/actions/monkeysphere +++ b/actions/monkeysphere @@ -53,9 +53,11 @@ def subcommand_host_show_keys(arguments): """Show host key fingerprints.""" try: output = subprocess.check_output( - ['monkeysphere-host', 'show-keys'] + arguments.key_ids) + ['monkeysphere-host', 'show-keys'] + arguments.key_ids, + stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: # no keys available + print(json.dumps({'keys': []})) return # parse output From 41e730d7acae2c06c0b0150ef4019e3b6846110a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:01:40 +0530 Subject: [PATCH 25/45] mumble: Use new setup mechanism --- plinth/modules/mumble/__init__.py | 25 +++++++++++++++++---- plinth/modules/mumble/templates/mumble.html | 22 ++---------------- plinth/modules/mumble/views.py | 10 ++------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py index 846681b29..b7f772494 100644 --- a/plinth/modules/mumble/__init__.py +++ b/plinth/modules/mumble/__init__.py @@ -26,21 +26,38 @@ from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('Voice Chat (Mumble)') + +description = [ + _('Mumble is an open source, low-latency, encrypted, high quality ' + 'voice chat software.'), + + _('You can connect to your Mumble server on the regular Mumble port ' + '64738. Clients to connect to Mumble ' + 'from your desktop and Android devices are available.') +] + service = None def init(): """Intialize the Mumble module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Voice Chat (Mumble)'), 'glyphicon-headphones', - 'mumble:index', 900) + menu.add_urlname(title, 'glyphicon-headphones', 'mumble:index', 900) global service service = service_module.Service( - 'mumble-plinth', _('Mumble Voice Chat Server'), - is_external=True, enabled=is_enabled()) + 'mumble-plinth', title, is_external=True, enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['mumble-server']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/mumble/templates/mumble.html b/plinth/modules/mumble/templates/mumble.html index 8f9d1d725..96745dfd8 100644 --- a/plinth/modules/mumble/templates/mumble.html +++ b/plinth/modules/mumble/templates/mumble.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,25 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Voice Chat (Mumble)" %}

- -

- {% blocktrans trimmed %} - Mumble is an open source, low-latency, encrypted, high quality - voice chat software. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - You can connect to your Mumble server on the regular Mumble port 64738. - Clients to connect to Mumble - from your desktop and Android devices are available. - {% endblocktrans %} -

- +{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/mumble/views.py b/plinth/modules/mumble/views.py index 3ecadd7cc..4d67f6458 100644 --- a/plinth/modules/mumble/views.py +++ b/plinth/modules/mumble/views.py @@ -26,18 +26,11 @@ import logging from .forms import MumbleForm from plinth import actions -from plinth import package from plinth.modules import mumble logger = logging.getLogger(__name__) -def on_install(): - """Notify that the service is now enabled.""" - mumble.service.notify_enabled(None, True) - - -@package.required(['mumble-server'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -55,7 +48,8 @@ def index(request): form = MumbleForm(initial=status, prefix='mumble') return TemplateResponse(request, 'mumble.html', - {'title': _('Voice Chat (Mumble)'), + {'title': mumble.title, + 'description': mumble.description, 'status': status, 'form': form}) From 0568bf259cf595cf19eddd7a942008d7b239241e Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:05:28 +0530 Subject: [PATCH 26/45] names: Use new setup mechanism --- plinth/modules/names/__init__.py | 10 ++++++++-- plinth/modules/names/templates/names.html | 6 ++---- plinth/modules/names/views.py | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/plinth/modules/names/__init__.py b/plinth/modules/names/__init__.py index 8f9fd8488..d2c5e9565 100644 --- a/plinth/modules/names/__init__.py +++ b/plinth/modules/names/__init__.py @@ -31,8 +31,14 @@ SERVICES = ( ('ssh', _('SSH'), 22), ) +version = 1 + +is_essential = True + depends = ['system'] +title = _('Name Services') + domain_types = {} domains = {} @@ -42,8 +48,7 @@ logger = logging.getLogger(__name__) def init(): """Initialize the names module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Name Services'), 'glyphicon-tag', - 'names:index', 19) + menu.add_urlname(title, 'glyphicon-tag', 'names:index', 19) domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) @@ -54,6 +59,7 @@ def on_domain_added(sender, domain_type, name='', description='', """Add domain to global list.""" if not domain_type: return + domain_types[domain_type] = description if not name: diff --git a/plinth/modules/names/templates/names.html b/plinth/modules/names/templates/names.html index fffc51897..bbdc0107b 100644 --- a/plinth/modules/names/templates/names.html +++ b/plinth/modules/names/templates/names.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,9 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{{ title }}

+{% block configuration %}
diff --git a/plinth/modules/names/views.py b/plinth/modules/names/views.py index c95ea0b00..d8daecd1f 100644 --- a/plinth/modules/names/views.py +++ b/plinth/modules/names/views.py @@ -24,6 +24,7 @@ from django.utils.translation import ugettext as _ from . import SERVICES, get_domain_types, get_description from . import get_domain, get_services_status +from plinth.modules import names def index(request): @@ -31,7 +32,7 @@ def index(request): status = get_status() return TemplateResponse(request, 'names.html', - {'title': _('Name Services'), + {'title': names.title, 'status': status}) From 335eeccee95b625988bebaead42e325acbe1ee08 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:10:06 +0530 Subject: [PATCH 27/45] networks: Use new setup mechanism --- plinth/modules/networks/__init__.py | 28 ++++++++++++++++++++++------ plinth/modules/networks/networks.py | 10 ---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/plinth/modules/networks/__init__.py b/plinth/modules/networks/__init__.py index c3c93a17b..d72425a19 100644 --- a/plinth/modules/networks/__init__.py +++ b/plinth/modules/networks/__init__.py @@ -19,23 +19,37 @@ Plinth module to interface with network-manager """ -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from logging import Logger import subprocess -from . import networks -from .networks import init from plinth import action_utils +from plinth import cfg from plinth import network -__all__ = ['networks', 'init'] +version = 1 + +is_essential = True depends = ['system'] +title = _('Networks') + logger = Logger(__name__) +def init(): + """Initialize the Networks module.""" + menu = cfg.main_menu.get('system:index') + menu.add_urlname(title, 'glyphicon-signal', 'networks:index', 18) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['network-manager']) + + def diagnose(): """Run diagnostics and return the results.""" results = [] @@ -44,8 +58,10 @@ def diagnose(): addresses = _get_interface_addresses(interfaces) for address in addresses: - results.append(action_utils.diagnose_port_listening(53, 'tcp', address)) - results.append(action_utils.diagnose_port_listening(53, 'udp', address)) + results.append( + action_utils.diagnose_port_listening(53, 'tcp', address)) + results.append( + action_utils.diagnose_port_listening(53, 'udp', address)) results.append(_diagnose_dnssec('4')) results.append(_diagnose_dnssec('6')) diff --git a/plinth/modules/networks/networks.py b/plinth/modules/networks/networks.py index 88034ee9f..522b08aa1 100644 --- a/plinth/modules/networks/networks.py +++ b/plinth/modules/networks/networks.py @@ -25,9 +25,7 @@ from logging import Logger from .forms import (ConnectionTypeSelectForm, EthernetForm, PPPoEForm, WifiForm) -from plinth import cfg from plinth import network -from plinth import package logger = Logger(__name__) @@ -40,14 +38,6 @@ subsubmenu = [{'url': reverse_lazy('networks:index'), 'text': ugettext_lazy('Add Connection')}] -def init(): - """Initialize the Networks module.""" - menu = cfg.main_menu.get('system:index') - menu.add_urlname(ugettext_lazy('Networks'), 'glyphicon-signal', - 'networks:index', 18) - - -@package.required(['network-manager']) def index(request): """Show connection list.""" connections = network.get_connection_list() From b916d95a0b3f15efb8c2b67f6a761783eddebae7 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:14:59 +0530 Subject: [PATCH 28/45] openvpn: Use new setup mechanism --- plinth/modules/openvpn/__init__.py | 27 ++++++++++++++++--- plinth/modules/openvpn/templates/openvpn.html | 18 ++----------- plinth/modules/openvpn/views.py | 5 ++-- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py index 688b1507f..c035abb99 100644 --- a/plinth/modules/openvpn/__init__.py +++ b/plinth/modules/openvpn/__init__.py @@ -25,23 +25,42 @@ from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +from plinth.utils import format_lazy +version = 1 + depends = ['apps'] +title = _('Virtual Private Network (OpenVPN)') + +description = [ + format_lazy( + _('Virtual Private Network (VPN) is a technique for securely ' + 'connecting two devices in order to access resources of a ' + 'private network. While you are away from home, you can connect ' + 'to your {box_name} in order to join your home network and ' + 'access private/internal services provided by {box_name}. ' + 'You can also access the rest of the Internet via {box_name} ' + 'for added security and anonymity.'), box_name=_(cfg.box_name)) +] + service = None def init(): """Intialize the OpenVPN module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Virtual Private Network (OpenVPN)'), 'glyphicon-lock', - 'openvpn:index', 850) + menu.add_urlname(title, 'glyphicon-lock', 'openvpn:index', 850) global service service = service_module.Service( - 'openvpn', _('OpenVPN'), ['openvpn'], - is_external=True, enabled=is_enabled()) + 'openvpn', title, ['openvpn'], is_external=True, enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['openvpn', 'easy-rsa']) def is_enabled(): diff --git a/plinth/modules/openvpn/templates/openvpn.html b/plinth/modules/openvpn/templates/openvpn.html index d2ff0c5d8..422a9e69f 100644 --- a/plinth/modules/openvpn/templates/openvpn.html +++ b/plinth/modules/openvpn/templates/openvpn.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -30,21 +30,7 @@ {% endblock %} -{% block content %} - -

{% trans "Virtual Private Network (OpenVPN)" %}

- -

- {% blocktrans trimmed %} - Virtual Private Network (VPN) is a technique for securely - connecting two devices in order to access resources of a - private network. While you are away from home, you can connect - to your {{ box_name }} in order to join your home network and - access private/internal services provided by {{ box_name }}. - You can also access the rest of the Internet via {{ box_name }} - for added security and anonymity. - {% endblocktrans %} -

+{% block configuration %} {% if status.is_setup %} diff --git a/plinth/modules/openvpn/views.py b/plinth/modules/openvpn/views.py index f4cd85ed8..ea1c6aefa 100644 --- a/plinth/modules/openvpn/views.py +++ b/plinth/modules/openvpn/views.py @@ -29,7 +29,6 @@ import logging from .forms import OpenVpnForm from plinth import actions -from plinth import package from plinth.modules import openvpn from plinth.modules.config import config @@ -38,7 +37,6 @@ logger = logging.getLogger(__name__) setup_process = None -@package.required(['openvpn', 'easy-rsa']) def index(request): """Serve configuration page.""" status = get_status() @@ -59,7 +57,8 @@ def index(request): form = OpenVpnForm(initial=status, prefix='openvpn') return TemplateResponse(request, 'openvpn.html', - {'title': _('Virtual Private Network (OpenVPN)'), + {'title': openvpn.title, + 'description': openvpn.description, 'status': status, 'form': form}) From 166ff9b5bf05db525c3a5e3d166c52fcfb21f787 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:27:12 +0530 Subject: [PATCH 29/45] owncloud: Use new setup mechanism - Also reorganize views. --- plinth/modules/owncloud/__init__.py | 51 +++++++++++++++++-- plinth/modules/owncloud/forms.py | 30 +++++++++++ .../modules/owncloud/templates/owncloud.html | 27 +--------- plinth/modules/owncloud/urls.py | 2 +- .../owncloud/{owncloud.py => views.py} | 44 +++------------- 5 files changed, 87 insertions(+), 67 deletions(-) create mode 100644 plinth/modules/owncloud/forms.py rename plinth/modules/owncloud/{owncloud.py => views.py} (63%) diff --git a/plinth/modules/owncloud/__init__.py b/plinth/modules/owncloud/__init__.py index 543909e21..29d77934e 100644 --- a/plinth/modules/owncloud/__init__.py +++ b/plinth/modules/owncloud/__init__.py @@ -19,15 +19,60 @@ Plinth module to configure ownCloud """ -from . import owncloud -from .owncloud import init +from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import action_utils +from plinth import cfg +from plinth import service as service_module -__all__ = ['owncloud', 'init'] +version = 1 depends = ['apps'] +title = _('File Hosting (ownCloud)') + +description = [ + _('ownCloud gives you universal access to your files through a web ' + 'interface or WebDAV. It also provides a platform to easily view ' + '& sync your contacts, calendars and bookmarks across all your ' + 'devices and enables basic editing right on the web. Installation ' + 'has minimal server requirements, doesn\'t need special ' + 'permissions and is quick. ownCloud is extendable via a simple ' + 'but powerful API for applications and plugins.'), + + _('When enabled, the ownCloud installation will be available ' + 'from /owncloud path on the web server. ' + 'Visit this URL to set up the initial administration account for ' + 'ownCloud.') +] + +service = None + + +def init(): + """Initialize the ownCloud module""" + menu = cfg.main_menu.get('apps:index') + menu.add_urlname(title, 'glyphicon-picture', 'owncloud:index', 700) + + global service + service = service_module.Service( + 'owncloud', title, ['http', 'https'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['postgresql', 'php5-pgsql', 'owncloud']) + helper.call('post', actions.superuser_run, 'owncloud-setup', ['enable']) + helper.call('post', service.notify_enabled, None, True) + + +def is_enabled(): + """Return whether the module is enabled.""" + output = actions.run('owncloud-setup', ['status']) + return 'enable' in output.split() + def diagnose(): """Run diagnostics and return the results.""" diff --git a/plinth/modules/owncloud/forms.py b/plinth/modules/owncloud/forms.py new file mode 100644 index 000000000..3ea4c6f8c --- /dev/null +++ b/plinth/modules/owncloud/forms.py @@ -0,0 +1,30 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Forms for configuring ownCloud. +""" + +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232 + """ownCloud configuration form""" + enabled = forms.BooleanField( + label=_('Enable ownCloud'), + required=False) diff --git a/plinth/modules/owncloud/templates/owncloud.html b/plinth/modules/owncloud/templates/owncloud.html index 410071f08..266967d3d 100644 --- a/plinth/modules/owncloud/templates/owncloud.html +++ b/plinth/modules/owncloud/templates/owncloud.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,30 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "File Hosting (ownCloud)" %}

- -

- {% blocktrans trimmed %} - ownCloud gives you universal access to your files through a web - interface or WebDAV. It also provides a platform to easily view - & sync your contacts, calendars and bookmarks across all your - devices and enables basic editing right on the web. Installation - has minimal server requirements, doesn't need special - permissions and is quick. ownCloud is extendable via a simple - but powerful API for applications and plugins. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - When enabled, the ownCloud installation will be available - from /owncloud path on the web server. - Visit this URL to set up the initial administration account for - ownCloud. - {% endblocktrans %} -

+{% block configuration %} {% include "diagnostics_button.html" with module="owncloud" %} diff --git a/plinth/modules/owncloud/urls.py b/plinth/modules/owncloud/urls.py index d69ac14ef..338ad6cc5 100644 --- a/plinth/modules/owncloud/urls.py +++ b/plinth/modules/owncloud/urls.py @@ -21,7 +21,7 @@ URLs for the ownCloud module from django.conf.urls import url -from . import owncloud as views +from . import views urlpatterns = [ diff --git a/plinth/modules/owncloud/owncloud.py b/plinth/modules/owncloud/views.py similarity index 63% rename from plinth/modules/owncloud/owncloud.py rename to plinth/modules/owncloud/views.py index 249054dbb..b2e77077b 100644 --- a/plinth/modules/owncloud/owncloud.py +++ b/plinth/modules/owncloud/views.py @@ -19,47 +19,15 @@ Plinth module for configuring ownCloud. """ -from django import forms from django.contrib import messages from django.template.response import TemplateResponse from django.utils.translation import ugettext_lazy as _ +from .forms import OwnCloudForm from plinth import actions -from plinth import cfg -from plinth import package -from plinth import service as service_module +from plinth.modules import owncloud -service = None - - -class OwnCloudForm(forms.Form): # pylint: disable-msg=W0232 - """ownCloud configuration form""" - enabled = forms.BooleanField(label=_('Enable ownCloud'), required=False) - - -def init(): - """Initialize the ownCloud module""" - menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('File Hosting (ownCloud)'), 'glyphicon-picture', - 'owncloud:index', 700) - - status = get_status() - - global service # pylint: disable-msg=W0603 - service = service_module.Service( - 'owncloud', _('ownCloud'), ['http', 'https'], is_external=True, - enabled=status['enabled']) - - -def on_install(): - """Tasks to run after package install.""" - actions.superuser_run('owncloud-setup', ['enable']) - service.notify_enabled(None, True) - - -@package.required(['postgresql', 'php5-pgsql', 'owncloud'], - on_install=on_install) def index(request): """Serve the ownCloud configuration page""" status = get_status() @@ -77,14 +45,14 @@ def index(request): form = OwnCloudForm(initial=status, prefix='owncloud') return TemplateResponse(request, 'owncloud.html', - {'title': _('ownCloud'), + {'title': owncloud.title, + 'description': owncloud.description, 'form': form}) def get_status(): """Return the current status""" - output = actions.run('owncloud-setup', ['status']) - return {'enabled': 'enable' in output.split()} + return {'enabled': owncloud.is_enabled()} def _apply_changes(request, old_status, new_status): @@ -104,4 +72,4 @@ def _apply_changes(request, old_status, new_status): # Send a signal to other modules that the service is # enabled/disabled - service.notify_enabled(None, new_status['enabled']) + owncloud.service.notify_enabled(None, new_status['enabled']) From 5e094934b044b3cdb0f5f3e04add50b5d9c56297 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:51:02 +0530 Subject: [PATCH 30/45] pagekite: Use new setup mechanism --- plinth/modules/pagekite/__init__.py | 46 +++++++++++++++- .../templates/pagekite_introduction.html | 55 +------------------ plinth/modules/pagekite/views.py | 13 ++--- 3 files changed, 51 insertions(+), 63 deletions(-) diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py index 7e5f689ba..6862c1b68 100644 --- a/plinth/modules/pagekite/__init__.py +++ b/plinth/modules/pagekite/__init__.py @@ -21,19 +21,59 @@ Plinth module to configure PageKite from django.utils.translation import ugettext_lazy as _ from plinth import cfg +from plinth.utils import format_lazy from . import utils -__all__ = ['init'] +version = 1 depends = ['apps', 'names'] +title = _('Public Visibility (PageKite)') + +description = [ + format_lazy( + _('PageKite is a system for exposing {box_name} services when ' + 'you don\'t have a direct connection to the Internet. You only ' + 'need this if your {box_name} services are unreachable from ' + 'the rest of the Internet. This includes the following ' + 'situations:'), box_name=_(cfg.box_name)), + + format_lazy( + _('{box_name} is behind a restricted firewall.'), + box_name=_(cfg.box_name)), + + format_lazy( + _('{box_name} is connected to a (wireless) router which you ' + 'don\'t control.'), box_name=_(cfg.box_name)), + + _('Your ISP does not provide you an external IP address and ' + 'instead provides Internet connection through NAT.'), + + _('Your ISP does not provide you a static IP address and your IP ' + 'address changes evertime you connect to Internet.'), + + _('Your ISP limits incoming connections.'), + + format_lazy( + _('PageKite works around NAT, firewalls and IP-address limitations ' + 'by using a combination of tunnels and reverse proxies. You can ' + 'use any pagekite service provider, for example ' + 'pagekite.net. In future it ' + 'might be possible to use your buddy\'s {box_name} for this.'), + box_name=_(cfg.box_name)) +] + def init(): """Intialize the PageKite module""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Public Visibility (PageKite)'), - 'glyphicon-flag', 'pagekite:index', 800) + menu.add_urlname(title, 'glyphicon-flag', 'pagekite:index', 800) # Register kite name with Name Services module. utils.update_names_module(initial_registration=True) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['pagekite']) diff --git a/plinth/modules/pagekite/templates/pagekite_introduction.html b/plinth/modules/pagekite/templates/pagekite_introduction.html index 92682064d..0961c1923 100644 --- a/plinth/modules/pagekite/templates/pagekite_introduction.html +++ b/plinth/modules/pagekite/templates/pagekite_introduction.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -20,58 +20,7 @@ {% load i18n %} -{% block content %} - -

- {% blocktrans trimmed %} - PageKite is a system for exposing {{ box_name }} services when - you don't have a direct connection to the Internet. You only - need this if your {{ box_name }} services are unreachable from - the rest of the Internet. This includes the following - situations: - {% endblocktrans %} -

- -
    -
  • - {% blocktrans trimmed %} - {{ box_name }} is behind a restricted firewall. - {% endblocktrans %} -
  • - -
  • - {% blocktrans trimmed %} - {{ box_name }} is connected to a (wireless) router which you - don't control. - {% endblocktrans %} -
  • - -
  • - {% blocktrans trimmed %} - Your ISP does not provide you an external IP address and - instead provides Internet connection through NAT. - {% endblocktrans %} -
  • - -
  • - {% blocktrans trimmed %} - Your ISP does not provide you a static IP address and your IP - address changes evertime you connect to Internet. - {% endblocktrans %} -
  • - -
  • {% trans "Your ISP limits incoming connections." %}
  • -
- -

- {% blocktrans trimmed %} - PageKite works around NAT, firewalls and IP-address limitations - by using a combination of tunnels and reverse proxies. You can - use any pagekite service provider, for example - pagekite.net. In future it - might be possible to use your buddy's {{ box_name }} for this. - {% endblocktrans %} -

+{% block configuration %}

diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py index 99fe0fd46..583f072dc 100644 --- a/plinth/modules/pagekite/views.py +++ b/plinth/modules/pagekite/views.py @@ -18,18 +18,16 @@ from django.core.urlresolvers import reverse, reverse_lazy from django.http.response import HttpResponseRedirect from django.template.response import TemplateResponse -from django.utils.decorators import method_decorator from django.utils.translation import ugettext_lazy as _ from django.views.generic import View, TemplateView from django.views.generic.edit import FormView -from plinth import package from . import utils from .forms import ConfigurationForm, StandardServiceForm, \ AddCustomServiceForm, DeleteCustomServiceForm +from plinth.modules import pagekite -required_packages = ('pagekite',) subsubmenu = [{'url': reverse_lazy('pagekite:index'), 'text': _('About PageKite')}, {'url': reverse_lazy('pagekite:configure'), @@ -43,7 +41,8 @@ subsubmenu = [{'url': reverse_lazy('pagekite:index'), def index(request): """Serve introduction page""" return TemplateResponse(request, 'pagekite_introduction.html', - {'title': _('Public Visibility (PageKite)'), + {'title': pagekite.title, + 'description': pagekite.description, 'subsubmenu': subsubmenu}) @@ -59,7 +58,6 @@ class ContextMixin(object): context['subsubmenu'] = subsubmenu return context - @method_decorator(package.required(required_packages)) def dispatch(self, *args, **kwargs): return super(ContextMixin, self).dispatch(*args, **kwargs) @@ -81,8 +79,9 @@ class CustomServiceView(ContextMixin, TemplateView): unused, custom_services = utils.get_pagekite_services() for service in custom_services: service['form'] = AddCustomServiceForm(initial=service) - context['custom_services'] = [utils.prepare_service_for_display(service) - for service in custom_services] + context['custom_services'] = [ + utils.prepare_service_for_display(service) + for service in custom_services] context.update(utils.get_kite_details()) return context From a33f68f0513333d8b3fbafd581c871456a9d086b Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 16:54:37 +0530 Subject: [PATCH 31/45] power: Use new setup mechanism --- plinth/modules/power/__init__.py | 13 +++++++++++-- plinth/modules/power/templates/power.html | 10 ++-------- plinth/modules/power/views.py | 5 ++++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plinth/modules/power/__init__.py b/plinth/modules/power/__init__.py index 32cd2af14..9cf5d0aef 100644 --- a/plinth/modules/power/__init__.py +++ b/plinth/modules/power/__init__.py @@ -23,11 +23,20 @@ from django.utils.translation import ugettext_lazy as _ from plinth import cfg +version = 1 + +is_essential = True + depends = ['system'] +title = _('Power') + +description = [ + _('Restart or shut down the system.') +] + def init(): """Initialize the power module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Power'), 'glyphicon-off', - 'power:index', 1000) + menu.add_urlname(title, 'glyphicon-off', 'power:index', 1000) diff --git a/plinth/modules/power/templates/power.html b/plinth/modules/power/templates/power.html index 642aea911..89553d389 100644 --- a/plinth/modules/power/templates/power.html +++ b/plinth/modules/power/templates/power.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,13 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{{ title }}

- -

- {% blocktrans trimmed %}Restart or shut down the system.{% endblocktrans %} -

+{% block configuration %}

diff --git a/plinth/modules/power/views.py b/plinth/modules/power/views.py index e1d835f6a..ae18d4506 100644 --- a/plinth/modules/power/views.py +++ b/plinth/modules/power/views.py @@ -26,11 +26,14 @@ from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ from plinth import actions +from plinth.modules import power def index(request): """Serve power controls page.""" - return TemplateResponse(request, 'power.html', {'title': _('Power')}) + return TemplateResponse(request, 'power.html', + {'title': power.title, + 'description': power.description}) def restart(request): From 528fe47c160ed1e1b931a404998b91a6d2113207 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 17:02:06 +0530 Subject: [PATCH 32/45] quassel: Use new setup mechanism --- plinth/modules/quassel/__init__.py | 36 ++++++++++++++++--- plinth/modules/quassel/templates/quassel.html | 28 ++------------- plinth/modules/quassel/views.py | 10 ++---- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py index 54a2ec45b..c30f0abcc 100644 --- a/plinth/modules/quassel/__init__.py +++ b/plinth/modules/quassel/__init__.py @@ -16,7 +16,7 @@ # """ -Plinth module for quassel. +Plinth module for Quassel. """ from django.utils.translation import ugettext_lazy as _ @@ -24,22 +24,48 @@ from django.utils.translation import ugettext_lazy as _ from plinth import action_utils from plinth import cfg from plinth import service as service_module +from plinth.utils import format_lazy + +version = 1 depends = ['apps'] +title = _('IRC Client (Quassel)') + +description = [ + format_lazy( + _('Quassel is an IRC application that is split into two parts, a ' + '"core" and a "client". This allows the core to remain connected ' + 'to IRC servers, and to continue receiving messages, even when ' + 'the client is disconnected. {box_name} can run the Quassel ' + 'core service keeping you always online and one or more Quassel ' + 'clients from a desktop or a mobile can be used to connect and ' + 'disconnect from it.'), box_name=_(cfg.box_name)), + + _('You can connect to your Quassel core on the default Quassel port ' + '4242. Clients to connect to Quassel from your ' + 'desktop and ' + 'mobile devices ' + 'are available.') +] + service = None def init(): """Initialize the quassel module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('IRC Client (Quassel)'), 'glyphicon-retweet', - 'quassel:index', 730) + menu.add_urlname(title, 'glyphicon-retweet', 'quassel:index', 730) global service service = service_module.Service( - 'quassel-plinth', _('Quassel IRC Client'), - is_external=True, enabled=is_enabled()) + 'quassel-plinth', title, is_external=True, enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['quassel-core']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/quassel/templates/quassel.html b/plinth/modules/quassel/templates/quassel.html index c2f47ce0c..227769463 100644 --- a/plinth/modules/quassel/templates/quassel.html +++ b/plinth/modules/quassel/templates/quassel.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,31 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "IRC Client (Quassel)" %}

- -

- {% blocktrans trimmed %} - Quassel is an IRC application that is split into two parts, a - "core" and a "client". This allows the core to remain connected - to IRC servers, and to continue receiving messages, even when - the client is disconnected. {{ box_name }} can run the Quassel - core service keeping you always online and one or more Quassel - clients from a desktop or a mobile can be used to connect and - disconnect from it. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - You can connect to your Quassel core on the default Quassel port - 4242. Clients to connect to Quassel from your - desktop and - mobile devices - are available. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/quassel/views.py b/plinth/modules/quassel/views.py index 3918c849b..2adee5260 100644 --- a/plinth/modules/quassel/views.py +++ b/plinth/modules/quassel/views.py @@ -25,16 +25,9 @@ from django.utils.translation import ugettext as _ from .forms import QuasselForm from plinth import actions -from plinth import package from plinth.modules import quassel -def on_install(): - """Notify that the service is now enabled.""" - quassel.service.notify_enabled(None, True) - - -@package.required(['quassel-core'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -51,7 +44,8 @@ def index(request): form = QuasselForm(initial=status, prefix='quassel') return TemplateResponse(request, 'quassel.html', - {'title': _('IRC Client (Quassel)'), + {'title': quassel.title, + 'description': quassel.description, 'status': status, 'form': form}) From f78a5583577aaca147c1736ff6fa8db776a3187e Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 17:08:41 +0530 Subject: [PATCH 33/45] repro: Use new setup mechanism --- plinth/modules/repro/__init__.py | 40 ++++++++++++++++++++--- plinth/modules/repro/templates/repro.html | 36 ++------------------ plinth/modules/repro/views.py | 11 ++----- 3 files changed, 40 insertions(+), 47 deletions(-) diff --git a/plinth/modules/repro/__init__.py b/plinth/modules/repro/__init__.py index 5cba93082..0a72adcf8 100644 --- a/plinth/modules/repro/__init__.py +++ b/plinth/modules/repro/__init__.py @@ -21,25 +21,57 @@ Plinth module for repro. from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('SIP Server (repro)') + +description = [ + _('repro provides various SIP services that a SIP softphone can utilize ' + 'to provide audio and video calls as well as presence and instant ' + 'messaging. repro provides a server and SIP user accounts that clients ' + 'can use to let their presence known. It also acts as a proxy to ' + 'federate SIP communications to other servers on the Internet similar ' + 'to email.'), + + _('To make SIP calls, a client application is needed. Available clients ' + 'include Jitsi (for computers) and ' + ' ' + 'CSipSimple (for Android phones).'), + + _('Note: Before using repro, domains and users will ' + 'need to be configured using the ' + 'web-based configuration panel. Users in the admin group ' + 'will be able to log in to the repro configuration panel. After setting ' + 'the domain, it is required to restart the repro service. Disable the ' + 'service and re-enable it.'), +] + service = None def init(): """Initialize the repro module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('SIP Server (repro)'), 'glyphicon-phone-alt', - 'repro:index', 825) + menu.add_urlname(title, 'glyphicon-phone-alt', 'repro:index', 825) global service service = service_module.Service( - 'repro', _('repro SIP Server'), ['sip-plinth', 'sip-tls-plinth'], - is_external=True, enabled=is_enabled()) + 'repro', title, ['sip-plinth', 'sip-tls-plinth'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['repro']) + helper.call('post', actions.superuser_run, 'repro', ['setup']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/repro/templates/repro.html b/plinth/modules/repro/templates/repro.html index 1d4b2fe7d..df5109cb0 100644 --- a/plinth/modules/repro/templates/repro.html +++ b/plinth/modules/repro/templates/repro.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,39 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "SIP Server (repro)" %}

- -

- {% blocktrans trimmed %} - repro provides various SIP services that a SIP softphone can utilize to - provide audio and video calls as well as presence and instant messaging. - repro provides a server and SIP user accounts that clients can use to let - their presence known. It also acts as a proxy to federate SIP - communications to other servers on the Internet similar to email. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - To make SIP calls, a client application is needed. Available clients - include Jitsi (for computers) and - - CSipSimple (for Android phones). - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - Note: Before using repro, domains and users will need - to be configured using the web-based - configuration panel. Users in the admin group will be able - to log in to the repro configuration panel. After setting the domain, it - is required to restart the repro service. Disable the service and - re-enable it. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/repro/views.py b/plinth/modules/repro/views.py index 40392f48a..407a96b0c 100644 --- a/plinth/modules/repro/views.py +++ b/plinth/modules/repro/views.py @@ -25,17 +25,9 @@ from django.utils.translation import ugettext as _ from .forms import ReproForm from plinth import actions -from plinth import package from plinth.modules import repro -def on_install(): - """Notify that the service is now enabled.""" - actions.superuser_run('repro', ['setup']) - repro.service.notify_enabled(None, True) - - -@package.required(['repro'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -52,7 +44,8 @@ def index(request): form = ReproForm(initial=status, prefix='repro') return TemplateResponse(request, 'repro.html', - {'title': _('SIP Server (repro)'), + {'title': repro.title, + 'description': repro.description, 'status': status, 'form': form}) From c2cb1f32b98212fc6a005188cb442dbfa85dc98d Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 17:32:08 +0530 Subject: [PATCH 34/45] restore: Use new setup mechanism --- plinth/modules/restore/__init__.py | 30 +++++++++++++++---- .../restore/templates/restore_index.html | 24 ++------------- plinth/modules/restore/views.py | 6 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plinth/modules/restore/__init__.py b/plinth/modules/restore/__init__.py index 45404a27a..dff18b3d4 100644 --- a/plinth/modules/restore/__init__.py +++ b/plinth/modules/restore/__init__.py @@ -22,24 +22,44 @@ Plinth module to configure reStore. from django.utils.translation import ugettext_lazy as _ from plinth import action_utils, cfg from plinth import service as service_module +from plinth.utils import format_lazy service = None -__all__ = ['init'] +version = 1 depends = ['apps'] +title = _('Unhosted Storage (reStore)') + +description = [ + format_lazy( + _('reStore is a server for ' + 'unhosted web applications. The idea is to uncouple web ' + 'applications from data. No matter where a web application is ' + 'served from, the data can be stored on an unhosted storage ' + 'server of user\'s choice. With reStore, your {box_name} becomes ' + 'your unhosted storage server.'), box_name=_(cfg.box_name)), + + _('You can create and edit accounts in the ' + 'reStore web-interface.') +] + def init(): """Initialize the reStore module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Unhosted Storage (reStore)'), 'glyphicon-hdd', - 'restore:index', 750) + menu.add_urlname(title, 'glyphicon-hdd', 'restore:index', 750) global service service = service_module.Service( - 'node-restore', _('reStore'), ['http', 'https'], - is_external=False, enabled=is_enabled()) + 'node-restore', title, ['http', 'https'], is_external=False, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['node-restore']) def is_enabled(): diff --git a/plinth/modules/restore/templates/restore_index.html b/plinth/modules/restore/templates/restore_index.html index 8c9402ba7..4148a047c 100644 --- a/plinth/modules/restore/templates/restore_index.html +++ b/plinth/modules/restore/templates/restore_index.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,27 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Unhosted Storage (reStore)" %}

- -

- {% blocktrans trimmed %} - reStore is a server for unhosted - web applications. The idea is to uncouple web applications from - data. No matter where a web application is served from, the - data can be stored on an unhosted storage server of user's - choice. With reStore, your {{ box_name }} becomes your - unhosted storage server. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - You can create and edit accounts in the - reStore web-interface. - {% endblocktrans %} -

+{% block configuration %}

Configuration

diff --git a/plinth/modules/restore/views.py b/plinth/modules/restore/views.py index e0c75fbd6..0e91904a4 100644 --- a/plinth/modules/restore/views.py +++ b/plinth/modules/restore/views.py @@ -24,11 +24,10 @@ from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ from .forms import ReStoreForm -from plinth import actions, package +from plinth import actions from plinth.modules import restore -@package.required(['node-restore']) def index(request): """Serve configuration page.""" status = get_status() @@ -43,7 +42,8 @@ def index(request): form = ReStoreForm(initial=status, prefix='restore') return TemplateResponse(request, 'restore_index.html', - {'title': _('Unhosted Storage (reStore)'), + {'title': restore.title, + 'description': restore.description, 'status': status, 'form': form}) From bdfd20d661d4756aceed483f93be29202f85675a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 17:47:25 +0530 Subject: [PATCH 35/45] roundcube: Use new setup mechanism --- plinth/modules/roundcube/__init__.py | 38 ++++++++++++++++++- .../roundcube/templates/roundcube.html | 38 +------------------ plinth/modules/roundcube/views.py | 16 +------- 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py index c170c6f2b..1c11596af 100644 --- a/plinth/modules/roundcube/__init__.py +++ b/plinth/modules/roundcube/__init__.py @@ -21,18 +21,52 @@ Plinth module to configure Roundcube. from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import action_utils from plinth import cfg +version = 1 + depends = ['apps'] +title = _('Email Client (Roundcube)') + +description = [ + _('Roundcube webmail is a browser-based multilingual IMAP ' + 'client with an application-like user interface. It provides ' + 'full functionality you expect from an email client, including ' + 'MIME support, address book, folder manipulation, message ' + 'searching and spell checking.'), + + _('You can access Roundcube from ' + '/roundcube. Provide the username and password of the email ' + 'account you wish to access followed by the domain name of the ' + 'IMAP server for your email provider, like imap.example.com' + '. For IMAP over SSL (recommended), fill the server field ' + 'like imaps://imap.example.com.'), + + _('For Gmail, username will be your Gmail address, password will be ' + 'your Google account password and server will be ' + 'imaps://imap.gmail.com. Note that you will also need ' + 'to enable "Less secure apps" in your Google account settings ' + '(https://www.google.com/settings/security/lesssecureapps).'), +] + def init(): """Intialize the module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Email Client (Roundcube)'), 'glyphicon-envelope', - 'roundcube:index', 600) + menu.add_urlname(title, 'glyphicon-envelope', 'roundcube:index', 600) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.call('pre', actions.superuser_run, 'roundcube', ['pre-install']) + helper.install(['sqlite3', 'roundcube', 'roundcube-sqlite3']) + helper.call('pre', actions.superuser_run, 'roundcube', ['setup']) + def is_enabled(): """Return whether the module is enabled.""" diff --git a/plinth/modules/roundcube/templates/roundcube.html b/plinth/modules/roundcube/templates/roundcube.html index fea5d2c92..6028f7aba 100644 --- a/plinth/modules/roundcube/templates/roundcube.html +++ b/plinth/modules/roundcube/templates/roundcube.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,41 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Email Client (Roundcube)" %}

- -

- {% blocktrans trimmed %} - Roundcube webmail is a browser-based multilingual IMAP client - with an application-like user interface. It provides full - functionality you expect from an email client, including MIME - support, address book, folder manipulation, message searching - and spell checking. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - You can access Roundcube from /roundcube. - Provide the username and password of the email account you wish - to access followed by the domain name of the IMAP server for - your email provider, like imap.example.com. For - IMAP over SSL (recommended), fill the server field like - imaps://imap.example.com. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - For Gmail, username will be your Gmail address, password will be - your Google account password and server will be - imaps://imap.gmail.com. Note that you will also need - to enable "Less secure apps" in your Google account settings - (https://www.google.com/settings/security/lesssecureapps). - {% endblocktrans %} -

+{% block configuration %} {% include "diagnostics_button.html" with module="roundcube" %} diff --git a/plinth/modules/roundcube/views.py b/plinth/modules/roundcube/views.py index 1790d8868..610fdb8c0 100644 --- a/plinth/modules/roundcube/views.py +++ b/plinth/modules/roundcube/views.py @@ -26,24 +26,11 @@ import logging from .forms import RoundcubeForm from plinth import actions -from plinth import package from plinth.modules import roundcube logger = logging.getLogger(__name__) -def before_install(): - """Preseed debconf values before the packages are installed.""" - actions.superuser_run('roundcube', ['pre-install']) - - -def on_install(): - """Setup Roundcube Apache configuration.""" - actions.superuser_run('roundcube', ['setup']) - - -@package.required(['sqlite3', 'roundcube', 'roundcube-sqlite3'], - before_install=before_install, on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -61,7 +48,8 @@ def index(request): form = RoundcubeForm(initial=status, prefix='roundcube') return TemplateResponse(request, 'roundcube.html', - {'title': _('Email Client (Roundcube)'), + {'title': roundcube.title, + 'description': roundcube.description, 'status': status, 'form': form}) From 4066a2f8d62d35bd9f637e26df7cf6820a083249 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:12:51 +0530 Subject: [PATCH 36/45] shaarli: Use new setup mechanism --- plinth/modules/shaarli/__init__.py | 26 ++++++++++++++++--- plinth/modules/shaarli/templates/shaarli.html | 18 ++----------- plinth/modules/shaarli/views.py | 8 ++---- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py index ac2e07df1..2210da998 100644 --- a/plinth/modules/shaarli/__init__.py +++ b/plinth/modules/shaarli/__init__.py @@ -26,21 +26,39 @@ from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('Bookmarks (Shaarli)') + +description = [ + _('Shaarli allows you to save and share bookmarks.'), + + _('When enabled, Shaarli will be available from ' + '/shaarli path on the web server. Note that Shaarli only supports a ' + 'single user account, which you will need to setup on the initial ' + 'visit.'), +] + service = None def init(): """Initialize the module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Bookmarks (Shaarli)'), 'glyphicon-bookmark', - 'shaarli:index', 350) + menu.add_urlname(title, 'glyphicon-bookmark', 'shaarli:index', 350) global service service = service_module.Service( - 'shaarli', _('Shaarli'), ['http', 'https'], - is_external=True, enabled=is_enabled()) + 'shaarli', title, ['http', 'https'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['shaarli']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/shaarli/templates/shaarli.html b/plinth/modules/shaarli/templates/shaarli.html index 391c25466..9a6c55b0a 100644 --- a/plinth/modules/shaarli/templates/shaarli.html +++ b/plinth/modules/shaarli/templates/shaarli.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,21 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Bookmarks (Shaarli)" %}

- -

{% trans "Shaarli allows you to save and share bookmarks." %}

- -

- {% blocktrans trimmed %} - - When enabled, Shaarli will be available from /shaarli - path on the web server. Note that Shaarli only supports a single - user account, which you will need to setup on the initial visit. - - {% endblocktrans %} -

+{% block configuration %}

{% trans "Configuration" %}

diff --git a/plinth/modules/shaarli/views.py b/plinth/modules/shaarli/views.py index 6182db539..de174faa7 100644 --- a/plinth/modules/shaarli/views.py +++ b/plinth/modules/shaarli/views.py @@ -25,14 +25,9 @@ from django.utils.translation import ugettext as _ from .forms import ShaarliForm from plinth import actions -from plinth import package from plinth.modules import shaarli -def on_install(): - """Notify that the service is now enabled.""" - shaarli.service.notify_enabled(None, True) -@package.required(['shaarli'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -49,7 +44,8 @@ def index(request): form = ShaarliForm(initial=status, prefix='shaarli') return TemplateResponse(request, 'shaarli.html', - {'title': _('Bookmarks (Shaarli)'), + {'title': shaarli.title, + 'description': shaarli.description, 'status': status, 'form': form}) From ecad252653e0cff3cfee22fdb5e15cc81d86f95a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:22:21 +0530 Subject: [PATCH 37/45] system: Use new setup mechanism --- plinth/modules/system/__init__.py | 27 ++++++++++++++++--- plinth/modules/system/templates/system.html | 24 +---------------- plinth/modules/system/urls.py | 2 +- plinth/modules/system/{system.py => views.py} | 12 +++------ 4 files changed, 29 insertions(+), 36 deletions(-) rename plinth/modules/system/{system.py => views.py} (73%) diff --git a/plinth/modules/system/__init__.py b/plinth/modules/system/__init__.py index ec0f9e62d..7b75d3976 100644 --- a/plinth/modules/system/__init__.py +++ b/plinth/modules/system/__init__.py @@ -19,8 +19,29 @@ Plinth module for system section page """ -from . import system -from .system import init +from django.utils.translation import ugettext_lazy as _ + +from plinth import cfg +from plinth.utils import format_lazy -__all__ = ['system', 'init'] +version = 1 + +is_essential = 1 + +title = _('System Configuration') + +description = [ + format_lazy( + _('Here you can administrate the underlying system of your ' + '{box_name}.'), box_name=_(cfg.box_name)), + + format_lazy( + _('The options affect the {box_name} at its most general level, ' + 'so be careful!'), box_name=_(cfg.box_name)) +] + + +def init(): + """Initialize the system module""" + cfg.main_menu.add_urlname(title, 'glyphicon-cog', 'system:index', 100) diff --git a/plinth/modules/system/templates/system.html b/plinth/modules/system/templates/system.html index e8c2a46f3..a60745d8f 100644 --- a/plinth/modules/system/templates/system.html +++ b/plinth/modules/system/templates/system.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'app.html' %} {% comment %} # # This file is part of Plinth. @@ -17,25 +17,3 @@ # along with this program. If not, see . # {% endcomment %} - -{% load i18n %} - -{% block content %} - -

{% trans "System Configuration" %}

- -

- {% blocktrans trimmed %} - Here you can administrate the underlying system of your - {{ box_name }}. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - The options affect the {{ box_name }} at its most general level, - so be careful! - {% endblocktrans %} -

- -{% endblock %} diff --git a/plinth/modules/system/urls.py b/plinth/modules/system/urls.py index aceecc21d..8e75a6af5 100644 --- a/plinth/modules/system/urls.py +++ b/plinth/modules/system/urls.py @@ -21,7 +21,7 @@ URLs for the System module from django.conf.urls import url -from . import system as views +from . import views urlpatterns = [ diff --git a/plinth/modules/system/system.py b/plinth/modules/system/views.py similarity index 73% rename from plinth/modules/system/system.py rename to plinth/modules/system/views.py index c7a68b7f9..bc4dd1953 100644 --- a/plinth/modules/system/system.py +++ b/plinth/modules/system/views.py @@ -16,18 +16,12 @@ # from django.template.response import TemplateResponse -from django.utils.translation import ugettext_lazy as _ -from plinth import cfg - - -def init(): - """Initialize the system module""" - cfg.main_menu.add_urlname(_('System'), 'glyphicon-cog', 'system:index', - 100) +from plinth.modules import system def index(request): """Serve the index page""" return TemplateResponse(request, 'system.html', - {'title': _('System Configuration')}) + {'title': system.title, + 'description': system.description}) From ac558568ba26852573b2fddcdce8843dd36c04db Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:31:59 +0530 Subject: [PATCH 38/45] tor: Use new setup mechanism --- plinth/modules/tor/__init__.py | 29 ++++++++++++++++++++++++--- plinth/modules/tor/templates/tor.html | 17 ++-------------- plinth/modules/tor/views.py | 16 ++------------- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py index 6933e9348..e12773198 100644 --- a/plinth/modules/tor/__init__.py +++ b/plinth/modules/tor/__init__.py @@ -20,7 +20,7 @@ Plinth module to configure Tor. """ import augeas -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ import glob import itertools @@ -32,8 +32,21 @@ from plinth.modules.names import SERVICES from plinth.signals import domain_added +version = 1 + depends = ['apps', 'names'] +title = _('Anonymity Network (Tor)') + +description = [ + _('Tor is an anonymous communication system. You can learn more ' + 'about it from the Tor ' + 'Project website. For best protection when web surfing, the ' + 'Tor Project recommends that you use the ' + '' + 'Tor Browser.') +] + socks_service = None bridge_service = None @@ -45,8 +58,7 @@ APT_TOR_PREFIX = 'tor+' def init(): """Initialize the module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Anonymity Network (Tor)'), 'glyphicon-eye-close', - 'tor:index', 100) + menu.add_urlname(title, 'glyphicon-eye-close', 'tor:index', 100) global socks_service socks_service = service_module.Service( @@ -77,6 +89,17 @@ def init(): services=hs_services) +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['tor', 'tor-geoipdb', 'torsocks', 'obfs4proxy', + 'apt-transport-tor']) + helper.call('post', actions.superuser_run, 'tor', ['setup']) + helper.call('post', actions.superuser_run, 'tor', + ['configure', '--apt-transport-tor', 'enable']) + helper.call('post', socks_service.notify_enabled, None, True) + helper.call('post', bridge_service.notify_enabled, None, True) + + def is_enabled(): """Return whether the module is enabled.""" return action_utils.service_is_enabled('tor') diff --git a/plinth/modules/tor/templates/tor.html b/plinth/modules/tor/templates/tor.html index 2b1b0f07e..9b6db3ed5 100644 --- a/plinth/modules/tor/templates/tor.html +++ b/plinth/modules/tor/templates/tor.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -30,20 +30,7 @@ {% endblock %} -{% block content %} - -

{% trans "Anonymity Network (Tor)" %}

- -

- {% blocktrans trimmed %} - Tor is an anonymous communication system. You can learn more - about it from the Tor - Project website. For best protection when web surfing, the - Tor Project recommends that you use the - - Tor Browser. - {% endblocktrans %} -

+{% block configuration %}

{% trans "Status" %}

diff --git a/plinth/modules/tor/views.py b/plinth/modules/tor/views.py index 120e42133..58e513a1f 100644 --- a/plinth/modules/tor/views.py +++ b/plinth/modules/tor/views.py @@ -25,7 +25,6 @@ from django.utils.translation import ugettext_lazy as _ from .forms import TorForm from plinth import actions -from plinth import package from plinth.errors import ActionError from plinth.modules import tor from plinth.modules.names import SERVICES @@ -34,18 +33,6 @@ from plinth.signals import domain_added, domain_removed config_process = None -def on_install(): - """Setup Tor configuration as soon as it is installed.""" - actions.superuser_run('tor', ['setup']) - actions.superuser_run('tor', - ['configure', '--apt-transport-tor', 'enable']) - tor.socks_service.notify_enabled(None, True) - tor.bridge_service.notify_enabled(None, True) - - -@package.required(['tor', 'tor-geoipdb', 'torsocks', 'obfs4proxy', - 'apt-transport-tor'], - on_install=on_install) def index(request): """Serve configuration page.""" if config_process: @@ -65,7 +52,8 @@ def index(request): form = TorForm(initial=status, prefix='tor') return TemplateResponse(request, 'tor.html', - {'title': _('Tor Control Panel'), + {'title': tor.title, + 'description': tor.description, 'status': status, 'config_running': bool(config_process), 'form': form}) From 065d6c4c0a2e73ae14dd0af73cf7e281784fc6b1 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:45:17 +0530 Subject: [PATCH 39/45] transmission: Use new setup mechanism --- plinth/modules/transmission/__init__.py | 32 ++++++++++++++++--- .../transmission/templates/transmission.html | 14 ++------ plinth/modules/transmission/views.py | 15 ++------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py index 4206c1985..aed1380ed 100644 --- a/plinth/modules/transmission/__init__.py +++ b/plinth/modules/transmission/__init__.py @@ -20,27 +20,51 @@ Plinth module to configure Transmission server """ from django.utils.translation import ugettext_lazy as _ +import json +from plinth import actions from plinth import action_utils from plinth import cfg from plinth import service as service_module +version = 1 + depends = ['apps'] +title = _('BitTorrent (Transmission)') + +description = [ + _('BitTorrent is a peer-to-peer file sharing protocol. ' + 'Transmission daemon handles Bitorrent file sharing. Note that ' + 'BitTorrent is not anonymous.') +] + service = None def init(): """Intialize the Transmission module.""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('BitTorrent (Transmission)'), 'glyphicon-save', - 'transmission:index', 300) + menu.add_urlname(title, 'glyphicon-save', 'transmission:index', 300) global service service = service_module.Service( - 'transmission', _('Transmission BitTorrent'), ['http', 'https'], - is_external=True, enabled=is_enabled()) + 'transmission', title, ['http', 'https'], is_external=True, + enabled=is_enabled()) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['transmission-daemon']) + + new_configuration = {'rpc-whitelist-enabled': False} + helper.call('post', actions.superuser_run, 'transmission', + ['merge-configuration'], + input=json.dumps(new_configuration).encode()) + + helper.call('post', actions.superuser_run, 'transmission', ['enable']) + helper.call('post', service.notify_enabled, None, True) def is_enabled(): diff --git a/plinth/modules/transmission/templates/transmission.html b/plinth/modules/transmission/templates/transmission.html index de67c4908..0adf4648f 100644 --- a/plinth/modules/transmission/templates/transmission.html +++ b/plinth/modules/transmission/templates/transmission.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,17 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "BitTorrent (Transmission)" %}

- -

- {% blocktrans trimmed %} - BitTorrent is a peer-to-peer file sharing protocol. - Transmission daemon handles Bitorrent file sharing. Note that - BitTorrent is not anonymous. - {% endblocktrans %} -

+{% block configuration %}

{% blocktrans trimmed %} diff --git a/plinth/modules/transmission/views.py b/plinth/modules/transmission/views.py index 213e69da8..c65c81013 100644 --- a/plinth/modules/transmission/views.py +++ b/plinth/modules/transmission/views.py @@ -28,7 +28,6 @@ import socket from .forms import TransmissionForm from plinth import actions -from plinth import package from plinth.modules import transmission logger = logging.getLogger(__name__) @@ -36,17 +35,6 @@ logger = logging.getLogger(__name__) TRANSMISSION_CONFIG = '/etc/transmission-daemon/settings.json' -def on_install(): - """Enable transmission as soon as it is installed.""" - new_configuration = {'rpc-whitelist-enabled': False} - actions.superuser_run('transmission', ['merge-configuration'], - input=json.dumps(new_configuration).encode()) - - actions.superuser_run('transmission', ['enable']) - transmission.service.notify_enabled(None, True) - - -@package.required(['transmission-daemon'], on_install=on_install) def index(request): """Serve configuration page.""" status = get_status() @@ -64,7 +52,8 @@ def index(request): form = TransmissionForm(initial=status, prefix='transmission') return TemplateResponse(request, 'transmission.html', - {'title': _('BitTorrent (Transmission)'), + {'title': transmission.title, + 'description': transmission.description, 'status': status, 'form': form}) From b5ccada3a6da405f0fd4b173be90c1848e92c408 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:55:46 +0530 Subject: [PATCH 40/45] upgrades: Use new setup mechanism --- plinth/modules/upgrades/__init__.py | 22 +++++++++++++++++-- .../modules/upgrades/templates/upgrades.html | 14 ++---------- .../templates/upgrades_configure.html | 6 ++--- plinth/modules/upgrades/views.py | 18 +++++---------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py index 2e5110a16..795b88e1b 100644 --- a/plinth/modules/upgrades/__init__.py +++ b/plinth/modules/upgrades/__init__.py @@ -21,14 +21,32 @@ Plinth module for upgrades from django.utils.translation import ugettext_lazy as _ +from plinth import actions from plinth import cfg +version = 1 + +is_essential = 1 + depends = ['system'] +title = _('Software Upgrades') + +description = [ + _('Upgrades install the latest software and security updates. When ' + 'automatic upgrades are enabled, upgrades are automatically run every ' + 'night. You don\'t normally need to start the upgrade process.') +] + def init(): """Initialize the module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Software Upgrades'), 'glyphicon-refresh', - 'upgrades:index', 21) + menu.add_urlname(title, 'glyphicon-refresh', 'upgrades:index', 21) + + +def setup(helper, old_version=None): + """Install and configure the module.""" + helper.install(['unattended-upgrades']) + helper.call('post', actions.superuser_run, 'upgrades', ['enable-auto']) diff --git a/plinth/modules/upgrades/templates/upgrades.html b/plinth/modules/upgrades/templates/upgrades.html index 15b0167b2..4cc47be98 100644 --- a/plinth/modules/upgrades/templates/upgrades.html +++ b/plinth/modules/upgrades/templates/upgrades.html @@ -1,4 +1,4 @@ -{% extends 'base.html' %} +{% extends 'app.html' %} {% comment %} # # This file is part of Plinth. @@ -29,17 +29,7 @@ {% endblock %} -{% block content %} - -

{{ title }}

- -

- {% blocktrans trimmed %} - Upgrades install the latest software and security updates. When automatic - upgrades are enabled, upgrades are automatically run every night. You - don't normally need to start the upgrade process. - {% endblocktrans %} -

+{% block configuration %}

{% blocktrans trimmed %} diff --git a/plinth/modules/upgrades/templates/upgrades_configure.html b/plinth/modules/upgrades/templates/upgrades_configure.html index 1ee94ec32..4a4e1c3fe 100644 --- a/plinth/modules/upgrades/templates/upgrades_configure.html +++ b/plinth/modules/upgrades/templates/upgrades_configure.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,9 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{{ title }}

+{% block configuration %} {% csrf_token %} diff --git a/plinth/modules/upgrades/views.py b/plinth/modules/upgrades/views.py index d08d6f291..81b0a4ba3 100644 --- a/plinth/modules/upgrades/views.py +++ b/plinth/modules/upgrades/views.py @@ -21,16 +21,14 @@ Plinth module for upgrades from django.contrib import messages from django.core.urlresolvers import reverse_lazy -from django.shortcuts import redirect from django.template.response import TemplateResponse from django.utils.translation import ugettext as _, ugettext_lazy -from django.views.decorators.http import require_POST import subprocess from .forms import ConfigureForm from plinth import actions -from plinth import package from plinth.errors import ActionError +from plinth.modules import upgrades subsubmenu = [{'url': reverse_lazy('upgrades:index'), 'text': ugettext_lazy('Automatic Upgrades')}, @@ -41,12 +39,6 @@ LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log' LOCK_FILE = '/var/log/dpkg/lock' -def on_install(): - """Enable automatic upgrades after install.""" - actions.superuser_run('upgrades', ['enable-auto']) - - -@package.required(['unattended-upgrades'], on_install=on_install) def index(request): """Serve the configuration form.""" status = get_status() @@ -63,10 +55,12 @@ def index(request): form = ConfigureForm(initial=status, prefix='upgrades') return TemplateResponse(request, 'upgrades_configure.html', - {'title': _('Automatic Upgrades'), + {'title': upgrades.title, + 'description': upgrades.description, 'form': form, 'subsubmenu': subsubmenu}) + def is_package_manager_busy(): """Return whether a package manager is running.""" try: @@ -85,7 +79,6 @@ def get_log(): return None -@package.required(['unattended-upgrades'], on_install=on_install) def upgrade(request): """Serve the upgrade page.""" is_busy = is_package_manager_busy() @@ -99,7 +92,8 @@ def upgrade(request): messages.error(request, _('Starting upgrade failed.')) return TemplateResponse(request, 'upgrades.html', - {'title': _('Package Upgrades'), + {'title': upgrades.title, + 'description': upgrades.description, 'subsubmenu': subsubmenu, 'is_busy': is_busy, 'log': get_log()}) From bee0260af74e1263090442a12036451e8d024acc Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 18:58:01 +0530 Subject: [PATCH 41/45] users: Use new setup mechanism --- plinth/modules/users/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plinth/modules/users/__init__.py b/plinth/modules/users/__init__.py index 7cb738071..b7c6c8ca1 100644 --- a/plinth/modules/users/__init__.py +++ b/plinth/modules/users/__init__.py @@ -25,14 +25,19 @@ import subprocess from plinth import cfg from plinth import action_utils +version = 1 + +is_essential = True + depends = ['system'] +title = _('Users and Groups') + def init(): """Intialize the user module.""" menu = cfg.main_menu.get('system:index') - menu.add_urlname(_('Users and Groups'), 'glyphicon-user', 'users:index', - 15) + menu.add_urlname(title, 'glyphicon-user', 'users:index', 15) def diagnose(): From 30f0876c32ece47d90bc7497a1dd0b81d6c8e252 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 19:07:36 +0530 Subject: [PATCH 42/45] xmpp: Use new setup mechanism --- plinth/modules/xmpp/__init__.py | 42 ++++++++++++++++++++++--- plinth/modules/xmpp/templates/xmpp.html | 21 ++----------- plinth/modules/xmpp/views.py | 29 ++--------------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/plinth/modules/xmpp/__init__.py b/plinth/modules/xmpp/__init__.py index 65e8a3c19..1365f1d98 100644 --- a/plinth/modules/xmpp/__init__.py +++ b/plinth/modules/xmpp/__init__.py @@ -20,6 +20,8 @@ Plinth module to configure XMPP server """ from django.utils.translation import ugettext_lazy as _ +import logging +import socket from plinth import actions from plinth import action_utils @@ -29,21 +31,35 @@ from plinth.signals import pre_hostname_change, post_hostname_change from plinth.signals import domainname_change +version = 1 + depends = ['apps'] +title = _('Chat Server (XMPP)') + +description = [ + _('XMPP is an open and standardized communication protocol. Here ' + 'you can run and configure your XMPP server, called ejabberd.'), + + _('To actually communicate, you can use the web ' + 'client or any other ' + 'XMPP client.') +] + service = None +logger = logging.getLogger(__name__) + def init(): """Initialize the XMPP module""" menu = cfg.main_menu.get('apps:index') - menu.add_urlname(_('Chat Server (XMPP)'), 'glyphicon-comment', - 'xmpp:index', 400) + menu.add_urlname(title, 'glyphicon-comment', 'xmpp:index', 400) global service service = service_module.Service( - 'xmpp', _('Chat Server (XMPP)'), - ['xmpp-client', 'xmpp-server', 'xmpp-bosh'], + 'xmpp', title, ['xmpp-client', 'xmpp-server', 'xmpp-bosh'], is_external=True, enabled=is_enabled()) pre_hostname_change.connect(on_pre_hostname_change) @@ -51,6 +67,18 @@ def init(): domainname_change.connect(on_domainname_change) +def setup(helper, old_version=None): + """Install and configure the module.""" + domainname = get_domainname() + logger.info('XMPP service domainname - %s', domainname) + + helper.call('pre', actions.superuser_run, 'xmpp', + ['pre-install', '--domainname', domainname]) + helper.install(['jwchat', 'ejabberd']) + helper.call('post', actions.superuser_run, 'xmpp', ['setup']) + helper.call('post', service.notify_enabled, None, True) + + def is_enabled(): """Return whether the module is enabled.""" return (action_utils.service_is_enabled('ejabberd') and @@ -62,6 +90,12 @@ def is_running(): return action_utils.service_is_running('ejabberd') +def get_domainname(): + """Return the domainname""" + fqdn = socket.getfqdn() + return '.'.join(fqdn.split('.')[1:]) + + def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs): """ Backup ejabberd database before hostname is changed. diff --git a/plinth/modules/xmpp/templates/xmpp.html b/plinth/modules/xmpp/templates/xmpp.html index b0f3ac56b..31e387be2 100644 --- a/plinth/modules/xmpp/templates/xmpp.html +++ b/plinth/modules/xmpp/templates/xmpp.html @@ -1,4 +1,4 @@ -{% extends "base.html" %} +{% extends "app.html" %} {% comment %} # # This file is part of Plinth. @@ -21,24 +21,7 @@ {% load bootstrap %} {% load i18n %} -{% block content %} - -

{% trans "Chat Server (XMPP)" %}

- -

- {% blocktrans trimmed %} - XMPP is an open and standardized communication protocol. Here - you can run and configure your XMPP server, called ejabberd. - {% endblocktrans %} -

- -

- {% blocktrans trimmed %} - To actually communicate, you can use the web - client or any other XMPP client. - {% endblocktrans %} -

+{% block configuration %}

{% url 'config:index' as index_url %} diff --git a/plinth/modules/xmpp/views.py b/plinth/modules/xmpp/views.py index 817417177..ca1841535 100644 --- a/plinth/modules/xmpp/views.py +++ b/plinth/modules/xmpp/views.py @@ -23,39 +23,15 @@ from django.contrib import messages from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ import logging -import socket from .forms import XmppForm from plinth import actions -from plinth import package from plinth.modules import xmpp logger = logging.getLogger(__name__) -def get_domainname(): - """Return the domainname""" - fqdn = socket.getfqdn() - return '.'.join(fqdn.split('.')[1:]) - - -def before_install(): - """Preseed debconf values before the packages are installed.""" - domainname = get_domainname() - logger.info('XMPP service domainname - %s', domainname) - actions.superuser_run('xmpp', ['pre-install', '--domainname', domainname]) - - -def on_install(): - """Setup jwchat apache conf""" - actions.superuser_run('xmpp', ['setup']) - xmpp.service.notify_enabled(None, True) - - -@package.required(['jwchat', 'ejabberd'], - before_install=before_install, - on_install=on_install) def index(request): """Serve configuration page""" status = get_status() @@ -72,7 +48,8 @@ def index(request): form = XmppForm(initial=status, prefix='xmpp') return TemplateResponse(request, 'xmpp.html', - {'title': _('Chat Server (XMPP)'), + {'title': xmpp.title, + 'description': xmpp.description, 'status': status, 'form': form}) @@ -81,7 +58,7 @@ def get_status(): """Get the current settings.""" status = {'enabled': xmpp.is_enabled(), 'is_running': xmpp.is_running(), - 'domainname': get_domainname()} + 'domainname': xmpp.get_domainname()} return status From 60c4c17f94f9bc5f0a32553418a939e6330d7807 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 13 Feb 2016 13:47:27 +0530 Subject: [PATCH 43/45] firstboot: Use new setup mechanism --- plinth/modules/first_boot/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plinth/modules/first_boot/__init__.py b/plinth/modules/first_boot/__init__.py index 558116da9..55d9941a0 100644 --- a/plinth/modules/first_boot/__init__.py +++ b/plinth/modules/first_boot/__init__.py @@ -18,3 +18,7 @@ """ Plinth module for first boot wizard """ + +version = 1 + +is_essential = True From 1f5fa31e53d9c52338139b7466e9899660c5b841 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Fri, 12 Feb 2016 21:03:17 +0530 Subject: [PATCH 44/45] package: Remove old package installation methods --- plinth/package.py | 158 +------------------------- plinth/setup.py | 3 - plinth/templates/package_install.html | 94 --------------- plinth/views.py | 34 ------ 4 files changed, 2 insertions(+), 287 deletions(-) delete mode 100644 plinth/templates/package_install.html diff --git a/plinth/package.py b/plinth/package.py index 5a5f2ae3c..562aa0236 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -19,13 +19,9 @@ Framework for installing and updating distribution packages """ -from django.contrib import messages from django.utils.translation import ugettext as _ -import functools import logging -import threading -import plinth from plinth.utils import import_from_gi glib = import_from_gi('GLib', '2.0') packagekit = import_from_gi('PackageKitGlib', '1.0') @@ -55,15 +51,12 @@ class PackageException(Exception): class Transaction(object): """Information about an ongoing transaction.""" - def __init__(self, package_names, before_install=None, on_install=None): + def __init__(self, package_names): """Initialize transaction object. Set most values to None until they are sent as progress update. """ self.package_names = package_names - # XXX: This is hack, remove after implementing proper setup mechanism. - self.before_install = before_install - self.on_install = on_install # Progress self.allow_cancel = None @@ -79,10 +72,6 @@ class Transaction(object): self.download_size_remaining = None self.speed = None - # Completion - self.is_finished = False - self.exception = None - def get_id(self): """Return a identifier to use as a key in a map of transactions.""" return frozenset(self.package_names) @@ -94,43 +83,12 @@ class Transaction(object): self.package_names, self.allow_cancel, self.status_string, self.percentage, self.package, self.item_progress) - def start_install_in_thread(self): - """Start a PackageKit transaction to install given list of packages. - - This operation is non-blocking at it spawns a new thread. - """ - thread = threading.Thread(target=self.install) - thread.start() - def install(self): """Run a PackageKit transaction to install given packages.""" - try: - if self.before_install: - self.before_install() - except Exception as exception: - logger.exception('Error during setup before install - %s', - exception) - self.finish(exception) - return - try: self._do_install() - except PackageException as exception: - self.finish(exception) - return except glib.Error as exception: - self.finish(PackageException(exception.message)) - return - - try: - if self.on_install: - self.on_install() - except Exception as exception: - logger.exception('Error during setup - %s', exception) - self.finish(exception) - return - - self.finish() + raise PackageException(exception.message) def _do_install(self): """Run a PackageKit transaction to install given packages. @@ -208,115 +166,3 @@ class Transaction(object): else: logger.info('Unhandle packagekit progress callback - %s, %s', progress, progress_type) - - def finish(self, exception=None): - """Mark transaction as complected and store exception if any.""" - self.is_finished = True - self.exception = exception - - def collect_result(self): - """Retrieve the result of this transaction. - - Also remove self from global transactions list. - """ - assert self.is_finished - - del transactions[self.get_id()] - return self.exception - - -def required(package_names, before_install=None, on_install=None): - """Decorate a view to check and install required packages.""" - - def wrapper2(func): - """Return a function to check and install packages.""" - - @functools.wraps(func) - def wrapper(request, *args, **kwargs): - """Check and install packages required by a view.""" - if not _should_show_install_view(request, package_names): - return func(request, *args, **kwargs) - - view = plinth.views.PackageInstallView.as_view() - return view(request, package_names=package_names, - before_install=before_install, on_install=on_install, - *args, **kwargs) - - return wrapper - - return wrapper2 - - -def _should_show_install_view(request, package_names): - """Return whether the installation view should be shown.""" - transaction_id = frozenset(package_names) - - # No transaction in progress - if transaction_id not in transactions: - is_installed = check_installed(package_names) - return not is_installed - - # Installing - transaction = transactions[transaction_id] - if not transaction.is_finished: - return True - - # Transaction finished, waiting to show the result - exception = transaction.collect_result() - if not exception: - messages.success(request, - _('Installed and configured packages successfully.')) - return False - else: - error_string = getattr(exception, 'error_string', str(exception)) - error_details = getattr(exception, 'error_details', '') - messages.error(request, _('Error installing packages: {string} {details}') - .format(string=error_string, details=error_details)) - return True - - -def check_installed(package_names): - """Return a boolean installed status of package. - - This operation is blocking and waits until the check is finished. - """ - def _callback(progress, progress_type, user_data): - """Process progress updates on package resolve operation.""" - pass - - client = packagekit.Client() - response = client.resolve(packagekit.FilterEnum.INSTALLED, - tuple(package_names) + (None, ), None, - _callback, None) - - installed_package_names = [] - for package in response.get_package_array(): - if package.get_info() == packagekit.InfoEnum.INSTALLED: - installed_package_names.append(package.get_name()) - - packages_resolved[package.get_name()] = package - - # When package names could not be resolved - for package_name in package_names: - if package_name not in packages_resolved: - packages_resolved[package_name] = None - - return set(installed_package_names) == set(package_names) - - -def is_installing(package_names): - """Return whether a set of packages are currently being installed.""" - return frozenset(package_names) in transactions - - -def start_install(package_names, before_install=None, on_install=None): - """Start a PackageKit transaction to install given list of packages. - - This operation is non-blocking at it spawns a new thread. - """ - transaction = Transaction(package_names, - before_install=before_install, - on_install=on_install) - transactions[frozenset(package_names)] = transaction - - transaction.start_install_in_thread() diff --git a/plinth/setup.py b/plinth/setup.py index 2ade51021..c8fd747d2 100644 --- a/plinth/setup.py +++ b/plinth/setup.py @@ -98,9 +98,6 @@ class Helper(object): } transaction.install() - if transaction.exception: - logger.error('Error running install - %s', transaction.exception) - raise transaction.exception def call(self, step, method, *args, **kwargs): """Call an arbitrary method during setup and note down its stage.""" diff --git a/plinth/templates/package_install.html b/plinth/templates/package_install.html deleted file mode 100644 index 7df7bb8c6..000000000 --- a/plinth/templates/package_install.html +++ /dev/null @@ -1,94 +0,0 @@ -{% extends "base.html" %} -{% comment %} -# -# This file is part of Plinth. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -{% endcomment %} - -{% load bootstrap %} -{% load i18n %} - -{% block page_head %} - - {% if is_installing %} - - {% endif %} - -{% endblock %} - - -{% block content %} - -

{% trans "Installation" %}

- - {% if not is_installing %} - -

- {% blocktrans trimmed %} - This feature requires addtional packages to be installed. Do - you wish to install them? - {% endblocktrans %} -

- - - - - - - - - - {% for package_name, package in packages.items %} - - - - - {% endfor %} - -
{% trans "Package" %}{% trans "Summary" %}
{{ package_name }}{{ package.get_summary }}
- - - {% csrf_token %} - - - - - {% else %} - - {% for key, transaction in transactions.items %} -
- {% blocktrans trimmed with package_names=transaction.package_names|join:", " status=transaction.status_string %} - Installing {{ package_names }}: {{ status }} - {% endblocktrans %} -
-
-
- - {% blocktrans trimmed with percentage=transaction.percentage %} - {{ percentage }}% complete - {% endblocktrans %} - -
-
- {% endfor %} - - {% endif %} - -{% endblock %} diff --git a/plinth/views.py b/plinth/views.py index 8cee96adf..a5b9a7680 100644 --- a/plinth/views.py +++ b/plinth/views.py @@ -24,46 +24,12 @@ from django.http.response import HttpResponseRedirect from django.views.generic import TemplateView import time -from plinth import package as package_module - def index(request): """Serve the main index page.""" return HttpResponseRedirect(reverse('apps:index')) -class PackageInstallView(TemplateView): - """View to prompt and install packages.""" - template_name = 'package_install.html' - - def get_context_data(self, **kwargs): - """Return the context data rendering the template.""" - context = super(PackageInstallView, self).get_context_data(**kwargs) - - if 'packages_names' not in context: - context['package_names'] = self.kwargs.get('package_names', []) - context['packages'] = { - package_name: package_module.packages_resolved[package_name] - for package_name in context['package_names']} - context['is_installing'] = \ - package_module.is_installing(context['package_names']) - context['transactions'] = package_module.transactions - - return context - - def post(self, *args, **kwargs): - """Handle installing packages - - Start the package installation, and refresh the page every x seconds to - keep displaying PackageInstallView.get() with the installation status. - """ - package_module.start_install( - self.kwargs['package_names'], - before_install=self.kwargs.get('before_install'), - on_install=self.kwargs.get('on_install')) - return self.render_to_response(self.get_context_data()) - - class SetupView(TemplateView): """View to prompt and setup applications.""" template_name = 'setup.html' From 55cabdaa2e7b15ad20280952ec42436c0fe513ba Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Sat, 13 Feb 2016 12:36:51 +0530 Subject: [PATCH 45/45] package: Better error handling --- plinth/package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plinth/package.py b/plinth/package.py index 562aa0236..8a46401ca 100644 --- a/plinth/package.py +++ b/plinth/package.py @@ -88,7 +88,7 @@ class Transaction(object): try: self._do_install() except glib.Error as exception: - raise PackageException(exception.message) + raise PackageException(exception.message) from exception def _do_install(self): """Run a PackageKit transaction to install given packages.