mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-08-26 12:46:08 +00:00
app: Separate app enable/disable form from config form
- Introduce new API to mark an app that it can't be disabled. - Mark jsxc, storage, config, upgrade and firewall apps as can't be disabled. - Fixed functional tests - Replaced AppForm with forms.Form in all modules' forms.py. - Remove app.template.js. - Remove unused styles. - Remove app status checks in form_valid of Deluge, Diaspora, Matrix, Ejabberd, MediaWiki, Storage, Transmission, Quassel - Purge unused is_enabled context variables (Ikiwiki) - ejabberd: Minor cleanup in template - jsxc: Cleanup unneeded overrides - tahoe: Cleanup unnecessary overrides Tests performed: - For all apps affected, test enable/disable button works and submitting configuration form works: with changes updates message and without changes 'settings unchanged' message. - avahi - bind - cockpit - SKIP: coquelicot - datetime - deluge - SKIP: diaspora - ejabberd - gitweb - i2p - infinoted - ikiwiki - matrixsynapse - mediawiki - minetest - minidlna - mldonkey - mumble - pagekite - privoxy - quassel - radicale - roundcube - SKIP: samba - searx - SKIP: shaarli - shadowsocks - ssh - tahoe - transmission - FAIL: tt-rss (not installable) - wireguard - Deluge test that configuration changes when app is disabled work - Quassel test that setting the domain works when app is diabled - Transmission test that setting the domain works when app is diabled - Ikiwiki create form works properly - Enable/disable button appears as expected when enabled and when disabled - Enable/disable button works without Javascript - Functional tests work for affected apps, Tor and OpenVPN - AppForm is removed from developer documentation - Forms reference - Customizing tutorial - Test all apps using directory select form - Transmission - Deluge - Visit each template that overrides block configuration and ensure that it is loaded properly and the display is as expected. - All apps that use AppView that are not tested above should not have an enable/disable button. That is JSXC, update, config, firewall, storage, users. Signed-off-by: Alice Kile <buoyantair@protonmail.com> Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Veiko Aasa <veiko17@disroot.org>
This commit is contained in:
parent
bd8aee17d5
commit
0b5b384651
@ -3,9 +3,6 @@
|
|||||||
Forms
|
Forms
|
||||||
-----
|
-----
|
||||||
|
|
||||||
.. autoclass:: plinth.forms.AppForm
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: plinth.forms.DomainSelectionForm
|
.. autoclass:: plinth.forms.DomainSelectionForm
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|||||||
@ -63,10 +63,8 @@ provide options to the user. Add the following to ``forms.py``.
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class TransmissionForm(forms.Form): # pylint: disable=W0232
|
||||||
class TransmissionForm(AppForm): # pylint: disable=W0232
|
|
||||||
"""Transmission configuration form"""
|
"""Transmission configuration form"""
|
||||||
download_dir = forms.CharField(
|
download_dir = forms.CharField(
|
||||||
label='Download directory',
|
label='Download directory',
|
||||||
|
|||||||
@ -91,14 +91,17 @@ def is_installed(browser, app_name):
|
|||||||
|
|
||||||
def _change_app_status(browser, app_name, change_status_to='enabled'):
|
def _change_app_status(browser, app_name, change_status_to='enabled'):
|
||||||
"""Enable or disable application."""
|
"""Enable or disable application."""
|
||||||
button = browser.find_by_id('app-toggle-button')
|
button = browser.find_by_css('button[name="app_enable_disable_button"]')
|
||||||
checkbox_id = get_app_checkbox_id(app_name)
|
|
||||||
checkbox = browser.find_by_id(checkbox_id)
|
|
||||||
if button:
|
if button:
|
||||||
if checkbox.checked and change_status_to == 'disabled' or (
|
should_enable_field = browser.find_by_id('id_should_enable')
|
||||||
not checkbox.checked and change_status_to == 'enabled'):
|
if (should_enable_field.value == 'False'
|
||||||
|
and change_status_to == 'disabled') or (
|
||||||
|
should_enable_field.value == 'True'
|
||||||
|
and change_status_to == 'enabled'):
|
||||||
interface.submit(browser, element=button)
|
interface.submit(browser, element=button)
|
||||||
else:
|
else:
|
||||||
|
checkbox_id = get_app_checkbox_id(app_name)
|
||||||
_change_status(browser, app_name, checkbox_id, change_status_to)
|
_change_status(browser, app_name, checkbox_id, change_status_to)
|
||||||
|
|
||||||
if app_name in apps_with_loaders:
|
if app_name in apps_with_loaders:
|
||||||
|
|||||||
@ -14,14 +14,21 @@ class App:
|
|||||||
variation their behavior by choosing which components to have and by
|
variation their behavior by choosing which components to have and by
|
||||||
customizing the components themselves.
|
customizing the components themselves.
|
||||||
|
|
||||||
'app_id' property of the app must string that is a globally unique ID. This
|
'app_id' property of the app must be a string that is a globally unique ID.
|
||||||
is typically also the name of the python module handling the app. So, it
|
This is typically also the name of the python module handling the app. So,
|
||||||
should be all lower-case English alphabet and digits without any special
|
it should be all lower-case English alphabet and digits without any special
|
||||||
characters.
|
characters.
|
||||||
|
|
||||||
|
'can_be_disabled' is a boolean indicating whether an app can be disabled by
|
||||||
|
the user. Enable/disable button for this app will not be shown. Default
|
||||||
|
value is True, so the app can be disabled.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
app_id = None
|
app_id = None
|
||||||
|
|
||||||
|
can_be_disabled = True
|
||||||
|
|
||||||
_all_apps = collections.OrderedDict()
|
_all_apps = collections.OrderedDict()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@ -17,11 +17,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
import plinth
|
import plinth
|
||||||
|
|
||||||
|
|
||||||
class AppForm(forms.Form):
|
class AppEnableDisableForm(forms.Form):
|
||||||
"""Generic configuration form for an app."""
|
"""Form to enable / disable an app."""
|
||||||
is_enabled = forms.BooleanField(
|
should_enable = forms.BooleanField(widget=forms.HiddenInput,
|
||||||
widget=CheckboxInput(attrs={'id': 'app-toggle-input'}),
|
required=False)
|
||||||
label=_('Enable application'), required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class DomainSelectionForm(forms.Form):
|
class DomainSelectionForm(forms.Form):
|
||||||
|
|||||||
@ -7,8 +7,6 @@ from django import forms
|
|||||||
from django.core.validators import validate_ipv46_address
|
from django.core.validators import validate_ipv46_address
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ips(ips):
|
def validate_ips(ips):
|
||||||
"""Validate that ips is a list of IP addresses, separated by space."""
|
"""Validate that ips is a list of IP addresses, separated by space."""
|
||||||
@ -16,7 +14,7 @@ def validate_ips(ips):
|
|||||||
validate_ipv46_address(ip_addr)
|
validate_ipv46_address(ip_addr)
|
||||||
|
|
||||||
|
|
||||||
class BindForm(AppForm):
|
class BindForm(forms.Form):
|
||||||
"""BIND configuration form"""
|
"""BIND configuration form"""
|
||||||
forwarders = forms.CharField(
|
forwarders = forms.CharField(
|
||||||
label=_('Forwarders'), required=False, validators=[validate_ips],
|
label=_('Forwarders'), required=False, validators=[validate_ips],
|
||||||
|
|||||||
@ -42,6 +42,8 @@ class ConfigApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'config'
|
app_id = 'config'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Plinth form for configuring Coquelicot.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class CoquelicotForm(forms.Form): # pylint: disable=W0232
|
||||||
class CoquelicotForm(AppForm): # pylint: disable=W0232
|
|
||||||
"""Coquelicot configuration form."""
|
"""Coquelicot configuration form."""
|
||||||
upload_password = forms.CharField(
|
upload_password = forms.CharField(
|
||||||
label=_('Upload Password'),
|
label=_('Upload Password'),
|
||||||
|
|||||||
@ -9,12 +9,10 @@ import subprocess
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DateTimeForm(AppForm):
|
class DateTimeForm(forms.Form):
|
||||||
"""Date/time configuration form."""
|
"""Date/time configuration form."""
|
||||||
time_zone = forms.ChoiceField(
|
time_zone = forms.ChoiceField(
|
||||||
label=_('Time Zone'),
|
label=_('Time Zone'),
|
||||||
|
|||||||
@ -32,15 +32,13 @@ class DelugeAppView(views.AppView):
|
|||||||
old_status = form.initial
|
old_status = form.initial
|
||||||
new_status = form.cleaned_data
|
new_status = form.cleaned_data
|
||||||
|
|
||||||
# don't change the configuration if the application was disabled
|
if old_status['storage_path'] != new_status['storage_path']:
|
||||||
if new_status['is_enabled'] or not old_status['is_enabled']:
|
new_configuration = [
|
||||||
if old_status['storage_path'] != new_status['storage_path']:
|
'download_location', new_status['storage_path']
|
||||||
new_configuration = [
|
]
|
||||||
'download_location', new_status['storage_path']
|
|
||||||
]
|
|
||||||
|
|
||||||
actions.superuser_run('deluge', ['set-configuration'] +
|
actions.superuser_run('deluge',
|
||||||
new_configuration)
|
['set-configuration'] + new_configuration)
|
||||||
messages.success(self.request, _('Configuration updated'))
|
messages.success(self.request, _('Configuration updated'))
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Forms for configuring diaspora*
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class DiasporaAppForm(forms.Form):
|
||||||
class DiasporaAppForm(AppForm):
|
|
||||||
"""Service Form with additional fields for diaspora*"""
|
"""Service Form with additional fields for diaspora*"""
|
||||||
is_user_registrations_enabled = forms.BooleanField(
|
is_user_registrations_enabled = forms.BooleanField(
|
||||||
label=_('Enable new user registrations'), required=False)
|
label=_('Enable new user registrations'), required=False)
|
||||||
|
|||||||
@ -65,16 +65,10 @@ class DiasporaAppView(AppView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Enable/disable user registrations"""
|
"""Enable/disable user registrations"""
|
||||||
old_enabled = form.initial['is_enabled']
|
|
||||||
new_enabled = form.cleaned_data['is_enabled']
|
|
||||||
old_registration = form.initial['is_user_registrations_enabled']
|
old_registration = form.initial['is_user_registrations_enabled']
|
||||||
new_registration = form.cleaned_data['is_user_registrations_enabled']
|
new_registration = form.cleaned_data['is_user_registrations_enabled']
|
||||||
|
|
||||||
if old_registration == new_registration:
|
if old_registration != new_registration:
|
||||||
if old_enabled == new_enabled:
|
|
||||||
if not self.request._messages._queued_messages:
|
|
||||||
messages.info(self.request, _('Setting unchanged'))
|
|
||||||
else:
|
|
||||||
if new_registration:
|
if new_registration:
|
||||||
diaspora.enable_user_registrations()
|
diaspora.enable_user_registrations()
|
||||||
messages.success(self.request, _('User registrations enabled'))
|
messages.success(self.request, _('User registrations enabled'))
|
||||||
|
|||||||
@ -7,11 +7,10 @@ from django import forms
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
from plinth.forms import AppForm
|
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
|
|
||||||
class EjabberdForm(AppForm):
|
class EjabberdForm(forms.Form):
|
||||||
"""Ejabberd configuration form."""
|
"""Ejabberd configuration form."""
|
||||||
MAM_enabled = forms.BooleanField(
|
MAM_enabled = forms.BooleanField(
|
||||||
label=_('Enable Message Archive Management'), required=False,
|
label=_('Enable Message Archive Management'), required=False,
|
||||||
|
|||||||
@ -28,18 +28,3 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block configuration %}
|
|
||||||
|
|
||||||
<h3>{% trans "Configuration" %}</h3>
|
|
||||||
|
|
||||||
<form id="app-form" class="form form-configuration" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary"
|
|
||||||
value="{% trans "Update setup" %}"/>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@ -36,21 +36,8 @@ class EjabberdAppView(AppView):
|
|||||||
"""Enable/disable a service and set messages."""
|
"""Enable/disable a service and set messages."""
|
||||||
old_status = form.initial
|
old_status = form.initial
|
||||||
new_status = form.cleaned_data
|
new_status = form.cleaned_data
|
||||||
app_same = old_status['is_enabled'] == new_status['is_enabled']
|
|
||||||
mam_same = old_status['MAM_enabled'] == new_status['MAM_enabled']
|
|
||||||
|
|
||||||
if app_same and mam_same:
|
if old_status['MAM_enabled'] != new_status['MAM_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'))
|
|
||||||
elif not app_same:
|
|
||||||
if new_status['is_enabled']:
|
|
||||||
self.app.enable()
|
|
||||||
else:
|
|
||||||
self.app.disable()
|
|
||||||
|
|
||||||
if not mam_same:
|
|
||||||
# note ejabberd action "enable" or "disable" restarts, if running
|
# note ejabberd action "enable" or "disable" restarts, if running
|
||||||
if new_status['MAM_enabled']:
|
if new_status['MAM_enabled']:
|
||||||
actions.superuser_run('ejabberd', ['mam', 'enable'])
|
actions.superuser_run('ejabberd', ['mam', 'enable'])
|
||||||
|
|||||||
@ -56,6 +56,8 @@ class FirewallApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'firewall'
|
app_id = 'firewall'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -54,12 +54,10 @@ def create(request):
|
|||||||
else:
|
else:
|
||||||
form = IkiwikiCreateForm(prefix='ikiwiki')
|
form = IkiwikiCreateForm(prefix='ikiwiki')
|
||||||
|
|
||||||
return TemplateResponse(
|
return TemplateResponse(request, 'ikiwiki_create.html', {
|
||||||
request, 'ikiwiki_create.html', {
|
'title': ikiwiki.app.info.name,
|
||||||
'title': ikiwiki.app.info.name,
|
'form': form
|
||||||
'form': form,
|
})
|
||||||
'is_enabled': ikiwiki.app.is_enabled(),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _create_wiki(request, name, admin_name, admin_password):
|
def _create_wiki(request, name, admin_name, admin_password):
|
||||||
|
|||||||
@ -34,6 +34,8 @@ class JSXCApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'jsxc'
|
app_id = 'jsxc'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
{% extends "app.html" %}
|
|
||||||
{% comment %}
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
{% endcomment %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block configuration %}
|
|
||||||
{% endblock %}
|
|
||||||
@ -6,9 +6,11 @@ URLs for the JSXC module
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from stronghold.decorators import public
|
from stronghold.decorators import public
|
||||||
|
|
||||||
from .views import JSXCAppView, JsxcView
|
from plinth.views import AppView
|
||||||
|
|
||||||
|
from .views import JsxcView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^apps/jsxc/$', JSXCAppView.as_view(), name='index'),
|
url(r'^apps/jsxc/$', AppView.as_view(app_id='jsxc'), name='index'),
|
||||||
url(r'^apps/jsxc/jsxc/$', public(JsxcView.as_view()), name='jsxc')
|
url(r'^apps/jsxc/jsxc/$', public(JsxcView.as_view()), name='jsxc')
|
||||||
]
|
]
|
||||||
|
|||||||
@ -6,13 +6,6 @@ Views for the JSXC module
|
|||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from plinth.modules import config
|
from plinth.modules import config
|
||||||
from plinth.views import AppView
|
|
||||||
|
|
||||||
|
|
||||||
class JSXCAppView(AppView):
|
|
||||||
"""Show ejabberd as an app."""
|
|
||||||
app_id = 'jsxc'
|
|
||||||
template_name = 'jsxc.html'
|
|
||||||
|
|
||||||
|
|
||||||
class JsxcView(TemplateView):
|
class JsxcView(TemplateView):
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Forms for the Matrix Synapse module.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class MatrixSynapseForm(forms.Form):
|
||||||
class MatrixSynapseForm(AppForm):
|
|
||||||
enable_public_registration = forms.BooleanField(
|
enable_public_registration = forms.BooleanField(
|
||||||
label=_('Enable Public Registration'), required=False, help_text=_(
|
label=_('Enable Public Registration'), required=False, help_text=_(
|
||||||
'Enabling public registration means that anyone on the Internet '
|
'Enabling public registration means that anyone on the Internet '
|
||||||
|
|||||||
@ -74,21 +74,9 @@ class MatrixSynapseAppView(AppView):
|
|||||||
"""Handle valid form submission."""
|
"""Handle valid form submission."""
|
||||||
old_config = self.get_initial()
|
old_config = self.get_initial()
|
||||||
new_config = form.cleaned_data
|
new_config = form.cleaned_data
|
||||||
app_same = old_config['is_enabled'] == new_config['is_enabled']
|
|
||||||
pubreg_same = old_config['enable_public_registration'] == \
|
pubreg_same = old_config['enable_public_registration'] == \
|
||||||
new_config['enable_public_registration']
|
new_config['enable_public_registration']
|
||||||
|
|
||||||
if app_same and pubreg_same:
|
|
||||||
# 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'))
|
|
||||||
elif not app_same:
|
|
||||||
if new_config['is_enabled']:
|
|
||||||
self.app.enable()
|
|
||||||
else:
|
|
||||||
self.app.disable()
|
|
||||||
|
|
||||||
if not pubreg_same:
|
if not pubreg_same:
|
||||||
# note action public-registration restarts, if running now
|
# note action public-registration restarts, if running now
|
||||||
if new_config['enable_public_registration']:
|
if new_config['enable_public_registration']:
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import pathlib
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
|
||||||
def get_skins():
|
def get_skins():
|
||||||
"""Return a list of available skins as choice field values."""
|
"""Return a list of available skins as choice field values."""
|
||||||
@ -21,7 +19,7 @@ def get_skins():
|
|||||||
if skin.is_dir()]
|
if skin.is_dir()]
|
||||||
|
|
||||||
|
|
||||||
class MediaWikiForm(AppForm): # pylint: disable=W0232
|
class MediaWikiForm(forms.Form): # pylint: disable=W0232
|
||||||
"""MediaWiki configuration form."""
|
"""MediaWiki configuration form."""
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
label=_('Administrator Password'), help_text=_(
|
label=_('Administrator Password'), help_text=_(
|
||||||
|
|||||||
@ -42,27 +42,12 @@ class MediaWikiAppView(views.AppView):
|
|||||||
def is_unchanged(key):
|
def is_unchanged(key):
|
||||||
return old_config[key] == new_config[key]
|
return old_config[key] == new_config[key]
|
||||||
|
|
||||||
app_same = is_unchanged('is_enabled')
|
|
||||||
pub_reg_same = is_unchanged('enable_public_registrations')
|
|
||||||
private_mode_same = is_unchanged('enable_private_mode')
|
|
||||||
default_skin_same = is_unchanged('default_skin')
|
|
||||||
|
|
||||||
if new_config['password']:
|
if new_config['password']:
|
||||||
actions.superuser_run('mediawiki', ['change-password'],
|
actions.superuser_run('mediawiki', ['change-password'],
|
||||||
input=new_config['password'].encode())
|
input=new_config['password'].encode())
|
||||||
messages.success(self.request, _('Password updated'))
|
messages.success(self.request, _('Password updated'))
|
||||||
|
|
||||||
if (app_same and pub_reg_same and private_mode_same
|
if not is_unchanged('enable_public_registrations'):
|
||||||
and default_skin_same):
|
|
||||||
if not self.request._messages._queued_messages:
|
|
||||||
messages.info(self.request, _('Setting unchanged'))
|
|
||||||
elif not app_same:
|
|
||||||
if new_config['is_enabled']:
|
|
||||||
self.app.enable()
|
|
||||||
else:
|
|
||||||
self.app.disable()
|
|
||||||
|
|
||||||
if not pub_reg_same:
|
|
||||||
# note action public-registration restarts, if running now
|
# note action public-registration restarts, if running now
|
||||||
if new_config['enable_public_registrations']:
|
if new_config['enable_public_registrations']:
|
||||||
if not new_config['enable_private_mode']:
|
if not new_config['enable_private_mode']:
|
||||||
@ -80,7 +65,7 @@ class MediaWikiAppView(views.AppView):
|
|||||||
messages.success(self.request,
|
messages.success(self.request,
|
||||||
_('Public registrations disabled'))
|
_('Public registrations disabled'))
|
||||||
|
|
||||||
if not private_mode_same:
|
if not is_unchanged('enable_private_mode'):
|
||||||
if new_config['enable_private_mode']:
|
if new_config['enable_private_mode']:
|
||||||
actions.superuser_run('mediawiki', ['private-mode', 'enable'])
|
actions.superuser_run('mediawiki', ['private-mode', 'enable'])
|
||||||
messages.success(self.request, _('Private mode enabled'))
|
messages.success(self.request, _('Private mode enabled'))
|
||||||
@ -95,7 +80,7 @@ class MediaWikiAppView(views.AppView):
|
|||||||
shortcut = mediawiki.app.get_component('shortcut-mediawiki')
|
shortcut = mediawiki.app.get_component('shortcut-mediawiki')
|
||||||
shortcut.login_required = new_config['enable_private_mode']
|
shortcut.login_required = new_config['enable_private_mode']
|
||||||
|
|
||||||
if not default_skin_same:
|
if not is_unchanged('default_skin'):
|
||||||
actions.superuser_run(
|
actions.superuser_run(
|
||||||
'mediawiki', ['set-default-skin', new_config['default_skin']])
|
'mediawiki', ['set-default-skin', new_config['default_skin']])
|
||||||
messages.success(self.request, _('Default skin changed'))
|
messages.success(self.request, _('Default skin changed'))
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Forms for minetest module.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class MinetestForm(forms.Form):
|
||||||
class MinetestForm(AppForm):
|
|
||||||
"""Minetest configuration form"""
|
"""Minetest configuration form"""
|
||||||
max_players = forms.IntegerField(
|
max_players = forms.IntegerField(
|
||||||
label=_('Maximum number of players'), required=True, min_value=1,
|
label=_('Maximum number of players'), required=True, min_value=1,
|
||||||
|
|||||||
@ -6,10 +6,8 @@ FreedomBox configuration form for MiniDLNA server.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class MiniDLNAServerForm(forms.Form):
|
||||||
class MiniDLNAServerForm(AppForm):
|
|
||||||
"""MiniDLNA server configuration form."""
|
"""MiniDLNA server configuration form."""
|
||||||
media_dir = forms.CharField(
|
media_dir = forms.CharField(
|
||||||
label=_('Media Files Directory'),
|
label=_('Media Files Directory'),
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Mumble server configuration form
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class MumbleForm(forms.Form):
|
||||||
class MumbleForm(AppForm):
|
|
||||||
"""Mumble server configuration"""
|
"""Mumble server configuration"""
|
||||||
super_user_password = forms.CharField(
|
super_user_password = forms.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
@ -17,7 +15,6 @@ class MumbleForm(AppForm):
|
|||||||
widget=forms.PasswordInput,
|
widget=forms.PasswordInput,
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Optional. Leave this field blank to keep the current password. '
|
'Optional. Leave this field blank to keep the current password. '
|
||||||
'SuperUser password can be used to manage permissions in Mumble.'
|
'SuperUser password can be used to manage permissions in Mumble.'),
|
||||||
),
|
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
from plinth.errors import ActionError
|
from plinth.errors import ActionError
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ class SubdomainWidget(forms.widgets.TextInput):
|
|||||||
</div>""".format(inputfield, self.domain)
|
</div>""".format(inputfield, self.domain)
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationForm(AppForm):
|
class ConfigurationForm(forms.Form):
|
||||||
"""Configure PageKite credentials and frontend"""
|
"""Configure PageKite credentials and frontend"""
|
||||||
|
|
||||||
server_domain = forms.CharField(
|
server_domain = forms.CharField(
|
||||||
|
|||||||
@ -6,7 +6,6 @@ Forms for Quassel app.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
from plinth.modules import quassel
|
from plinth.modules import quassel
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +14,7 @@ def get_domain_choices():
|
|||||||
return ((domain, domain) for domain in quassel.get_available_domains())
|
return ((domain, domain) for domain in quassel.get_available_domains())
|
||||||
|
|
||||||
|
|
||||||
class QuasselForm(AppForm):
|
class QuasselForm(forms.Form):
|
||||||
"""Form to select a TLS domain for Quassel."""
|
"""Form to select a TLS domain for Quassel."""
|
||||||
|
|
||||||
domain = forms.ChoiceField(
|
domain = forms.ChoiceField(
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.modules import quassel
|
from plinth.modules import quassel
|
||||||
from plinth.views import AppView
|
from plinth.views import AppView
|
||||||
|
|
||||||
@ -20,11 +23,10 @@ class QuasselAppView(AppView):
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Change the access control of Radicale service."""
|
"""Change the access control of Radicale service."""
|
||||||
data = form.cleaned_data
|
data = form.cleaned_data
|
||||||
app_disable = form.initial['is_enabled'] and not data['is_enabled']
|
if quassel.get_domain() != data['domain']:
|
||||||
|
|
||||||
if not app_disable and quassel.get_domain() != data['domain']:
|
|
||||||
quassel.set_domain(data['domain'])
|
quassel.set_domain(data['domain'])
|
||||||
quassel.app.get_component(
|
quassel.app.get_component(
|
||||||
'letsencrypt-quassel').setup_certificates()
|
'letsencrypt-quassel').setup_certificates()
|
||||||
|
messages.success(self.request, _('Configuration updated'))
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from django import forms
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth import cfg
|
from plinth import cfg
|
||||||
from plinth.forms import AppForm
|
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
CHOICES = [
|
CHOICES = [
|
||||||
@ -26,7 +25,7 @@ CHOICES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class RadicaleForm(AppForm):
|
class RadicaleForm(forms.Form):
|
||||||
"""Specialized configuration form for radicale service."""
|
"""Specialized configuration form for radicale service."""
|
||||||
access_rights = forms.ChoiceField(choices=CHOICES, required=True,
|
access_rights = forms.ChoiceField(choices=CHOICES, required=True,
|
||||||
widget=forms.RadioSelect())
|
widget=forms.RadioSelect())
|
||||||
|
|||||||
@ -6,10 +6,8 @@ Django form for configuring Searx.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class SearxForm(forms.Form):
|
||||||
class SearxForm(AppForm):
|
|
||||||
"""Searx configuration form."""
|
"""Searx configuration form."""
|
||||||
safe_search = forms.ChoiceField(
|
safe_search = forms.ChoiceField(
|
||||||
label=_('Safe Search'), help_text=_(
|
label=_('Safe Search'), help_text=_(
|
||||||
|
|||||||
@ -6,7 +6,6 @@ FreedomBox app for configuring Shadowsocks.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
from plinth.utils import format_lazy
|
from plinth.utils import format_lazy
|
||||||
|
|
||||||
METHODS = [('chacha20-ietf-poly1305',
|
METHODS = [('chacha20-ietf-poly1305',
|
||||||
@ -32,7 +31,7 @@ class TrimmedCharField(forms.CharField):
|
|||||||
return super(TrimmedCharField, self).clean(value)
|
return super(TrimmedCharField, self).clean(value)
|
||||||
|
|
||||||
|
|
||||||
class ShadowsocksForm(AppForm):
|
class ShadowsocksForm(forms.Form):
|
||||||
"""Shadowsocks configuration form"""
|
"""Shadowsocks configuration form"""
|
||||||
server = TrimmedCharField(label=_('Server'),
|
server = TrimmedCharField(label=_('Server'),
|
||||||
help_text=_('Server hostname or IP address'))
|
help_text=_('Server hostname or IP address'))
|
||||||
|
|||||||
@ -6,10 +6,8 @@ FreedomBox configuration form for OpenSSH server.
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth.forms import AppForm
|
|
||||||
|
|
||||||
|
class SSHServerForm(forms.Form):
|
||||||
class SSHServerForm(AppForm):
|
|
||||||
"""SSH server configuration form."""
|
"""SSH server configuration form."""
|
||||||
password_auth_disabled = forms.BooleanField(
|
password_auth_disabled = forms.BooleanField(
|
||||||
label=_('Disable password authentication'),
|
label=_('Disable password authentication'),
|
||||||
|
|||||||
@ -45,6 +45,8 @@ class StorageApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'storage'
|
app_id = 'storage'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -11,7 +11,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth import actions, module_loader
|
from plinth import actions, module_loader
|
||||||
from plinth.forms import AppForm
|
|
||||||
from plinth.modules import storage
|
from plinth.modules import storage
|
||||||
|
|
||||||
|
|
||||||
@ -78,8 +77,8 @@ class DirectoryValidator:
|
|||||||
if 'ValidationError' in output:
|
if 'ValidationError' in output:
|
||||||
error_nr = int(output.strip().split()[1])
|
error_nr = int(output.strip().split()[1])
|
||||||
if error_nr == 1:
|
if error_nr == 1:
|
||||||
raise ValidationError(
|
raise ValidationError(_('Directory does not exist.'),
|
||||||
_('Directory does not exist.'), 'invalid')
|
'invalid')
|
||||||
elif error_nr == 2:
|
elif error_nr == 2:
|
||||||
raise ValidationError(_('Path is not a directory.'), 'invalid')
|
raise ValidationError(_('Path is not a directory.'), 'invalid')
|
||||||
elif error_nr == 3:
|
elif error_nr == 3:
|
||||||
@ -90,12 +89,12 @@ class DirectoryValidator:
|
|||||||
_('Directory is not writable by the user.'), 'invalid')
|
_('Directory is not writable by the user.'), 'invalid')
|
||||||
|
|
||||||
|
|
||||||
class DirectorySelectForm(AppForm):
|
class DirectorySelectForm(forms.Form):
|
||||||
"""Directory selection form."""
|
"""Directory selection form."""
|
||||||
storage_dir = forms.ChoiceField(choices=[], label=_('Directory'),
|
storage_dir = forms.ChoiceField(choices=[], label=_('Directory'),
|
||||||
required=True)
|
required=True)
|
||||||
storage_subdir = forms.CharField(
|
storage_subdir = forms.CharField(label=_('Subdirectory (optional)'),
|
||||||
label=_('Subdirectory (optional)'), required=False)
|
required=False)
|
||||||
|
|
||||||
def __init__(self, title=None, default='/', validator=DirectoryValidator,
|
def __init__(self, title=None, default='/', validator=DirectoryValidator,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
@ -108,16 +107,15 @@ class DirectorySelectForm(AppForm):
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Clean and validate form data."""
|
"""Clean and validate form data."""
|
||||||
if self.cleaned_data['is_enabled'] or not self.initial['is_enabled']:
|
storage_dir = self.cleaned_data['storage_dir']
|
||||||
storage_dir = self.cleaned_data['storage_dir']
|
storage_subdir = self.cleaned_data['storage_subdir']
|
||||||
storage_subdir = self.cleaned_data['storage_subdir']
|
if storage_dir != '/':
|
||||||
if storage_dir != '/':
|
storage_subdir = storage_subdir.lstrip('/')
|
||||||
storage_subdir = storage_subdir.lstrip('/')
|
storage_path = os.path.realpath(
|
||||||
storage_path = os.path.realpath(
|
os.path.join(storage_dir, storage_subdir))
|
||||||
os.path.join(storage_dir, storage_subdir))
|
if self.validator:
|
||||||
if self.validator:
|
self.validator(storage_path)
|
||||||
self.validator(storage_path)
|
self.cleaned_data.update({'storage_path': storage_path})
|
||||||
self.cleaned_data.update({'storage_path': storage_path})
|
|
||||||
|
|
||||||
def get_initial(self, choices):
|
def get_initial(self, choices):
|
||||||
"""Get initial form data."""
|
"""Get initial form data."""
|
||||||
|
|||||||
@ -24,17 +24,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block configuration %}
|
{% block configuration %}
|
||||||
<h3>{% trans "Configuration" %}</h3>
|
{{ block.super }}
|
||||||
|
|
||||||
<form class="form form-configuration" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary"
|
|
||||||
value="{% trans "Update setup" %}"/>
|
|
||||||
</form>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<h4>{% trans "Local introducer" %}</h4>
|
<h4>{% trans "Local introducer" %}</h4>
|
||||||
<table class="table table-bordered local-introducers">
|
<table class="table table-bordered local-introducers">
|
||||||
|
|||||||
@ -37,16 +37,13 @@ class TransmissionAppView(views.AppView):
|
|||||||
"""Apply the changes submitted in the form."""
|
"""Apply the changes submitted in the form."""
|
||||||
old_status = form.initial
|
old_status = form.initial
|
||||||
new_status = form.cleaned_data
|
new_status = form.cleaned_data
|
||||||
|
if old_status['storage_path'] != new_status['storage_path']:
|
||||||
|
new_configuration = {
|
||||||
|
'download-dir': new_status['storage_path'],
|
||||||
|
}
|
||||||
|
|
||||||
if new_status['is_enabled'] or not old_status['is_enabled']:
|
actions.superuser_run('transmission', ['merge-configuration'],
|
||||||
if old_status['storage_path'] != new_status['storage_path']:
|
input=json.dumps(new_configuration).encode())
|
||||||
new_configuration = {
|
messages.success(self.request, _('Configuration updated'))
|
||||||
'download-dir': new_status['storage_path'],
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.superuser_run(
|
|
||||||
'transmission', ['merge-configuration'],
|
|
||||||
input=json.dumps(new_configuration).encode())
|
|
||||||
messages.success(self.request, _('Configuration updated'))
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|||||||
@ -31,6 +31,8 @@ class UpgradesApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'upgrades'
|
app_id = 'upgrades'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from django.contrib import messages
|
|||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic.edit import FormView
|
|
||||||
|
|
||||||
from plinth import actions
|
from plinth import actions
|
||||||
from plinth.errors import ActionError
|
from plinth.errors import ActionError
|
||||||
@ -52,10 +51,8 @@ class UpgradesConfigurationView(AppView):
|
|||||||
else:
|
else:
|
||||||
messages.success(self.request,
|
messages.success(self.request,
|
||||||
_('Automatic upgrades disabled'))
|
_('Automatic upgrades disabled'))
|
||||||
else:
|
|
||||||
messages.info(self.request, _('Settings unchanged'))
|
|
||||||
|
|
||||||
return FormView.form_valid(self, form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
def is_package_manager_busy():
|
def is_package_manager_busy():
|
||||||
|
|||||||
@ -56,6 +56,8 @@ class UsersApp(app_module.App):
|
|||||||
|
|
||||||
app_id = 'users'
|
app_id = 'users'
|
||||||
|
|
||||||
|
can_be_disabled = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create components for the app."""
|
"""Create components for the app."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -28,15 +28,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
<div id='app-toggle-container' class="app-toggle-container">
|
{% if app_enable_disable_form %}
|
||||||
{% if is_enabled %}
|
<form class="form form-app-enable-disable" method="post">
|
||||||
<button id="app-toggle-button" value="False"
|
{% csrf_token %}
|
||||||
class="btn toggle-button toggle-button--toggled"></button>
|
|
||||||
{% else %}
|
{{ app_enable_disable_form }}
|
||||||
<button id="app-toggle-button" value="True"
|
|
||||||
class="btn toggle-button"></button>
|
<button name="app_enable_disable_button" type="submit"
|
||||||
{% endif %}
|
class="btn toggle-button {{ is_enabled|yesno:'toggle-button--toggled,' }}">
|
||||||
</div>
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% block description %}
|
{% block description %}
|
||||||
|
|||||||
@ -42,16 +42,18 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block configuration %}
|
{% block configuration %}
|
||||||
<h3>{% trans "Configuration" %}</h3>
|
{% if form %}
|
||||||
<form id="app-form" class="form form-configuration" method="post">
|
<h3>{% trans "Configuration" %}</h3>
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
<form id="app-form" class="form form-configuration" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary"
|
{{ form|bootstrap }}
|
||||||
value="{% trans "Update setup" %}"/>
|
|
||||||
</form>
|
<input type="submit" class="btn btn-primary"
|
||||||
|
value="{% trans "Update setup" %}"/>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script src="{% static 'theme/js/app.template.js' %}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import time
|
|||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.http import is_safe_url
|
from django.utils.http import is_safe_url
|
||||||
@ -114,8 +114,8 @@ class AppView(FormView):
|
|||||||
retrieve the App instance for the app that is needed for basic information
|
retrieve the App instance for the app that is needed for basic information
|
||||||
and operations such as enabling/disabling the app.
|
and operations such as enabling/disabling the app.
|
||||||
|
|
||||||
'form_class' is the Django form class that is used by this view. By default
|
'form_class' is the Django form class that is used by this view. It may be
|
||||||
the AppForm class is used.
|
None if the app does not have a configuration form. Default is None.
|
||||||
|
|
||||||
'template_name' is the template used to render this view. By default it is
|
'template_name' is the template used to render this view. By default it is
|
||||||
app.html. It may be overridden with a template that derives from app.html
|
app.html. It may be overridden with a template that derives from app.html
|
||||||
@ -127,7 +127,7 @@ class AppView(FormView):
|
|||||||
forward ports on their router for this app to work properly.
|
forward ports on their router for this app to work properly.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
form_class = forms.AppForm
|
form_class = None
|
||||||
app_id = None
|
app_id = None
|
||||||
template_name = 'app.html'
|
template_name = 'app.html'
|
||||||
port_forwarding_info = None
|
port_forwarding_info = None
|
||||||
@ -137,6 +137,17 @@ class AppView(FormView):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._common_status = None
|
self._common_status = None
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Handle app enable/disable button separately."""
|
||||||
|
if 'app_enable_disable_button' not in request.POST:
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
form = forms.AppEnableDisableForm(data=self.request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
return self.enable_disable_form_valid(form)
|
||||||
|
|
||||||
|
return HttpResponseBadRequest('Invalid form submission')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def success_url(self):
|
def success_url(self):
|
||||||
return self.request.path
|
return self.request.path
|
||||||
@ -149,6 +160,17 @@ class AppView(FormView):
|
|||||||
|
|
||||||
return app.App.get(self.app_id)
|
return app.App.get(self.app_id)
|
||||||
|
|
||||||
|
def get_form(self, *args, **kwargs):
|
||||||
|
"""Return an instance of this view's form.
|
||||||
|
|
||||||
|
Also the form_class for this view to be None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.form_class:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return super().get_form(*args, **kwargs)
|
||||||
|
|
||||||
def _get_common_status(self):
|
def _get_common_status(self):
|
||||||
"""Return the status needed for form and template.
|
"""Return the status needed for form and template.
|
||||||
|
|
||||||
@ -170,24 +192,36 @@ class AppView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Enable/disable a service and set messages."""
|
"""Enable/disable a service and set messages."""
|
||||||
old_status = form.initial
|
if not self.request._messages._queued_messages:
|
||||||
new_status = form.cleaned_data
|
messages.info(self.request, _('Setting unchanged'))
|
||||||
|
|
||||||
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.app.enable()
|
|
||||||
messages.success(self.request, _('Application enabled'))
|
|
||||||
else:
|
|
||||||
self.app.disable()
|
|
||||||
messages.success(self.request, _('Application disabled'))
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_enable_disable_form(self):
|
||||||
|
"""Return an instance of the app enable/disable form.
|
||||||
|
|
||||||
|
If the app can't be disabled by the user, return None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.app.can_be_disabled:
|
||||||
|
return None
|
||||||
|
|
||||||
|
initial = {
|
||||||
|
'should_enable': not self._get_common_status()['is_enabled']
|
||||||
|
}
|
||||||
|
return forms.AppEnableDisableForm(initial=initial)
|
||||||
|
|
||||||
|
def enable_disable_form_valid(self, form):
|
||||||
|
"""Form handling for enabling / disabling apps."""
|
||||||
|
should_enable = form.cleaned_data['should_enable']
|
||||||
|
if should_enable != self.app.is_enabled():
|
||||||
|
if should_enable:
|
||||||
|
self.app.enable()
|
||||||
|
else:
|
||||||
|
self.app.disable()
|
||||||
|
|
||||||
|
return HttpResponseRedirect(self.request.path)
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
"""Add service to the context data."""
|
"""Add service to the context data."""
|
||||||
context = super().get_context_data(*args, **kwargs)
|
context = super().get_context_data(*args, **kwargs)
|
||||||
@ -197,6 +231,7 @@ class AppView(FormView):
|
|||||||
context['app_info'] = self.app.info
|
context['app_info'] = self.app.info
|
||||||
context['has_diagnostics'] = self.app.has_diagnostics()
|
context['has_diagnostics'] = self.app.has_diagnostics()
|
||||||
context['port_forwarding_info'] = self.port_forwarding_info
|
context['port_forwarding_info'] = self.port_forwarding_info
|
||||||
|
context['app_enable_disable_form'] = self.get_enable_disable_form()
|
||||||
|
|
||||||
from plinth.modules.firewall.components import Firewall
|
from plinth.modules.firewall.components import Firewall
|
||||||
context['firewall'] = self.app.get_components_of_type(Firewall)
|
context['firewall'] = self.app.get_components_of_type(Firewall)
|
||||||
|
|||||||
@ -571,10 +571,6 @@ a.menu_link_active {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-titles .app-toggle-container {
|
|
||||||
margin: auto 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-titles h3 {
|
.app-titles h3 {
|
||||||
margin-top: -0.5rem;
|
margin-top: -0.5rem;
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
@ -583,10 +579,8 @@ a.menu_link_active {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-toggle-container {
|
.form-app-enable-disable {
|
||||||
display: none;
|
margin: auto 0;
|
||||||
flex-flow: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
@ -612,7 +606,7 @@ a.menu_link_active {
|
|||||||
margin: 0 0 15px 0;
|
margin: 0 0 15px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-titles .app-toggle-container {
|
.app-titles .form-app-enable-disable {
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
/**
|
|
||||||
* @licstart The following is the entire license notice for the JavaScript
|
|
||||||
* code in this page.
|
|
||||||
*
|
|
||||||
* This file is part of FreedomBox.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* @licend The above is the entire license notice for the JavaScript code
|
|
||||||
* in this page.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var appForm = document.querySelector('#app-form');
|
|
||||||
var appToggleContainer = document.querySelector('#app-toggle-container');
|
|
||||||
var appToggleButton = document.querySelector('#app-toggle-button');
|
|
||||||
var appToggleInput = document.querySelector('#app-toggle-input');
|
|
||||||
|
|
||||||
if (appForm && appToggleButton && appToggleInput && appToggleContainer) {
|
|
||||||
var onSubmit = (e) => {
|
|
||||||
e.preventDefault;
|
|
||||||
appToggleInput.checked = !appToggleInput.checked;
|
|
||||||
appForm.submit();
|
|
||||||
};
|
|
||||||
|
|
||||||
appToggleButton.addEventListener('click', onSubmit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if javascript is enabled, this script will run and show the toggle button
|
|
||||||
*/
|
|
||||||
|
|
||||||
appToggleInput.parentElement.style.display = 'none';
|
|
||||||
appToggleContainer.style.display = 'flex';
|
|
||||||
|
|
||||||
/* A basic form has only three elements:
|
|
||||||
* 1. An input tag with CSRF token
|
|
||||||
* 2. A div with form elements
|
|
||||||
* 3. A Submit button
|
|
||||||
*
|
|
||||||
* This kind of form can be completely hidden.
|
|
||||||
*/
|
|
||||||
if (appForm.children.length === 3) {
|
|
||||||
appForm.style.display = 'none';
|
|
||||||
appForm.previousElementSibling.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user