diff --git a/actions/tor b/actions/tor
index a421e30e5..3b8f48fb7 100755
--- a/actions/tor
+++ b/actions/tor
@@ -31,8 +31,9 @@ import socket
import time
from plinth import action_utils
-from plinth.modules.tor import is_enabled, is_running, get_augeas, \
- get_real_apt_uri_path, iter_apt_uris, APT_TOR_PREFIX
+from plinth.modules.tor import is_enabled, is_running, APT_TOR_PREFIX
+from plinth.modules.tor.utils import get_real_apt_uri_path, iter_apt_uris, \
+ get_augeas
SERVICE_FILE = '/etc/firewalld/services/tor-{0}.xml'
TOR_CONFIG = '/files/etc/tor/torrc'
diff --git a/plinth/forms.py b/plinth/forms.py
index f554bcfc8..896581975 100644
--- a/plinth/forms.py
+++ b/plinth/forms.py
@@ -23,8 +23,16 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
+# TODO: remove this form once owncloud is removed (it's the last using it).
class ConfigurationForm(forms.Form):
"""Generic configuration form for simple modules."""
enabled = forms.BooleanField(
label=_('Enable application'),
required=False)
+
+
+class ServiceForm(forms.Form):
+ """Generic configuration form for a service."""
+ is_enabled = forms.BooleanField(
+ label=_('Enable application'),
+ required=False)
diff --git a/plinth/modules/avahi/__init__.py b/plinth/modules/avahi/__init__.py
index bf68b952e..1954106fc 100644
--- a/plinth/modules/avahi/__init__.py
+++ b/plinth/modules/avahi/__init__.py
@@ -24,6 +24,7 @@ from django.utils.translation import ugettext_lazy as _
from plinth import cfg
from plinth import service as service_module
from plinth.utils import format_lazy
+from plinth.views import ServiceView
# pylint: disable=C0103
@@ -66,15 +67,6 @@ def setup(helper, old_version=False):
helper.install(['avahi-daemon'])
-def get_status():
- """Get the current settings from server."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
+class AvahiServiceView(ServiceView):
+ service_id = managed_services[0]
+ description = description
diff --git a/plinth/modules/avahi/templates/avahi.html b/plinth/modules/avahi/templates/avahi.html
deleted file mode 100644
index 193a1ac07..000000000
--- a/plinth/modules/avahi/templates/avahi.html
+++ /dev/null
@@ -1,49 +0,0 @@
-{% extends "app.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 configuration %}
-
-
+ {% blocktrans trimmed %}
+ If your Internet provider changes your IP address periodically
+ (i.e. every 24h), it may be hard for others to find you on the
+ Internet. This will prevent others from finding services which are
+ provided by this {box_name}.
+ {% 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 a
+
+ GnuDIP server. Afterwards, the server will assign your DNS name
+ to the new IP, and if someone from the Internet asks for your DNS
+ name, they will get a response with your current IP address.
+ {% endblocktrans %}
+
{% blocktrans trimmed %}
If you are looking for a free dynamic DNS account, you may find
@@ -39,4 +65,5 @@
and TCP port 443 (HTTPS).
{% endblocktrans %}
+
{% endblock %}
diff --git a/plinth/modules/ikiwiki/__init__.py b/plinth/modules/ikiwiki/__init__.py
index 588bb1ba3..f793503de 100644
--- a/plinth/modules/ikiwiki/__init__.py
+++ b/plinth/modules/ikiwiki/__init__.py
@@ -20,7 +20,6 @@ Plinth module to configure ikiwiki
"""
from django.utils.translation import ugettext_lazy as _
-from functools import partial
from plinth import actions
from plinth import action_utils
@@ -32,6 +31,8 @@ version = 1
depends = ['apps']
+service = None
+
title = _('Wiki and Blog (ikiwiki)')
description = [
@@ -39,8 +40,6 @@ description = [
'from [\w.@+-]+)/delete/$', views.delete,
name='delete'),
diff --git a/plinth/modules/ikiwiki/views.py b/plinth/modules/ikiwiki/views.py
index df806bdb6..0f1dffecb 100644
--- a/plinth/modules/ikiwiki/views.py
+++ b/plinth/modules/ikiwiki/views.py
@@ -25,9 +25,11 @@ from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.translation import ugettext as _, ugettext_lazy
-from .forms import IkiwikiCreateForm
from plinth import actions
from plinth import views
+from plinth.modules import ikiwiki
+
+from .forms import IkiwikiCreateForm
subsubmenu = [{'url': reverse_lazy('ikiwiki:index'),
@@ -38,14 +40,18 @@ subsubmenu = [{'url': reverse_lazy('ikiwiki:index'),
'text': ugettext_lazy('Create')}]
-class ConfigurationView(views.ConfigurationView):
+class IkiwikiServiceView(views.ServiceView):
"""Serve configuration page."""
+ service_id = "ikiwiki"
+ template_name = "apache_service.html"
+ diagnostics_module_name = "ikiwiki"
+ description = ikiwiki.description
+
def get_context_data(self, **kwargs):
"""Return the context data for rendering the template view."""
- if 'subsubmenu' not in kwargs:
- kwargs['subsubmenu'] = subsubmenu
-
- return super().get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
+ context['subsubmenu'] = subsubmenu
+ return context
def manage(request):
diff --git a/plinth/modules/minetest/__init__.py b/plinth/modules/minetest/__init__.py
index d628f5b65..4eb04f273 100644
--- a/plinth/modules/minetest/__init__.py
+++ b/plinth/modules/minetest/__init__.py
@@ -25,12 +25,17 @@ from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
from plinth.utils import format_lazy
+from plinth.views import ServiceView
version = 1
depends = ['apps']
+service = None
+
+managed_services = ['minetest-server']
+
title = _('Block Sandbox (Minetest)')
description = [
@@ -42,11 +47,6 @@ description = [
'is needed.'), box_name=_(cfg.box_name)),
]
-service = None
-
-managed_services = ['minetest-server']
-
-
def init():
"""Initialize the module."""
menu = cfg.main_menu.get('apps:index')
@@ -63,18 +63,11 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current service status."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
+class MinetestServiceView(ServiceView):
+ service_id = managed_services[0]
+ template_name = "apache_service.html"
+ diagnostics_module_name = "minetest"
+ description = description
def diagnose():
diff --git a/plinth/modules/minetest/templates/minetest.html b/plinth/modules/minetest/templates/minetest.html
deleted file mode 100644
index 327a91434..000000000
--- a/plinth/modules/minetest/templates/minetest.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Minetest server is running" %}
- {% else %}
-
- {% trans "Minetest server is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="minetest" %}
-
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/minetest/urls.py b/plinth/modules/minetest/urls.py
index a32f5a7c5..2ffa4dc44 100644
--- a/plinth/modules/minetest/urls.py
+++ b/plinth/modules/minetest/urls.py
@@ -21,10 +21,9 @@ URLs for the minetest module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.minetest import MinetestServiceView
urlpatterns = [
- url(r'^apps/minetest/$', ConfigurationView.as_view(module_name='minetest'),
- name='index'),
+ url(r'^apps/minetest/$', MinetestServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/mumble/__init__.py b/plinth/modules/mumble/__init__.py
index 426d2f11c..ff5ea1dba 100644
--- a/plinth/modules/mumble/__init__.py
+++ b/plinth/modules/mumble/__init__.py
@@ -24,6 +24,7 @@ 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.views import ServiceView
version = 1
@@ -32,6 +33,10 @@ depends = ['apps']
title = _('Voice Chat (Mumble)')
+service = None
+
+managed_services = ['mumble-server']
+
description = [
_('Mumble is an open source, low-latency, encrypted, high quality '
'voice chat software.'),
@@ -41,10 +46,6 @@ description = [
'from your desktop and Android devices are available.')
]
-service = None
-
-managed_services = ['mumble-server']
-
def init():
"""Intialize the Mumble module."""
@@ -56,26 +57,18 @@ def init():
managed_services[0], title, is_external=True)
+class MumbleServiceView(ServiceView):
+ service_id = managed_services[0]
+ diagnostics_module_name = "mumble"
+ description = description
+
+
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(['mumble-server'])
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings from server."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
-
-
def diagnose():
"""Run diagnostics and return the results."""
results = []
diff --git a/plinth/modules/mumble/templates/mumble.html b/plinth/modules/mumble/templates/mumble.html
deleted file mode 100644
index 96745dfd8..000000000
--- a/plinth/modules/mumble/templates/mumble.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Mumble server is running" %}
- {% else %}
-
- {% trans "Mumble server is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="mumble" %}
-
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/mumble/urls.py b/plinth/modules/mumble/urls.py
index 77aa97da5..3abd8506d 100644
--- a/plinth/modules/mumble/urls.py
+++ b/plinth/modules/mumble/urls.py
@@ -21,10 +21,9 @@ URLs for the Mumble module
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.mumble import MumbleServiceView
urlpatterns = [
- url(r'^apps/mumble/$', ConfigurationView.as_view(module_name='mumble'),
- name='index'),
+ url(r'^apps/mumble/$', MumbleServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/openvpn/__init__.py b/plinth/modules/openvpn/__init__.py
index 7e8a8e26e..fc1a16f6d 100644
--- a/plinth/modules/openvpn/__init__.py
+++ b/plinth/modules/openvpn/__init__.py
@@ -32,6 +32,10 @@ version = 1
depends = ['apps']
+service = None
+
+managed_services = ['openvpn@freedombox']
+
title = _('Virtual Private Network (OpenVPN)')
description = [
@@ -45,10 +49,6 @@ description = [
'for added security and anonymity.'), box_name=_(cfg.box_name))
]
-service = None
-
-managed_services = ['openvpn@freedombox']
-
def init():
"""Intialize the OpenVPN module."""
diff --git a/plinth/modules/pagekite/__init__.py b/plinth/modules/pagekite/__init__.py
index 6862c1b68..c08ce4f44 100644
--- a/plinth/modules/pagekite/__init__.py
+++ b/plinth/modules/pagekite/__init__.py
@@ -21,7 +21,6 @@ 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
@@ -31,39 +30,6 @@ 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"""
diff --git a/plinth/modules/pagekite/templates/pagekite_introduction.html b/plinth/modules/pagekite/templates/pagekite_introduction.html
index 0961c1923..2f21aadf1 100644
--- a/plinth/modules/pagekite/templates/pagekite_introduction.html
+++ b/plinth/modules/pagekite/templates/pagekite_introduction.html
@@ -1,4 +1,4 @@
-{% extends "app.html" %}
+{% extends "base.html" %}
{% comment %}
#
# This file is part of Plinth.
@@ -20,7 +20,54 @@
{% load i18n %}
-{% block configuration %}
+{% block content %}
+ {{ title }}
+
+
+ {% 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 %}
+
+
+ -
+ {{ box_name }} {% trans "is behind a restricted firewall." %}
+
+ -
+ {{ box_name }}
+ {% blocktrans trimmed %}
+ 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 %}
+
+ -
+ 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 %}
+
diff --git a/plinth/modules/pagekite/views.py b/plinth/modules/pagekite/views.py
index 583f072dc..4c170bf55 100644
--- a/plinth/modules/pagekite/views.py
+++ b/plinth/modules/pagekite/views.py
@@ -42,7 +42,6 @@ def index(request):
"""Serve introduction page"""
return TemplateResponse(request, 'pagekite_introduction.html',
{'title': pagekite.title,
- 'description': pagekite.description,
'subsubmenu': subsubmenu})
diff --git a/plinth/modules/privoxy/__init__.py b/plinth/modules/privoxy/__init__.py
index 3a4e47734..c1335cb1f 100644
--- a/plinth/modules/privoxy/__init__.py
+++ b/plinth/modules/privoxy/__init__.py
@@ -26,6 +26,7 @@ from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
from plinth.utils import format_lazy
+from plinth.views import ServiceView
version = 1
@@ -73,18 +74,10 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings from server."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
+class PrivoxyServiceView(ServiceView):
+ service_id = managed_services[0]
+ diagnostics_module_name = 'privoxy'
+ description = description
def diagnose():
diff --git a/plinth/modules/privoxy/templates/privoxy.html b/plinth/modules/privoxy/templates/privoxy.html
deleted file mode 100644
index d80099eb4..000000000
--- a/plinth/modules/privoxy/templates/privoxy.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Privoxy is running" %}
- {% else %}
-
- {% trans "Privoxy is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="privoxy" %}
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/privoxy/urls.py b/plinth/modules/privoxy/urls.py
index a4a60ca39..7341c3c42 100644
--- a/plinth/modules/privoxy/urls.py
+++ b/plinth/modules/privoxy/urls.py
@@ -21,10 +21,9 @@ URLs for the Privoxy module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.privoxy import PrivoxyServiceView
urlpatterns = [
- url(r'^apps/privoxy/$', ConfigurationView.as_view(module_name='privoxy'),
- name='index'),
+ url(r'^apps/privoxy/$', PrivoxyServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/quassel/__init__.py b/plinth/modules/quassel/__init__.py
index d01ffcb41..2b5ab8c78 100644
--- a/plinth/modules/quassel/__init__.py
+++ b/plinth/modules/quassel/__init__.py
@@ -25,11 +25,16 @@ from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
from plinth.utils import format_lazy
+from plinth.views import ServiceView
version = 1
depends = ['apps']
+service = None
+
+managed_services = ['quasselcore']
+
title = _('IRC Client (Quassel)')
description = [
@@ -49,10 +54,6 @@ description = [
'are available.')
]
-service = None
-
-managed_services = ['quasselcore']
-
def init():
"""Initialize the quassel module."""
@@ -61,7 +62,13 @@ def init():
global service
service = service_module.Service(
- managed_services[0], title, description=description, is_external=True)
+ managed_services[0], title, is_external=True)
+
+
+class QuasselServiceView(ServiceView):
+ service_id = managed_services[0]
+ diagnostics_module_name = "quassel"
+ description = description
def setup(helper, old_version=None):
@@ -70,20 +77,6 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current service status."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
-
-
def diagnose():
"""Run diagnostics and return the results."""
results = []
diff --git a/plinth/modules/quassel/templates/quassel.html b/plinth/modules/quassel/templates/quassel.html
deleted file mode 100644
index 227769463..000000000
--- a/plinth/modules/quassel/templates/quassel.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Quassel core service is running" %}
- {% else %}
-
- {% trans "Quassel core service is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="quassel" %}
-
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/quassel/urls.py b/plinth/modules/quassel/urls.py
index 3fef76f7f..650129d66 100644
--- a/plinth/modules/quassel/urls.py
+++ b/plinth/modules/quassel/urls.py
@@ -21,10 +21,9 @@ URLs for the quassel module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.quassel import QuasselServiceView
urlpatterns = [
- url(r'^apps/quassel/$', ConfigurationView.as_view(module_name='quassel'),
- name='index'),
+ url(r'^apps/quassel/$', QuasselServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/radicale/__init__.py b/plinth/modules/radicale/__init__.py
index 6f595eff3..f103aff19 100644
--- a/plinth/modules/radicale/__init__.py
+++ b/plinth/modules/radicale/__init__.py
@@ -19,8 +19,6 @@
Plinth module for radicale.
"""
-from functools import partial
-
from django.utils.translation import ugettext_lazy as _
from plinth import actions
@@ -28,12 +26,17 @@ from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
from plinth.utils import format_lazy
+from plinth.views import ServiceView
version = 1
depends = ['apps']
+service = None
+
+managed_services = ['radicale']
+
title = _('Calendar and Addressbook (Radicale)')
description = [
@@ -46,10 +49,6 @@ description = [
'login.'), box_name=_(cfg.box_name)),
]
-service = None
-
-managed_services = ['radicale']
-
def init():
"""Initialize the radicale module."""
@@ -59,7 +58,13 @@ def init():
global service
service = service_module.Service(
managed_services[0], title, ports=['http', 'https'], is_external=True,
- enable=_enable, disable=_disable)
+ enable=enable, disable=disable)
+
+
+class RadicaleServiceView(ServiceView):
+ service_id = managed_services[0]
+ diagnostics_module_name = 'radicale'
+ description = description
def setup(helper, old_version=None):
@@ -69,17 +74,14 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current service status."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
+def enable():
+ """Enable the module."""
+ actions.superuser_run('radicale', ['enable'])
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('radicale', [sub_command])
- service.notify_enabled(None, should_enable)
+def disable():
+ """Disable the module."""
+ actions.superuser_run('radicale', ['disable'])
def diagnose():
@@ -92,7 +94,3 @@ def diagnose():
'https://{host}/radicale', extra_options=['--no-check-certificate']))
return results
-
-
-_enable = partial(enable, True)
-_disable = partial(enable, False)
diff --git a/plinth/modules/radicale/templates/radicale.html b/plinth/modules/radicale/templates/radicale.html
deleted file mode 100644
index 088981c63..000000000
--- a/plinth/modules/radicale/templates/radicale.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Radicale service is running" %}
- {% else %}
-
- {% trans "Radicale service is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="radicale" %}
-
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/radicale/urls.py b/plinth/modules/radicale/urls.py
index 55f492662..5a6c873fc 100644
--- a/plinth/modules/radicale/urls.py
+++ b/plinth/modules/radicale/urls.py
@@ -21,10 +21,9 @@ URLs for the radicale module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.radicale import RadicaleServiceView
urlpatterns = [
- url(r'^apps/radicale/$', ConfigurationView.as_view(module_name='radicale'),
- name='index'),
+ url(r'^apps/radicale/$', RadicaleServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/repro/__init__.py b/plinth/modules/repro/__init__.py
index 2ce9faa73..85a25b99f 100644
--- a/plinth/modules/repro/__init__.py
+++ b/plinth/modules/repro/__init__.py
@@ -25,6 +25,7 @@ from plinth import actions
from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
+from plinth.views import ServiceView
version = 1
@@ -69,6 +70,12 @@ def init():
is_external=True)
+class ReproServiceView(ServiceView):
+ service_id = managed_services[0]
+ diagnostics_module_name = "repro"
+ description = description
+
+
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(['repro'])
@@ -76,20 +83,6 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current service status."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
-
-
def diagnose():
"""Run diagnostics and return the results."""
results = []
diff --git a/plinth/modules/repro/templates/repro.html b/plinth/modules/repro/templates/repro.html
deleted file mode 100644
index df5109cb0..000000000
--- a/plinth/modules/repro/templates/repro.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "repro service is running" %}
- {% else %}
-
- {% trans "repro service is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="repro" %}
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/repro/urls.py b/plinth/modules/repro/urls.py
index aeff7f51a..5ca3a301a 100644
--- a/plinth/modules/repro/urls.py
+++ b/plinth/modules/repro/urls.py
@@ -21,10 +21,9 @@ URLs for the repro module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.repro import ReproServiceView
urlpatterns = [
- url(r'^apps/repro/$', ConfigurationView.as_view(module_name='repro'),
- name='index'),
+ url(r'^apps/repro/$', ReproServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/restore/__init__.py b/plinth/modules/restore/__init__.py
index 0ec2a20b3..0ce855940 100644
--- a/plinth/modules/restore/__init__.py
+++ b/plinth/modules/restore/__init__.py
@@ -57,23 +57,9 @@ def init():
global service
service = service_module.Service(
- managed_services[0], title, ports=['http', 'https'], is_external=False,
- description=description)
+ managed_services[0], title, ports=['http', 'https'], is_external=False)
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(['node-restore'])
-
-
-def get_status():
- """Get the current settings."""
- return {'enabled': service.is_enabled()}
-
-
-def enable(should_enable):
- """Enable/disable the module."""
- if should_enable:
- service.enable()
- else:
- service.disable()
diff --git a/plinth/modules/restore/urls.py b/plinth/modules/restore/urls.py
index c51116f8f..e68aa4662 100644
--- a/plinth/modules/restore/urls.py
+++ b/plinth/modules/restore/urls.py
@@ -21,10 +21,13 @@ URLs for the reStore module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.views import ServiceView
+from plinth.modules import restore
urlpatterns = [
- url(r'^apps/restore/$', ConfigurationView.as_view(module_name='restore'),
- name='index'),
+ url(r'^apps/restore/$', ServiceView.as_view(
+ service_id=restore.managed_services[0],
+ description=restore.description
+ ), name='index'),
]
diff --git a/plinth/modules/roundcube/__init__.py b/plinth/modules/roundcube/__init__.py
index 9e86c6821..58e2fda28 100644
--- a/plinth/modules/roundcube/__init__.py
+++ b/plinth/modules/roundcube/__init__.py
@@ -24,6 +24,7 @@ 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
@@ -54,12 +55,19 @@ description = [
'>https://www.google.com/settings/security/lesssecureapps).'),
]
+service = None
+
def init():
"""Intialize the module."""
menu = cfg.main_menu.get('apps:index')
menu.add_urlname(title, 'glyphicon-envelope', 'roundcube:index', 600)
+ global service
+ service = service_module.Service(
+ 'roundcube', title, ports=['http', 'https'], is_external=True,
+ is_enabled=is_enabled, enable=enable, disable=disable)
+
def setup(helper, old_version=None):
"""Install and configure the module."""
@@ -68,20 +76,19 @@ def setup(helper, old_version=None):
helper.call('post', actions.superuser_run, 'roundcube', ['setup'])
-def get_status():
- """Get the current status."""
- return {'enabled': is_enabled()}
-
-
def is_enabled():
"""Return whether the module is enabled."""
return action_utils.webserver_is_enabled('roundcube')
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('roundcube', [sub_command])
+def enable():
+ """Enable the module."""
+ actions.superuser_run('roundcube', ['enable'])
+
+
+def disable():
+ """Enable the module."""
+ actions.superuser_run('roundcube', ['disable'])
def diagnose():
diff --git a/plinth/modules/roundcube/templates/roundcube.html b/plinth/modules/roundcube/templates/roundcube.html
deleted file mode 100644
index 6028f7aba..000000000
--- a/plinth/modules/roundcube/templates/roundcube.html
+++ /dev/null
@@ -1,39 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% include "diagnostics_button.html" with module="roundcube" %}
-
-
{% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/roundcube/urls.py b/plinth/modules/roundcube/urls.py
index a307cce07..be4edd836 100644
--- a/plinth/modules/roundcube/urls.py
+++ b/plinth/modules/roundcube/urls.py
@@ -21,10 +21,15 @@ URLs for the Roundcube module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.views import ServiceView
+from plinth.modules import roundcube
urlpatterns = [
- url(r'^apps/roundcube/$',
- ConfigurationView.as_view(module_name='roundcube'), name='index'),
+ url(r'^apps/roundcube/$', ServiceView.as_view(
+ service_id="roundcube",
+ diagnostics_module_name="roundcube",
+ description=roundcube.description,
+ template_name="apache_service.html"
+ ), name='index'),
]
diff --git a/plinth/modules/shaarli/__init__.py b/plinth/modules/shaarli/__init__.py
index febc84776..0fdcecfa6 100644
--- a/plinth/modules/shaarli/__init__.py
+++ b/plinth/modules/shaarli/__init__.py
@@ -19,8 +19,6 @@
Plinth module to configure Shaarli.
"""
-from functools import partial
-
from django.utils.translation import ugettext_lazy as _
from plinth import actions
@@ -55,7 +53,7 @@ def init():
global service
service = service_module.Service(
'shaarli', title, ports=['http', 'https'], is_external=True,
- is_enabled=is_enabled, enable=_enable, disable=_disable)
+ is_enabled=is_enabled, enable=enable, disable=disable)
def setup(helper, old_version=None):
@@ -64,22 +62,17 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings."""
- return {'enabled': service.is_enabled()}
-
def is_enabled():
"""Return whether the module is enabled."""
return action_utils.webserver_is_enabled('shaarli')
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('shaarli', [sub_command])
- service.notify_enabled(None, should_enable)
+def enable():
+ """Enable the module."""
+ actions.superuser_run('shaarli', ['enable'])
-_enable = partial(enable, True)
-_disable = partial(enable, False)
+def disable():
+ """Enable the module."""
+ actions.superuser_run('shaarli', ['disable'])
diff --git a/plinth/modules/shaarli/templates/shaarli.html b/plinth/modules/shaarli/templates/shaarli.html
deleted file mode 100644
index 9a6c55b0a..000000000
--- a/plinth/modules/shaarli/templates/shaarli.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/shaarli/urls.py b/plinth/modules/shaarli/urls.py
index b75250a3d..236c00ee8 100644
--- a/plinth/modules/shaarli/urls.py
+++ b/plinth/modules/shaarli/urls.py
@@ -21,10 +21,14 @@ URLs for the Shaarli module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.views import ServiceView
+from plinth.modules import shaarli
urlpatterns = [
- url(r'^apps/shaarli/$', ConfigurationView.as_view(module_name='shaarli'),
- name='index'),
+ url(r'^apps/shaarli/$', ServiceView.as_view(
+ service_id="shaarli",
+ description=shaarli.description,
+ template_name="apache_service.html"
+ ), name='index'),
]
diff --git a/plinth/modules/tor/__init__.py b/plinth/modules/tor/__init__.py
index 71bef6a0b..78fcd7b68 100644
--- a/plinth/modules/tor/__init__.py
+++ b/plinth/modules/tor/__init__.py
@@ -19,10 +19,7 @@
Plinth module to configure Tor.
"""
-import augeas
from django.utils.translation import ugettext_lazy as _
-import glob
-import itertools
import json
from plinth import actions
@@ -32,6 +29,8 @@ from plinth import service as service_module
from plinth.modules.names import SERVICES
from plinth.signals import domain_added, domain_removed
+from . import utils as tor_utils
+
version = 1
@@ -73,7 +72,7 @@ def init():
is_external=True, is_enabled=is_enabled, is_running=is_running)
# Register hidden service name with Name Services module.
- hs_info = get_hs()
+ hs_info = tor_utils.get_hs()
hostname = hs_info['hostname']
hs_virtports = [port['virtport'] for port in hs_info['ports']]
@@ -108,7 +107,7 @@ def setup(helper, old_version=None):
def update_hidden_service_domain(status=None):
"""Update HS domain with Name Services module."""
if not status:
- status = get_status()
+ status = tor_utils.get_status()
domain_removed.send_robust(
sender='tor', domain_type='hiddenservice')
@@ -131,107 +130,6 @@ def is_running():
return action_utils.service_is_running('tor')
-def get_status():
- """Return current Tor status."""
- output = actions.superuser_run('tor', ['get-ports'])
- ports = json.loads(output)
-
- hs_info = get_hs()
- hs_services = []
- hs_virtports = [port['virtport'] for port in hs_info['ports']]
- for service_type in SERVICES:
- if str(service_type[2]) in hs_virtports:
- hs_services.append(service_type[0])
-
- return {'enabled': is_enabled(),
- 'is_running': is_running(),
- 'ports': ports,
- 'hs_enabled': hs_info['enabled'],
- 'hs_status': hs_info['status'],
- 'hs_hostname': hs_info['hostname'],
- 'hs_ports': hs_info['ports'],
- 'hs_services': hs_services,
- 'apt_transport_tor_enabled': is_apt_transport_tor_enabled()}
-
-
-def get_hs():
- """Return hidden service status."""
- output = actions.superuser_run('tor', ['get-hs'])
- return json.loads(output)
-
-
-def get_augeas():
- """Return an instance of Augeaus for processing APT configuration."""
- aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
- augeas.Augeas.NO_MODL_AUTOLOAD)
- aug.set('/augeas/load/Aptsources/lens', 'Aptsources.lns')
- aug.set('/augeas/load/Aptsources/incl[last() + 1]', '/etc/apt/sources.list')
- aug.set('/augeas/load/Aptsources/incl[last() + 1]',
- '/etc/apt/sources.list.d/*.list')
- aug.load()
-
- # Currently, augeas does not handle Deb822 format, it error out.
- if aug.match('/augeas/files/etc/apt/sources.list/error') or \
- aug.match('/augeas/files/etc/apt/sources.list.d//error'):
- raise Exception('Error parsing sources list')
-
- # Starting with Apt 1.1, /etc/apt/sources.list.d/*.sources will
- # contain files with Deb822 format. If they are found, error out
- # for now. XXX: Provide proper support Deb822 format with a new
- # Augeas lens.
- if glob.glob('/etc/apt/sources.list.d/*.sources'):
- raise Exception('Can not handle Deb822 source files')
-
- return aug
-
-
-def iter_apt_uris(aug):
- """Iterate over all the APT source URIs."""
- return itertools.chain.from_iterable([aug.match(path)
- for path in APT_SOURCES_URI_PATHS])
-
-
-def get_real_apt_uri_path(aug, path):
- """Return the actual path which contains APT URL.
-
- XXX: This is a workaround for Augeas bug parsing Apt source files
- with '[options]'. Remove this workaround after Augeas lens is
- fixed.
- """
- uri = aug.get(path)
- if uri[0] == '[':
- parent_path = path.rsplit('/', maxsplit=1)[0]
- skipped = False
- for child_path in aug.match(parent_path + '/*')[1:]:
- if skipped:
- return child_path
-
- value = aug.get(child_path)
- if value[-1] == ']':
- skipped = True
-
- return path
-
-
-def is_apt_transport_tor_enabled():
- """Return whether APT is set to download packages over Tor."""
- try:
- aug = get_augeas()
- except Exception:
- # If there was an error with parsing or there are Deb822
- # files.
- return False
-
- for uri_path in iter_apt_uris(aug):
- uri_path = get_real_apt_uri_path(aug, uri_path)
- uri = aug.get(uri_path)
- if not uri.startswith(APT_TOR_PREFIX) and \
- (uri.startswith('http://') or uri.startswith('https://')):
- return False
-
- return True
-
-
def diagnose():
"""Run diagnostics and return the results."""
results = []
diff --git a/plinth/modules/tor/utils.py b/plinth/modules/tor/utils.py
new file mode 100644
index 000000000..7f7e649b1
--- /dev/null
+++ b/plinth/modules/tor/utils.py
@@ -0,0 +1,132 @@
+#
+# 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 .
+#
+
+"""
+Tor utility functions
+"""
+
+import augeas
+import glob
+import itertools
+import json
+
+from plinth import actions
+from plinth.modules import tor
+from plinth.modules.names import SERVICES
+
+
+def get_hs():
+ """Return hidden service status."""
+ output = actions.superuser_run('tor', ['get-hs'])
+ return json.loads(output)
+
+
+def get_status():
+ """Return current Tor status."""
+ output = actions.superuser_run('tor', ['get-ports'])
+ ports = json.loads(output)
+
+ hs_info = get_hs()
+ hs_services = []
+ hs_virtports = [port['virtport'] for port in hs_info['ports']]
+ for service_type in SERVICES:
+ if str(service_type[2]) in hs_virtports:
+ hs_services.append(service_type[0])
+
+ return {'enabled': tor.is_enabled(),
+ 'is_running': tor.is_running(),
+ 'ports': ports,
+ 'hs_enabled': hs_info['enabled'],
+ 'hs_status': hs_info['status'],
+ 'hs_hostname': hs_info['hostname'],
+ 'hs_ports': hs_info['ports'],
+ 'hs_services': hs_services,
+ 'apt_transport_tor_enabled': \
+ _is_apt_transport_tor_enabled()
+ }
+
+
+def iter_apt_uris(aug):
+ """Iterate over all the APT source URIs."""
+ return itertools.chain.from_iterable([aug.match(path) for \
+ path in tor.APT_SOURCES_URI_PATHS])
+
+
+def get_real_apt_uri_path(aug, path):
+ """Return the actual path which contains APT URL.
+
+ XXX: This is a workaround for Augeas bug parsing Apt source files
+ with '[options]'. Remove this workaround after Augeas lens is
+ fixed.
+ """
+ uri = aug.get(path)
+ if uri[0] == '[':
+ parent_path = path.rsplit('/', maxsplit=1)[0]
+ skipped = False
+ for child_path in aug.match(parent_path + '/*')[1:]:
+ if skipped:
+ return child_path
+
+ value = aug.get(child_path)
+ if value[-1] == ']':
+ skipped = True
+
+ return path
+
+
+def get_augeas():
+ """Return an instance of Augeaus for processing APT configuration."""
+ aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
+ augeas.Augeas.NO_MODL_AUTOLOAD)
+ aug.set('/augeas/load/Aptsources/lens', 'Aptsources.lns')
+ aug.set('/augeas/load/Aptsources/incl[last() + 1]', '/etc/apt/sources.list')
+ aug.set('/augeas/load/Aptsources/incl[last() + 1]',
+ '/etc/apt/sources.list.d/*.list')
+ aug.load()
+
+ # Currently, augeas does not handle Deb822 format, it error out.
+ if aug.match('/augeas/files/etc/apt/sources.list/error') or \
+ aug.match('/augeas/files/etc/apt/sources.list.d//error'):
+ raise Exception('Error parsing sources list')
+
+ # Starting with Apt 1.1, /etc/apt/sources.list.d/*.sources will
+ # contain files with Deb822 format. If they are found, error out
+ # for now. XXX: Provide proper support Deb822 format with a new
+ # Augeas lens.
+ if glob.glob('/etc/apt/sources.list.d/*.sources'):
+ raise Exception('Can not handle Deb822 source files')
+
+ return aug
+
+
+def _is_apt_transport_tor_enabled():
+ """Return whether APT is set to download packages over Tor."""
+ try:
+ aug = get_augeas()
+ except Exception:
+ # If there was an error with parsing or there are Deb822
+ # files.
+ return False
+
+ for uri_path in iter_apt_uris(aug):
+ uri_path = get_real_apt_uri_path(aug, uri_path)
+ uri = aug.get(uri_path)
+ if not uri.startswith(tor.APT_TOR_PREFIX) and \
+ (uri.startswith('http://') or uri.startswith('https://')):
+ return False
+
+ return True
diff --git a/plinth/modules/tor/views.py b/plinth/modules/tor/views.py
index fc9f42f2b..34bfdf1c3 100644
--- a/plinth/modules/tor/views.py
+++ b/plinth/modules/tor/views.py
@@ -18,16 +18,17 @@
"""
Plinth module for configuring Tor.
"""
-
from django.contrib import messages
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _
+from . import utils as tor_utils
from .forms import TorForm
from plinth import actions
from plinth.errors import ActionError
from plinth.modules import tor
+
config_process = None
@@ -36,7 +37,7 @@ def index(request):
if config_process:
_collect_config_result(request)
- status = tor.get_status()
+ status = tor_utils.get_status()
form = None
if request.method == 'POST':
@@ -44,7 +45,7 @@ def index(request):
# pylint: disable=E1101
if form.is_valid():
_apply_changes(request, status, form.cleaned_data)
- status = tor.get_status()
+ status = tor_utils.get_status()
form = TorForm(initial=status, prefix='tor')
else:
form = TorForm(initial=status, prefix='tor')
@@ -109,7 +110,7 @@ def _collect_config_result(request):
if return_code is None:
return
- status = tor.get_status()
+ status = tor_utils.get_status()
tor.socks_service.notify_enabled(None, status['enabled'])
tor.bridge_service.notify_enabled(None, status['enabled'])
diff --git a/plinth/modules/transmission/__init__.py b/plinth/modules/transmission/__init__.py
index 255063f21..c46210f2d 100644
--- a/plinth/modules/transmission/__init__.py
+++ b/plinth/modules/transmission/__init__.py
@@ -20,9 +20,7 @@ Plinth module to configure Transmission server
"""
from django.utils.translation import ugettext_lazy as _
-from functools import partial
import json
-import socket
from plinth import actions
from plinth import action_utils
@@ -39,7 +37,8 @@ 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.')
+ 'BitTorrent is not anonymous.'),
+ _('Access the web interface at /transmission.')
]
service = None
@@ -57,8 +56,7 @@ def init():
global service
service = service_module.Service(
managed_services[0], title, ports=['http', 'https'], is_external=True,
- is_enabled=is_enabled, enable=_enable, disable=_disable,
- description=description)
+ is_enabled=is_enabled, enable=enable, disable=disable)
def setup(helper, old_version=None):
@@ -74,30 +72,20 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings from Transmission server."""
- configuration = open(TRANSMISSION_CONFIG, 'r').read()
- status = json.loads(configuration)
- status = {key.translate(str.maketrans({'-': '_'})): value
- for key, value in status.items()}
- status['enabled'] = is_enabled()
- status['is_running'] = service.is_running()
- status['hostname'] = socket.gethostname()
-
- return status
-
-
def is_enabled():
"""Return whether the module is enabled."""
return (action_utils.service_is_enabled('transmission-daemon') and
action_utils.webserver_is_enabled('transmission-plinth'))
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('transmission', [sub_command])
- service.notify_enabled(None, should_enable)
+def enable():
+ """Enable the module."""
+ actions.superuser_run('transmission', ['enable'])
+
+
+def disable():
+ """Enable the module."""
+ actions.superuser_run('transmission', ['disable'])
def diagnose():
@@ -111,7 +99,3 @@ def diagnose():
extra_options=['--no-check-certificate']))
return results
-
-
-_enable = partial(enable, True)
-_disable = partial(enable, False)
diff --git a/plinth/modules/transmission/forms.py b/plinth/modules/transmission/forms.py
index 6eaa42928..d9ade27bf 100644
--- a/plinth/modules/transmission/forms.py
+++ b/plinth/modules/transmission/forms.py
@@ -22,10 +22,10 @@ Plinth module for configuring Transmission.
from django import forms
from django.utils.translation import ugettext_lazy as _
-from plinth.forms import ConfigurationForm
+from plinth.forms import ServiceForm
-class TransmissionForm(ConfigurationForm): # pylint: disable=W0232
+class TransmissionForm(ServiceForm): # pylint: disable=W0232
"""Transmission configuration form"""
download_dir = forms.CharField(
label=_('Download directory'),
diff --git a/plinth/modules/transmission/templates/transmission.html b/plinth/modules/transmission/templates/transmission.html
deleted file mode 100644
index 0adf4648f..000000000
--- a/plinth/modules/transmission/templates/transmission.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{% extends "app.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 configuration %}
-
-
- {% blocktrans trimmed %}
- Access the web interface at /transmission.
- {% endblocktrans %}
-
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Transmission daemon is running" %}
- {% else %}
-
- {% trans "Transmission daemon is not running." %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="transmission" %}
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/transmission/urls.py b/plinth/modules/transmission/urls.py
index 1c0a7dddb..c7c046bc3 100644
--- a/plinth/modules/transmission/urls.py
+++ b/plinth/modules/transmission/urls.py
@@ -21,11 +21,10 @@ URLs for the Transmission module.
from django.conf.urls import url
-from .views import TransmissionConfigurationView
+from .views import TransmissionServiceView
urlpatterns = [
url(r'^apps/transmission/$',
- TransmissionConfigurationView.as_view(module_name='transmission'),
- name='index'),
+ TransmissionServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/transmission/views.py b/plinth/modules/transmission/views.py
index 30dafc9f4..781c87e97 100644
--- a/plinth/modules/transmission/views.py
+++ b/plinth/modules/transmission/views.py
@@ -23,21 +23,39 @@ from django.contrib import messages
from django.utils.translation import ugettext as _
import json
import logging
+import socket
from .forms import TransmissionForm
from plinth import actions
from plinth import views
+from plinth.modules import transmission
logger = logging.getLogger(__name__)
-class TransmissionConfigurationView(views.ConfigurationView):
+class TransmissionServiceView(views.ServiceView):
"""Serve configuration page."""
+ description = transmission.description
+ diagnostics_module_name = 'transmission'
form_class = TransmissionForm
+ service_id = transmission.managed_services[0]
- def apply_changes(self, old_status, new_status):
+ def get_initial(self):
+ """Get the current settings from Transmission server."""
+ configuration = open(transmission.TRANSMISSION_CONFIG, 'r').read()
+ status = json.loads(configuration)
+ status = {key.translate(str.maketrans({'-': '_'})): value
+ for key, value in status.items()}
+ status['is_enabled'] = self.service.is_enabled()
+ status['is_running'] = self.service.is_running()
+ status['hostname'] = socket.gethostname()
+
+ return status
+
+ def form_valid(self, form):
"""Apply the changes submitted in the form."""
- modified = super().apply_changes(old_status, new_status)
+ old_status = form.initial
+ new_status = form.cleaned_data
if old_status['download_dir'] != new_status['download_dir'] or \
old_status['rpc_username'] != new_status['rpc_username'] or \
@@ -51,6 +69,5 @@ class TransmissionConfigurationView(views.ConfigurationView):
actions.superuser_run('transmission', ['merge-configuration'],
input=json.dumps(new_configuration).encode())
messages.success(self.request, _('Configuration updated'))
- return True
- return modified
+ return super().form_valid(form)
diff --git a/plinth/modules/ttrss/__init__.py b/plinth/modules/ttrss/__init__.py
index 42d240e5d..ba163efc6 100644
--- a/plinth/modules/ttrss/__init__.py
+++ b/plinth/modules/ttrss/__init__.py
@@ -20,7 +20,6 @@ Plinth module to configure Tiny Tiny RSS.
"""
from django.utils.translation import ugettext_lazy as _
-from functools import partial
from plinth import actions
from plinth import action_utils
@@ -56,7 +55,7 @@ def init():
global service
service = service_module.Service(
managed_services[0], title, ports=['http', 'https'], is_external=True,
- is_enabled=is_enabled, enable=_enable, disable=_disable)
+ is_enabled=is_enabled, enable=enable, disable=disable)
def setup(helper, old_version=None):
@@ -67,23 +66,20 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings."""
- return {'enabled': is_enabled(),
- 'is_running': service.is_running()}
-
-
def is_enabled():
"""Return whether the module is enabled."""
return (action_utils.service_is_enabled('tt-rss') and
action_utils.webserver_is_enabled('tt-rss-plinth'))
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('ttrss', [sub_command])
- service.notify_enabled(None, should_enable)
+def enable():
+ """Enable the module."""
+ actions.superuser_run('ttrss', ['enable'])
+
+
+def disable():
+ """Enable the module."""
+ actions.superuser_run('ttrss', ['disable'])
def diagnose():
@@ -94,7 +90,3 @@ def diagnose():
'https://{host}/tt-rss', extra_options=['--no-check-certificate']))
return results
-
-
-_enable = partial(enable, True)
-_disable = partial(enable, False)
diff --git a/plinth/modules/ttrss/templates/ttrss.html b/plinth/modules/ttrss/templates/ttrss.html
deleted file mode 100644
index 4cd57ebac..000000000
--- a/plinth/modules/ttrss/templates/ttrss.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "app.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 configuration %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "Tiny Tiny RSS feed update service is running" %}
- {% else %}
-
- {% trans "Tiny Tiny RSS feed update service is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="ttrss" %}
-
-
- {% trans "Configuration" %}
-
-
-
-{% endblock %}
diff --git a/plinth/modules/ttrss/urls.py b/plinth/modules/ttrss/urls.py
index d758af068..34413e569 100644
--- a/plinth/modules/ttrss/urls.py
+++ b/plinth/modules/ttrss/urls.py
@@ -21,10 +21,15 @@ URLs for the Tiny Tiny RSS module.
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.views import ServiceView
+from plinth.modules import ttrss
urlpatterns = [
- url(r'^apps/ttrss/$', ConfigurationView.as_view(module_name='ttrss'),
- name='index'),
+ url(r'^apps/ttrss/$', ServiceView.as_view(
+ service_id=ttrss.managed_services[0],
+ diagnostics_module_name="ttrss",
+ description=ttrss.description,
+ template_name="apache_service.html"
+ ), name='index'),
]
diff --git a/plinth/modules/upgrades/__init__.py b/plinth/modules/upgrades/__init__.py
index e8e84ac38..afecac836 100644
--- a/plinth/modules/upgrades/__init__.py
+++ b/plinth/modules/upgrades/__init__.py
@@ -23,6 +23,7 @@ from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth import cfg
+from plinth import service as service_module
version = 1
@@ -39,11 +40,17 @@ description = [
'night. You don\'t normally need to start the upgrade process.')
]
+service = None
+
def init():
"""Initialize the module."""
menu = cfg.main_menu.get('system:index')
menu.add_urlname(title, 'glyphicon-refresh', 'upgrades:index', 21)
+ global service
+ service = service_module.Service(
+ 'auto-upgrades', title, is_external=False, is_enabled=is_enabled,
+ enable=enable, disable=disable)
def setup(helper, old_version=None):
@@ -52,18 +59,17 @@ def setup(helper, old_version=None):
helper.call('post', actions.superuser_run, 'upgrades', ['enable-auto'])
-def get_status():
- """Return the current status."""
- return {'auto_upgrades_enabled': 'is_enabled'}
-
-
def is_enabled():
"""Return whether the module is enabled."""
output = actions.run('upgrades', ['check-auto'])
return 'True' in output.split()
-def enable(should_enable):
- """Enable/disable the module."""
- option = 'enable-auto' if should_enable else 'disable-auto'
- actions.superuser_run('upgrades', [option])
+def enable():
+ """Enable the module."""
+ actions.superuser_run('upgrades', ['enable-auto'])
+
+
+def disable():
+ """Disable the module."""
+ actions.superuser_run('upgrades', ['disable-auto'])
diff --git a/plinth/modules/upgrades/urls.py b/plinth/modules/upgrades/urls.py
index eba0e70b8..afc0576e0 100644
--- a/plinth/modules/upgrades/urls.py
+++ b/plinth/modules/upgrades/urls.py
@@ -25,7 +25,7 @@ from . import views
urlpatterns = [
- url(r'^sys/upgrades/$',
- views.ConfigurationView.as_view(module_name='upgrades'), name='index'),
+ url(r'^sys/upgrades/$', views.UpgradesConfigurationView.as_view(),
+ name='index'),
url(r'^sys/upgrades/upgrade/$', views.upgrade, name='upgrade'),
]
diff --git a/plinth/modules/upgrades/views.py b/plinth/modules/upgrades/views.py
index 37b63bc82..857fb2070 100644
--- a/plinth/modules/upgrades/views.py
+++ b/plinth/modules/upgrades/views.py
@@ -23,11 +23,11 @@ from django.contrib import messages
from django.core.urlresolvers import reverse_lazy
from django.template.response import TemplateResponse
from django.utils.translation import ugettext as _, ugettext_lazy
+from django.views.generic.edit import FormView
import subprocess
from .forms import ConfigureForm
from plinth import actions
-from plinth import views
from plinth.errors import ActionError
from plinth.modules import upgrades
@@ -40,43 +40,52 @@ LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log'
LOCK_FILE = '/var/log/dpkg/lock'
-class ConfigurationView(views.ConfigurationView):
+class UpgradesConfigurationView(FormView):
"""Serve configuration page."""
form_class = ConfigureForm
+ success_url = reverse_lazy('upgrades:index')
+ template_name = "upgrades_configure.html"
- def get_context_data(self, **kwargs):
+ def get_context_data(self, *args, **kwargs):
"""Return the context data for rendering the template view."""
- if 'subsubmenu' not in kwargs:
- kwargs['subsubmenu'] = subsubmenu
+ context = super().get_context_data(*args, **kwargs)
+ context['subsubmenu'] = subsubmenu
+ context['title'] = upgrades.title
+ context['description'] = upgrades.description
+ return context
- return super().get_context_data(**kwargs)
+ def get_initial(self):
+ return {'auto_upgrades_enabled': upgrades.service.is_enabled()}
- def get_template_names(self):
- """Return the list of template names for the view."""
- return ['upgrades_configure.html']
-
- def apply_changes(self, old_status, new_status):
+ def form_valid(self, form):
"""Apply the form changes."""
+ old_status = form.initial
+ new_status = form.cleaned_data
+
if old_status['auto_upgrades_enabled'] \
- == new_status['auto_upgrades_enabled']:
- return False
+ != new_status['auto_upgrades_enabled']:
- try:
- upgrades.enable(new_status['auto_upgrades_enabled'])
- except ActionError as exception:
- error = exception.args[2]
- messages.error(
- self.request,
- _('Error when configuring unattended-upgrades: {error}')
- .format(error=error))
- return True
+ try:
+ if new_status['auto_upgrades_enabled']:
+ upgrades.service.enable()
+ else:
+ upgrades.service.disable()
+ except ActionError as exception:
+ error = exception.args[2]
+ messages.error(
+ self.request,
+ _('Error when configuring unattended-upgrades: {error}')
+ .format(error=error))
- if new_status['auto_upgrades_enabled']:
- messages.success(self.request, _('Automatic upgrades enabled'))
+ if new_status['auto_upgrades_enabled']:
+ messages.success(self.request, _('Automatic upgrades enabled'))
+ else:
+ messages.success(self.request, _('Automatic upgrades disabled'))
else:
- messages.success(self.request, _('Automatic upgrades disabled'))
+ messages.info(self.request, _('Settings unchanged'))
+
+ return super().form_valid(form)
- return True
def is_package_manager_busy():
diff --git a/plinth/modules/xmpp/__init__.py b/plinth/modules/xmpp/__init__.py
index 8602b7146..dd97dbe76 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 _
-from functools import partial
import logging
import socket
@@ -28,6 +27,7 @@ from plinth import actions
from plinth import action_utils
from plinth import cfg
from plinth import service as service_module
+from plinth.views import ServiceView
from plinth.signals import pre_hostname_change, post_hostname_change
from plinth.signals import domainname_change
@@ -63,8 +63,7 @@ def init():
global service
service = service_module.Service(
'ejabberd', title, ports=['xmpp-client', 'xmpp-server', 'xmpp-bosh'],
- is_external=True, is_enabled=is_enabled, enable=_enable,
- disable=_disable)
+ is_external=True, is_enabled=is_enabled, enable=enable, disable=disable)
pre_hostname_change.connect(on_pre_hostname_change)
post_hostname_change.connect(on_post_hostname_change)
@@ -83,11 +82,16 @@ def setup(helper, old_version=None):
helper.call('post', service.notify_enabled, None, True)
-def get_status():
- """Get the current settings."""
- return {'enabled': service.is_enabled(),
- 'is_running': service.is_running(),
- 'domainname': get_domainname()}
+class EjabberdServiceView(ServiceView):
+ service_id = managed_services[0]
+ template_name = "xmpp.html"
+ description = description
+ diagnostics_module_name = "xmpp"
+
+ def get_context_data(self, *args, **kwargs):
+ context = super().get_context_data(*args, **kwargs)
+ context['domainname'] = get_domainname()
+ return context
def is_enabled():
@@ -102,11 +106,14 @@ def get_domainname():
return '.'.join(fqdn.split('.')[1:])
-def enable(should_enable):
- """Enable/disable the module."""
- sub_command = 'enable' if should_enable else 'disable'
- actions.superuser_run('xmpp', [sub_command])
- service.notify_enabled(None, should_enable)
+def enable():
+ """Enable the module."""
+ actions.superuser_run('xmpp', ['enable'])
+
+
+def disable():
+ """Enable the module."""
+ actions.superuser_run('xmpp', ['disable'])
def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs):
@@ -163,7 +170,3 @@ def diagnose():
results.extend(action_utils.diagnose_url_on_all('http://{host}/jwchat'))
return results
-
-
-_enable = partial(enable, True)
-_disable = partial(enable, False)
diff --git a/plinth/modules/xmpp/templates/xmpp.html b/plinth/modules/xmpp/templates/xmpp.html
index 31e387be2..702a883e7 100644
--- a/plinth/modules/xmpp/templates/xmpp.html
+++ b/plinth/modules/xmpp/templates/xmpp.html
@@ -1,4 +1,4 @@
-{% extends "app.html" %}
+{% extends "service.html" %}
{% comment %}
#
# This file is part of Plinth.
@@ -18,10 +18,13 @@
#
{% endcomment %}
-{% load bootstrap %}
{% load i18n %}
-{% block configuration %}
+{% block description %}
+
+ {% for paragraph in description %}
+ {{ paragraph|safe }}
+ {% endfor %}
{% url 'config:index' as index_url %}
@@ -37,30 +40,4 @@
{% trans "Launch web client" %}
-
- {% trans "Status" %}
-
-
- {% if status.is_running %}
-
- {% trans "ejabberd is running" %}
- {% else %}
-
- {% trans "ejabberd is not running" %}
- {% endif %}
-
-
- {% include "diagnostics_button.html" with module="xmpp" %}
-
- {% trans "Configuration" %}
-
-
-
{% endblock %}
diff --git a/plinth/modules/xmpp/urls.py b/plinth/modules/xmpp/urls.py
index 36e86a7c3..b3efc5252 100644
--- a/plinth/modules/xmpp/urls.py
+++ b/plinth/modules/xmpp/urls.py
@@ -21,10 +21,9 @@ URLs for the XMPP module
from django.conf.urls import url
-from plinth.views import ConfigurationView
+from plinth.modules.xmpp import EjabberdServiceView
urlpatterns = [
- url(r'^apps/xmpp/$', ConfigurationView.as_view(module_name='xmpp'),
- name='index'),
+ url(r'^apps/xmpp/$', EjabberdServiceView.as_view(), name='index'),
]
diff --git a/plinth/modules/restore/templates/restore.html b/plinth/templates/apache_service.html
similarity index 70%
rename from plinth/modules/restore/templates/restore.html
rename to plinth/templates/apache_service.html
index 4148a047c..991405822 100644
--- a/plinth/modules/restore/templates/restore.html
+++ b/plinth/templates/apache_service.html
@@ -1,4 +1,4 @@
-{% extends "app.html" %}
+{% extends "service.html" %}
{% comment %}
#
# This file is part of Plinth.
@@ -18,20 +18,11 @@
#
{% endcomment %}
-{% load bootstrap %}
-{% load i18n %}
-
-{% block configuration %}
-
- Configuration
-
-
+{% comment %}
+# The status block uses is_running, but apache services by default don't
+# provide this method. We could use is_enabled instead, but this information is
+# available in the enable/disable checkbox already. So omit it altogether.
+{% endcomment %}
+{% block status %}
{% endblock %}
diff --git a/plinth/templates/service.html b/plinth/templates/service.html
new file mode 100644
index 000000000..92940e757
--- /dev/null
+++ b/plinth/templates/service.html
@@ -0,0 +1,67 @@
+{% 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 content %}
+ {% block pagetitle %}
+ {{ service.name }}
+ {% endblock %}
+
+ {% block description %}
+ {% for paragraph in description %}
+ {{ paragraph|safe }}
+ {% endfor %}
+ {% endblock %}
+
+ {% block status %}
+ Status
+
+ {% if service.is_running %}
+
+ Service {{ service.name }} {% trans "is running" %}
+ {% else %}
+
+ Service {{ service.name }} {% trans "is not running" %}
+ {% endif %}
+
+ {% endblock %}
+
+ {% block diagnostics %}
+ {% if diagnostics_module_name %}
+ {% include "diagnostics_button.html" with module=diagnostics_module_name %}
+ {% endif %}
+ {% endblock %}
+
+ {% block configuration %}
+ Configuration
+
+
+ {% endblock %}
+
+{% endblock %}
diff --git a/plinth/views.py b/plinth/views.py
index 669506d0c..efcad2f9a 100644
--- a/plinth/views.py
+++ b/plinth/views.py
@@ -37,6 +37,71 @@ def index(request):
return HttpResponseRedirect(reverse('apps:index'))
+class ServiceView(FormView):
+ """A generic view for configuring simple modules."""
+ service_id = None
+ form_class = forms.ServiceForm
+ template_name = 'service.html'
+ # Set diagnostics_module_name to the module name to show diagnostics button
+ diagnostics_module_name = ""
+ # List of paragraphs describing the service
+ description = ""
+
+ @property
+ def success_url(self):
+ return self.request.path
+
+ @property
+ def service(self):
+ if hasattr(self, '_service'):
+ return self._service
+
+ if not self.service_id:
+ raise ImproperlyConfigured("missing attribute: 'service_id'")
+ service = plinth.service.services.get(self.service_id, None)
+ if service is None:
+ message = "Could not find service %s" % self.service_id
+ raise ImproperlyConfigured(message)
+ self._service = service
+ return service
+
+ def get_initial(self):
+ """Return the status of the module to fill in the form."""
+ return {'is_enabled': self.service.is_enabled(),
+ 'is_running': self.service.is_running()}
+
+ def form_valid(self, form):
+ """Enable/disable a service and set messages."""
+ old_status = form.initial
+ new_status = form.cleaned_data
+
+ if old_status['is_enabled'] == new_status['is_enabled']:
+ # TODO: find a more reliable/official way to check whether the
+ # request has messages attached.
+ if not self.request._messages._queued_messages:
+ messages.info(self.request, _('Setting unchanged'))
+ else:
+ if new_status['is_enabled']:
+ self.service.enable()
+ messages.success(self.request, _('Application enabled'))
+ else:
+ self.service.disable()
+ messages.success(self.request, _('Application disabled'))
+
+ return super().form_valid(form)
+
+ def get_context_data(self, *args, **kwargs):
+ """Add service to the context data."""
+ context = super().get_context_data(*args, **kwargs)
+ context['service'] = self.service
+ if self.diagnostics_module_name:
+ context['diagnostics_module_name'] = self.diagnostics_module_name
+ if self.description:
+ context['description'] = self.description
+ return context
+
+
+# TODO: remove this view once owncloud is gone.
class ConfigurationView(FormView):
"""A generic view for configuring simple modules."""
form_class = forms.ConfigurationForm