Merge tag 'v19.18' into debian/buster-backports

Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
James Valleroy 2019-10-10 07:03:23 -04:00
commit b62f122429
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
99 changed files with 9678 additions and 9175 deletions

View File

@ -37,7 +37,7 @@ Documentation=man:deluge-web(1)
After=network.target
[Service]
ExecStart=/usr/bin/deluge-web --base=deluge
ExecStart=bash -c "/usr/bin/deluge-web --base=deluge $(/usr/bin/deluge-web --version | grep deluge-web | cut -f2 -d' ' | grep -q '^1.' && echo '' || echo '--do-not-daemonize')"
Restart=on-failure
User=debian-deluged
Group=debian-deluged
@ -60,11 +60,10 @@ def parse_arguments():
def subcommand_setup(_):
"""Perform initial setup for deluge-web."""
if not os.path.isfile(SYSTEMD_SERVICE_PATH):
with open(SYSTEMD_SERVICE_PATH, 'w') as file_handle:
file_handle.write(SYSTEMD_SERVICE)
with open(SYSTEMD_SERVICE_PATH, 'w') as file_handle:
file_handle.write(SYSTEMD_SERVICE)
subprocess.check_call(['systemctl', 'daemon-reload'])
subprocess.check_call(['systemctl', 'daemon-reload'])
def main():

View File

@ -34,12 +34,3 @@ secure_proxy_ssl_header = HTTP_X_FORWARDED_PROTO
[Misc]
box_name = FreedomBox
# The danube_edition changes the firstboot process and offers entering a
# voucher for a freedombox.me sub-domain. This functionality requires
# additional debian packages to be installed:
#
# pagekite, python3-requests
#
# They are not added as dependencies to keep the normal installation images
# lean, but make sure to add them if you want to build danube-edition images.
danube_edition = False

47
debian/changelog vendored
View File

@ -1,3 +1,50 @@
plinth (19.18) unstable; urgency=medium
[ Matthias Dellweg ]
* diagnose: Move negating diagnose result inside try block
[ Fioddor Superconcentrado ]
* Translated using Weblate (Spanish)
[ Luis A. Arizmendi ]
* Translated using Weblate (Spanish)
[ Allan Nordhøy ]
* Translated using Weblate (Norwegian Bokmål)
[ Dietmar ]
* Translated using Weblate (German)
[ Sunil Mohan Adapa ]
* pagekite: Remove first wizard step for danube edition
* pagekite: cosmetic: yapf and isort changes
* debian: Remove python3-requests from depends list
* users: Make UI close to rest of the apps
* upgrades: Remove unnecessary subsubmenu
* ikiwiki: Remove subsubmenu in favor of toolbar
* networks: Remove subsubmenu in favor of toolbar buttons
* backups: Remove unnecessary use of subsubmenu template
* templates: Remove unused invocation of subsubmenu
* templates: Simplify unnecessary override
* templates: Provide subsubmenu functionality in app.html
* dynamicdns: Use app.html instead of app-subsubmenu.html
* i2p: Use app.html instead of app-subsubmenu.html
* pagekite: Use app.html instead of app-subsubmenu.html
* snapshot: Use app.html instead of app-subsubmenu.html
* templates: Remove unused app-subsubmenu.html
* deluge: Support deluge 2 by starting it properly
* minetest: Remove mod-torches no longer available in testing/unstable
[ James Valleroy ]
* security: Add past vulnerabilities count
* security: Move security report to new page
* locale: Update translation strings
* doc: Fetch latest manual
* d/control: Add Rules-Requires-Root: no
* d/control: Update Standards-Version to 4.4.1
-- James Valleroy <jvalleroy@mailbox.org> Mon, 07 Oct 2019 19:06:16 -0400
plinth (19.17~bpo10+1) buster-backports; urgency=medium
* Rebuild for buster-backports.

3
debian/control vendored
View File

@ -43,10 +43,11 @@ Build-Depends:
python3-setuptools-git,
python3-yaml,
xmlto,
Standards-Version: 4.4.0
Standards-Version: 4.4.1
Homepage: https://salsa.debian.org/freedombox-team/plinth
Vcs-Git: https://salsa.debian.org/freedombox-team/plinth.git
Vcs-Browser: https://salsa.debian.org/freedombox-team/plinth
Rules-Requires-Root: no
Package: freedombox
Breaks:

File diff suppressed because one or more lines are too long

View File

@ -844,6 +844,14 @@ $ gpg --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
# This is the FreedomBox CI server's key
$ gpg --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8]]></screen>
<para>If this command shows an error such as <emphasis>new key but contains no user ID - skipped</emphasis>, then use a different keyserver to download the keys: </para>
<screen><![CDATA[$ gpg --keyserver keys.gnupg.net --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keys.gnupg.net --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
$ gpg --keyserver keys.gnupg.net --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8]]></screen>
<para>Or </para>
<screen><![CDATA[$ gpg --keyserver keyserver.ubuntu.com --recv-keys BCBEBD57A11F70B23782BC5736C361440C9BC971
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 7D6ADB750F91085589484BE677C0C75E7B650808
$ gpg --keyserver keyserver.ubuntu.com --recv-keys 013D86D8BA32EAB4A6691BF85D4153D6FE188FC8]]></screen>
</listitem>
<listitem>
<para>Next, verify the fingerprint of the public keys: </para>
@ -1732,6 +1740,32 @@ if [ -f /etc/default/radicale.dpkg-dist ] ; then cp /etc/default/radicale.dpkg-d
<para>The Synapse reference server implemented in Python is known to be quite RAM hungry, especially when loading large rooms with thousands of members like #matrix:matrix.org. It is recommended to avoid joining such rooms if your FreedomBox device only has 1 GiB RAM or less. Rooms with up to a hundred members should be safe to join. The Matrix team is working on a new implementation of the Matrix server written in Go called Dendrite which might perform better in low-memory environments. </para>
<para>Some large public rooms in the Matrix network are also available as IRC channels (e.g. #freedombox:matrix.org is also available as #freedombox on irc.debian.org). It is better to use IRC instead of Matrix for such large rooms. You can join the IRC channels using <ulink url="https://wiki.debian.org/FreedomBox/Manual/FreedomBox/Manual/Quassel#">Quassel</ulink>. </para>
</section>
<section>
<title>Advanced usage</title>
<orderedlist numeration="arabic">
<listitem>
<para>If you wish to create a large number of users on your Matrix Synapse server, use the following commands on a remote shell as root user: </para>
<itemizedlist>
<listitem override="none">
<screen><![CDATA[cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 | sed "s+^+registration_shared_secret: +" > /etc/matrix-synapse/conf.d/registration_shared_secret.yaml
chmod 600 /etc/matrix-synapse/conf.d/registration_shared_secret.yaml
chown matrix-synapse:nogroup /etc/matrix-synapse/conf.d/registration_shared_secret.yaml
systemctl restart matrix-synapse
register_new_matrix_user -c /etc/matrix-synapse/conf.d/registration_shared_secret.yaml]]></screen>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>If you wish to see the list of users registered in Matrix Synapse, the following as root user: </para>
<itemizedlist>
<listitem override="none">
<screen><![CDATA[apt install sqlite3
echo 'select name from users' | sqlite3 /var/lib/matrix-synapse/homeserver.db ]]></screen>
</listitem>
</itemizedlist>
</listitem>
</orderedlist>
</section>
</section>
<section>
<title>Email Client (Roundcube)</title>
@ -9831,6 +9865,35 @@ wget https://www.thinkpenguin.com/files/ath9k_firmware_free-version/htc_7010.fw]
<section>
<title>Release Notes</title>
<para>The following are the release notes for each FreedomBox version. </para>
<section>
<title>FreedomBox 19.18 (2019-10-07)</title>
<itemizedlist>
<listitem>
<para>diagnostics: Ensure that exceptions are reported as failures </para>
</listitem>
<listitem>
<para>users: Rearrange UI to match with other apps </para>
</listitem>
<listitem>
<para>upgrades, ikiwiki, networks, backups: Replace page tabs with buttons </para>
</listitem>
<listitem>
<para>dynamicdns, i2p, pagekite, snapshot: Cleanup page templates </para>
</listitem>
<listitem>
<para>deluge: Support deluge 2 by starting it properly </para>
</listitem>
<listitem>
<para>minetest: Remove mod-torches no longer available in testing/unstable </para>
</listitem>
<listitem>
<para>security: Add past vulnerabilities count, move report to new page </para>
</listitem>
<listitem>
<para>Update translations for Spanish, Norwegian Bokmål, German </para>
</listitem>
</itemizedlist>
</section>
<section>
<title>FreedomBox 19.17 (2019-09-23)</title>
<itemizedlist>

View File

@ -322,7 +322,6 @@ def ejabberd_has_contact(browser):
def ikiwiki_create_wiki_if_needed(browser):
"""Create wiki if it does not exist."""
interface.nav_to_module(browser, 'ikiwiki')
browser.find_link_by_href('/plinth/apps/ikiwiki/manage/').first.click()
wiki = browser.find_link_by_href('/ikiwiki/wiki')
if not wiki:
browser.find_link_by_href('/plinth/apps/ikiwiki/create/').first.click()
@ -337,7 +336,6 @@ def ikiwiki_create_wiki_if_needed(browser):
def ikiwiki_delete_wiki(browser):
"""Delete wiki."""
interface.nav_to_module(browser, 'ikiwiki')
browser.find_link_by_href('/plinth/apps/ikiwiki/manage/').first.click()
browser.find_link_by_href(
'/plinth/apps/ikiwiki/wiki/delete/').first.click()
submit(browser)
@ -346,7 +344,6 @@ def ikiwiki_delete_wiki(browser):
def ikiwiki_wiki_exists(browser):
"""Check whether the wiki exists."""
interface.nav_to_module(browser, 'ikiwiki')
browser.find_link_by_href('/plinth/apps/ikiwiki/manage/').first.click()
wiki = browser.find_link_by_href('/ikiwiki/wiki')
return bool(wiki)

View File

@ -350,7 +350,8 @@ def _deluge_ensure_daemon_started(browser):
"""Start the deluge daemon if it is not started."""
_deluge_open_connection_manager(browser)
browser.find_by_xpath('//em[text()="127.0.0.1:58846"]').first.click()
browser.find_by_xpath(
'//em[contains(text(),"127.0.0.1:58846")]').first.click()
if browser.is_element_present_by_xpath('//button[text()="Stop Daemon"]'):
return
@ -433,22 +434,27 @@ def deluge_upload_sample_torrent(browser):
eventually(
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
browser.find_by_css('button.x-deluge-add-file').first.click()
# Add from file window appears
eventually(
lambda: _deluge_get_active_window_title(browser) == 'Add from File')
# Attach file
file_path = os.path.join(
os.path.dirname(__file__), '..', 'data', 'sample.torrent')
browser.attach_file('file', file_path)
# Click Add
_deluge_click_active_window_button(browser, 'Add')
if browser.find_by_id('fileUploadForm'): # deluge-web 2.x
browser.attach_file('file', file_path)
else: # deluge-web 1.x
browser.find_by_css('button.x-deluge-add-file').first.click()
eventually(
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
# Add from file window appears
eventually(
lambda: _deluge_get_active_window_title(browser) == 'Add from File'
)
# Attach file
browser.attach_file('file', file_path)
# Click Add
_deluge_click_active_window_button(browser, 'Add')
eventually(
lambda: _deluge_get_active_window_title(browser) == 'Add Torrents')
# Click Add
time.sleep(1)

View File

@ -34,12 +34,3 @@ secure_proxy_ssl_header = HTTP_X_FORWARDED_PROTO
[Misc]
box_name = FreedomBox
# The danube_edition changes the firstboot process and offers entering a
# voucher for a freedombox.me sub-domain. This functionality requires
# additional debian packages to be installed:
#
# pagekite, python3-requests
#
# They are not added as dependencies to keep the normal installation images
# lean, but make sure to add them if you want to build danube-edition images.
danube_edition = False

View File

@ -18,4 +18,4 @@
Package init file.
"""
__version__ = '19.17'
__version__ = '19.18'

View File

@ -423,12 +423,14 @@ def diagnose_netcat(host, port, input='', negate=False):
result = 'failed'
else:
result = 'passed'
if negate:
result = 'failed' if result == 'passed' else 'passed'
except Exception:
result = 'failed'
test = _('Connect to {host}:{port}')
if negate:
result = 'failed' if result == 'passed' else 'passed'
test = _('Cannot connect to {host}:{port}')
return [test.format(host=host, port=port), result]

View File

@ -37,7 +37,6 @@ use_x_forwarded_host = False
secure_proxy_ssl_header = None
develop = False
server_dir = '/'
danube_edition = False
config_file = None
@ -102,7 +101,6 @@ def read(config_path=None, root_directory=None):
('Network', 'use_x_forwarded_for', 'bool'),
('Network', 'use_x_forwarded_host', 'bool'),
('Misc', 'box_name', 'string'),
('Misc', 'danube_edition', 'bool'),
)
for section, name, datatype in config_items:

View File

@ -14,7 +14,6 @@
# 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/>.
#
"""
Project specific errors
"""
@ -30,11 +29,6 @@ class ActionError(PlinthError):
pass
class DomainRegistrationError(PlinthError):
"""Domain registration failed"""
pass
class PackageNotInstalledError(PlinthError):
"""Could not complete module setup due to missing package."""
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "base.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -22,7 +22,9 @@
{% load i18n %}
{% load static %}
{% block configuration %}
{% block content %}
<h3>{{ title }}</h3>
<form class="form" method="post">
{% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "base.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -24,7 +24,9 @@
{% block page_head %}
{% endblock %}
{% block configuration %}
{% block content %}
<h3>{{ title }}</h3>
<p>
{% blocktrans %}

View File

@ -28,9 +28,9 @@ from plinth.modules.apache.components import Webserver
from plinth.modules.firewall.components import Firewall
from plinth.modules.users import register_group
from .manifest import backup, clients # noqa, pylint: disable=unused-import
from .manifest import backup, clients # noqa, pylint: disable=unused-import
version = 2
version = 3
managed_services = ['deluge-web']

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -55,6 +55,7 @@ def index(request):
return TemplateResponse(
request, 'dynamicdns.html', {
'title': dynamicdns.name,
'name': dynamicdns.name,
'description': dynamicdns.description,
'manual_page': dynamicdns.manual_page,
'subsubmenu': subsubmenu
@ -78,6 +79,7 @@ def configure(request):
return TemplateResponse(
request, 'dynamicdns_configure.html', {
'title': _('Configure Dynamic DNS'),
'name': dynamicdns.name,
'description': dynamicdns.description,
'manual_page': dynamicdns.manual_page,
'form': form,
@ -103,6 +105,7 @@ def statuspage(request):
return TemplateResponse(
request, 'dynamicdns_status.html', {
'title': _('Dynamic DNS Status'),
'name': dynamicdns.name,
'description': dynamicdns.description,
'manual_page': dynamicdns.manual_page,
'no_nat': no_nat,

View File

@ -29,7 +29,7 @@ from plinth.modules.firewall.components import Firewall
from plinth.modules.i2p.resources import FAVORITES
from plinth.modules.users import register_group
from .manifest import backup, clients # noqa, pylint: disable=unused-import
from .manifest import backup, clients # noqa, pylint: disable=unused-import
version = 1
@ -97,7 +97,7 @@ class I2PApp(app_module.App):
is_external=True)
self.add(firewall)
firewall = Firewall('firewall-i2p-proxies', name,
firewall = Firewall('firewall-i2p-proxies', _('I2P Proxy'),
ports=tunnels_to_manage.values(),
is_external=False)
self.add(firewall)

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -22,34 +22,6 @@
{% load i18n %}
{% block configuration %}
{% block status %}
{% if show_status_block %}
<h3>{% trans "Status" %}</h3>
<p class="running-status-parent">
{% with service_name=name %}
{% if is_running %}
<span class="running-status active"></span>
{% blocktrans trimmed %}
Service <em>{{ service_name }}</em> is running.
{% endblocktrans %}
{% else %}
<span class="running-status inactive"></span>
{% blocktrans trimmed %}
Service <em>{{ service_name }}</em> is not running.
{% endblocktrans %}
{% endif %}
{% endwith %}
</p>
{% endif %}
{% endblock %}
{% block diagnostics %}
{% if diagnostics_module_name %}
{% include "diagnostics_button.html" with module=diagnostics_module_name enabled=is_enabled %}
{% endif %}
{% endblock %}
{% include "port-forwarding-info.html" with service_name=title %}
<h3>{% trans "Configuration" %}</h3>

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -71,6 +71,7 @@ class ServiceBaseView(TemplateView):
"""Add context data for template."""
context = super().get_context_data(**kwargs)
context['title'] = i2p.name
context['name'] = i2p.name
context['description'] = i2p.description
context['clients'] = i2p.clients
context['manual_page'] = i2p.manual_page

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -21,22 +21,49 @@
{% load bootstrap %}
{% load i18n %}
{% block configuration %}
<h3>{% trans "Configuration" %}</h3>
{% block diagnostics %}
{% if diagnostics_module_name %}
{% include "diagnostics_button.html" with module=diagnostics_module_name enabled=is_enabled %}
{% endif %}
{% endblock %}
<form class="form form-configuration" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary"
value="{% trans "Update setup" %}"/>
</form>
{% block status %}
<a href="{% url 'ikiwiki:create' %}" class="btn btn-primary"
role="button" title="{% trans 'Create Wiki or Blog' %}">
<span class="fa fa-plus" aria-hidden="true"></span>
{% trans 'Create Wiki or Blog' %}
</a>
{% endblock %}
{% block configuration %}
{{ block.super }}
<h3>{% trans "Manage Wikis and Blogs" %}</h3>
<div class="row">
<div class="col-sm-6">
{% if not sites %}
<p>{% trans "No wikis or blogs available." %}</p>
<p>
<a class="btn btn-primary btn-md"
href="{% url 'ikiwiki:create' %}">
{% trans "Create a Wiki or Blog" %}
</a>
</p>
{% else %}
<div class="list-group">
{% for site in sites %}
<div class="list-group-item clearfix">
<a href="{% url 'ikiwiki:delete' site %}"
class="btn btn-default btn-sm pull-right"
role="button"
title="{% blocktrans %}Delete site {{ site }}{% endblocktrans %}">
<span class="fa fa-trash-o"
aria-hidden="true"></span>
</a>
<a class="wiki-label" href="/ikiwiki/{{ site }}"
title="{% blocktrans %}Go to site {{ site }}{% endblocktrans %}">
{{ site }}
</a>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "base.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -21,7 +21,7 @@
{% load bootstrap %}
{% load i18n %}
{% block configuration %}
{% block content %}
<h3>{% trans "Create Wiki or Blog" %}</h3>
<form class="form" method="post">

View File

@ -42,9 +42,6 @@
<input type="submit" class="btn btn-md btn-danger"
value="{% blocktrans %}Delete {{ name }}{% endblocktrans %}"/>
<a href="{% url 'ikiwiki:manage' %}" role="button"
class="btn btn-md btn-primary">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -1,72 +0,0 @@
{% extends "app-subsubmenu.html" %}
{% comment %}
#
# 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/>.
#
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% block page_head %}
<style type="text/css">
.wiki-label {
display: inline-block;
width: 40%;
}
.list-group-item .btn {
margin: -5px 0;
}
</style>
{% endblock %}
{% block configuration %}
<h3>{% trans "Manage Wikis and Blogs" %}</h3>
<div class="row">
<div class="col-sm-6">
{% if not sites %}
<p>{% trans "No wikis or blogs available." %}</p>
<p>
<a class="btn btn-primary btn-md"
href="{% url 'ikiwiki:create' %}">
{% trans "Create a Wiki or Blog" %}
</a>
</p>
{% else %}
<div class="list-group">
{% for site in sites %}
<div class="list-group-item clearfix">
<a href="{% url 'ikiwiki:delete' site %}"
class="btn btn-default btn-sm pull-right"
role="button"
title="{% blocktrans %}Delete site {{ site }}{% endblocktrans %}">
<span class="fa fa-trash-o"
aria-hidden="true"></span>
</a>
<a class="wiki-label" href="/ikiwiki/{{ site }}"
title="{% blocktrans %}Go to site {{ site }}{% endblocktrans %}">
{{ site }}
</a>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -24,7 +24,6 @@ from . import views
urlpatterns = [
url(r'^apps/ikiwiki/$', views.IkiwikiAppView.as_view(), name='index'),
url(r'^apps/ikiwiki/manage/$', views.manage, name='manage'),
url(r'^apps/ikiwiki/(?P<name>[\w.@+-]+)/delete/$', views.delete,
name='delete'),
url(r'^apps/ikiwiki/create/$', views.create, name='create'),

View File

@ -23,24 +23,12 @@ from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from plinth import actions, views
from plinth.modules import ikiwiki
from .forms import IkiwikiCreateForm
subsubmenu = [{
'url': reverse_lazy('ikiwiki:index'),
'text': ugettext_lazy('Configure')
}, {
'url': reverse_lazy('ikiwiki:manage'),
'text': ugettext_lazy('Manage')
}, {
'url': reverse_lazy('ikiwiki:create'),
'text': ugettext_lazy('Create')
}]
class IkiwikiAppView(views.AppView):
"""Serve configuration page."""
@ -50,34 +38,19 @@ class IkiwikiAppView(views.AppView):
diagnostics_module_name = 'ikiwiki'
show_status_block = False
template_name = 'ikiwiki_configure.html'
manual_page = ikiwiki.manual_page
clients = ikiwiki.clients
def get_context_data(self, **kwargs):
"""Return the context data for rendering the template view."""
sites = actions.run('ikiwiki', ['get-sites']).split('\n')
sites = [name for name in sites if name != '']
context = super().get_context_data(**kwargs)
context['title'] = ikiwiki.name
context['subsubmenu'] = subsubmenu
context['clients'] = ikiwiki.clients
context['manual_page'] = ikiwiki.manual_page
context['sites'] = sites
return context
def manage(request):
"""Manage existing wikis and blogs."""
sites = actions.run('ikiwiki', ['get-sites']).split('\n')
sites = [name for name in sites if name != '']
return TemplateResponse(
request, 'ikiwiki_manage.html', {
'title': ikiwiki.name,
'clients': ikiwiki.clients,
'description': ikiwiki.description,
'manual_page': ikiwiki.manual_page,
'subsubmenu': subsubmenu,
'sites': sites,
'is_enabled': ikiwiki.app.is_enabled(),
})
def create(request):
"""Form to create a wiki or blog."""
form = None
@ -98,7 +71,7 @@ def create(request):
shortcut = ikiwiki.app.add_shortcut(site)
shortcut.enable()
return redirect(reverse_lazy('ikiwiki:manage'))
return redirect(reverse_lazy('ikiwiki:index'))
else:
form = IkiwikiCreateForm(prefix='ikiwiki')
@ -109,7 +82,6 @@ def create(request):
'description': ikiwiki.description,
'form': form,
'manual_page': ikiwiki.manual_page,
'subsubmenu': subsubmenu,
'is_enabled': ikiwiki.app.is_enabled(),
})
@ -157,7 +129,7 @@ def delete(request, name):
_('Could not delete {name}: {error}').format(
name=name, error=error))
return redirect(reverse_lazy('ikiwiki:manage'))
return redirect(reverse_lazy('ikiwiki:index'))
return TemplateResponse(request, 'ikiwiki_delete.html', {
'title': ikiwiki.name,

View File

@ -29,7 +29,7 @@ from plinth.daemon import Daemon
from plinth.modules.firewall.components import Firewall
from plinth.utils import format_lazy
from .manifest import backup, clients # noqa, pylint: disable=unused-import
from .manifest import backup, clients # noqa, pylint: disable=unused-import
version = 2
@ -42,9 +42,8 @@ mods = [
'minetest-mod-moreblocks', 'minetest-mod-moreores', 'minetest-mod-nether',
'minetest-mod-pipeworks', 'minetest-mod-player-3d-armor',
'minetest-mod-protector', 'minetest-mod-quartz', 'minetest-mod-skyblock',
'minetest-mod-throwing', 'minetest-mod-torches',
'minetest-mod-unified-inventory', 'minetest-mod-unifieddyes',
'minetest-mod-worldedit'
'minetest-mod-throwing', 'minetest-mod-unified-inventory',
'minetest-mod-unifieddyes', 'minetest-mod-worldedit'
]
managed_packages = ['minetest-server'] + mods

View File

@ -35,6 +35,13 @@ managed_packages = ['network-manager', 'batctl']
name = _('Networks')
description = [
_('Configure network devices. Connect to the Internet via Ethernet, Wi-Fi '
'or PPPoE. Share that connection with other devices on the network.'),
_('Devices administered through other methods may not be available for '
'configuration here.'),
]
logger = Logger(__name__)
manual_page = 'Networks'

View File

@ -22,7 +22,6 @@ from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.decorators.http import require_POST
from plinth import network
@ -33,17 +32,6 @@ from .forms import (ConnectionTypeSelectForm, EthernetForm, GenericForm,
logger = Logger(__name__)
subsubmenu = [{
'url': reverse_lazy('networks:index'),
'text': ugettext_lazy('Network Connections')
}, {
'url': reverse_lazy('networks:scan'),
'text': ugettext_lazy('Nearby Wi-Fi Networks')
}, {
'url': reverse_lazy('networks:add'),
'text': ugettext_lazy('Add Connection')
}]
def index(request):
"""Show connection list."""
@ -52,8 +40,11 @@ def index(request):
return TemplateResponse(
request, 'connections_list.html', {
'title': _('Network Connections'),
'name': networks.name,
'description': networks.description,
'manual_page': networks.manual_page,
'subsubmenu': subsubmenu,
'diagnostics_module_name': 'networks',
'is_enabled': True,
'connections': connections
})
@ -100,7 +91,6 @@ def show(request, uuid):
return TemplateResponse(
request, 'connection_show.html', {
'title': _('Connection Information'),
'subsubmenu': subsubmenu,
'connection': connection_status,
'active_connection': active_connection_status,
'device': device_status,
@ -141,12 +131,10 @@ def edit(request, uuid):
return redirect(reverse_lazy('networks:index'))
else:
return TemplateResponse(
request, 'connections_edit.html', {
'title': _('Edit Connection'),
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(request, 'connections_edit.html', {
'title': _('Edit Connection'),
'form': form
})
else:
settings_connection = connection.get_setting_connection()
form_data['interface'] = connection.get_interface_name()
@ -229,7 +217,6 @@ def edit(request, uuid):
return TemplateResponse(request, 'connections_edit.html', {
'title': _('Edit Connection'),
'subsubmenu': subsubmenu,
'form': form
})
@ -243,14 +230,16 @@ def activate(request, uuid):
messages.success(request,
_('Activated connection {name}.').format(name=name))
except network.ConnectionNotFound:
messages.error(request,
_('Failed to activate connection: '
'Connection not found.'))
messages.error(
request,
_('Failed to activate connection: '
'Connection not found.'))
except network.DeviceNotFound as exception:
name = exception.args[0].get_id()
messages.error(request,
_('Failed to activate connection {name}: '
'No suitable device is available.').format(name=name))
messages.error(
request,
_('Failed to activate connection {name}: '
'No suitable device is available.').format(name=name))
return redirect(reverse_lazy('networks:index'))
@ -264,9 +253,10 @@ def deactivate(request, uuid):
messages.success(request,
_('Deactivated connection {name}.').format(name=name))
except network.ConnectionNotFound:
messages.error(request,
_('Failed to de-activate connection: '
'Connection not found.'))
messages.error(
request,
_('Failed to de-activate connection: '
'Connection not found.'))
return redirect(reverse_lazy('networks:index'))
@ -274,12 +264,10 @@ def deactivate(request, uuid):
def scan(request):
"""Show a list of nearby visible Wi-Fi access points."""
access_points = network.wifi_scan()
return TemplateResponse(
request, 'wifi_scan.html', {
'title': _('Nearby Wi-Fi Networks'),
'subsubmenu': subsubmenu,
'access_points': access_points
})
return TemplateResponse(request, 'wifi_scan.html', {
'title': _('Nearby Wi-Fi Networks'),
'access_points': access_points
})
def add(request):
@ -302,7 +290,6 @@ def add(request):
form = ConnectionTypeSelectForm()
return TemplateResponse(request, 'connections_type_select.html', {
'title': _('Add Connection'),
'subsubmenu': subsubmenu,
'form': form
})
@ -319,12 +306,10 @@ def add_generic(request):
else:
form = GenericForm()
return TemplateResponse(
request, 'connections_create.html', {
'title': _('Adding New Generic Connection'),
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(request, 'connections_create.html', {
'title': _('Adding New Generic Connection'),
'form': form
})
def add_ethernet(request):
@ -339,12 +324,10 @@ def add_ethernet(request):
else:
form = EthernetForm()
return TemplateResponse(
request, 'connections_create.html', {
'title': _('Adding New Ethernet Connection'),
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(request, 'connections_create.html', {
'title': _('Adding New Ethernet Connection'),
'form': form
})
def add_pppoe(request):
@ -359,12 +342,10 @@ def add_pppoe(request):
else:
form = PPPoEForm()
return TemplateResponse(
request, 'connections_create.html', {
'title': _('Adding New PPPoE Connection'),
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(request, 'connections_create.html', {
'title': _('Adding New PPPoE Connection'),
'form': form
})
def add_wifi(request, ssid=None, interface_name=None):
@ -396,12 +377,10 @@ def add_wifi(request, ssid=None, interface_name=None):
else:
form = WifiForm()
return TemplateResponse(
request, 'connections_create.html', {
'title': _('Adding New Wi-Fi Connection'),
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(request, 'connections_create.html', {
'title': _('Adding New Wi-Fi Connection'),
'form': form
})
def delete(request, uuid):
@ -416,9 +395,10 @@ def delete(request, uuid):
messages.success(request,
_('Connection {name} deleted.').format(name=name))
except network.ConnectionNotFound:
messages.error(request,
_('Failed to delete connection: '
'Connection not found.'))
messages.error(
request,
_('Failed to delete connection: '
'Connection not found.'))
return redirect(reverse_lazy('networks:index'))
@ -426,13 +406,12 @@ def delete(request, uuid):
connection = network.get_connection(uuid)
name = connection.get_id()
except network.ConnectionNotFound:
messages.error(request,
_('Failed to delete connection: '
'Connection not found.'))
messages.error(
request, _('Failed to delete connection: '
'Connection not found.'))
return redirect(reverse_lazy('networks:index'))
return TemplateResponse(request, 'connections_delete.html', {
'title': _('Delete Connection'),
'subsubmenu': subsubmenu,
'name': name
})

View File

@ -23,6 +23,8 @@
{% block content %}
<h3>{{ title }}</h3>
<form class="form" method="post">
{% csrf_token %}

View File

@ -34,11 +34,8 @@
<form class="form" method="post">
{% csrf_token %}
<input type="submit" class="btn btn-md btn-primary"
<input type="submit" class="btn btn-md btn-danger"
value="Delete {{ name }}"/>
<a href="{% url 'networks:index' %}" role="button"
class="btn btn-md btn-primary">{% trans "Cancel" %}</a>
</form>
{% endblock %}

View File

@ -24,6 +24,8 @@
{% block content %}
<h3>{{ title }}</h3>
<form class="form" method="post">
{% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -54,7 +54,22 @@
</style>
{% endblock %}
{% block content %}
{% block status %}
<a href="{% url 'networks:scan' %}" class="btn btn-default"
role="button" title="{% trans 'Nearby Wi-Fi Networks' %}">
<span class="fa fa-wifi" aria-hidden="true"></span>
{% trans "Nearby Wi-Fi Networks" %}
</a>
<a href="{% url 'networks:add' %}" class="btn btn-default"
role="button" title="{% trans 'Add Connection' %}">
<span class="fa fa-plus" aria-hidden="true"></span>
{% trans "Add Connection" %}
</a>
{% endblock %}
{% block configuration %}
<h3>{% trans "Connections" %}</h3>
<div class="list-group">
{% for connection in connections %}
@ -106,6 +121,4 @@
{% include "connections_diagram.html" %}
{% include "diagnostics_button.html" with module="networks" enabled=True %}
{% endblock %}

View File

@ -23,6 +23,8 @@
{% block content %}
<h3>{{ title }}</h3>
<form class="form" method="post">
{% csrf_token %}

View File

@ -23,6 +23,8 @@
{% block content %}
<h3>{{ title }}</h3>
<div class="row">
<div class="col-sm-6">
<div class="list-group">

View File

@ -36,14 +36,6 @@ managed_services = ['pagekite']
managed_packages = ['pagekite']
first_boot_steps = [
{
'id': 'pagekite_firstboot',
'url': 'pagekite:firstboot',
'order': 5,
},
]
name = _('PageKite')
short_description = _('Public Visibility')

View File

@ -16,20 +16,18 @@
#
import copy
import json
import logging
from django import forms
from django.contrib import messages
from django.core import validators
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _, ugettext_lazy
import json
import logging
import requests
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from plinth.errors import ActionError
from . import utils
from plinth import cfg
from plinth.errors import ActionError, DomainRegistrationError
from plinth.modules.pagekite.utils import PREDEFINED_SERVICES, run
from plinth.utils import format_lazy
LOGGER = logging.getLogger(__name__)
@ -72,21 +70,19 @@ class ConfigurationForm(forms.Form):
label=ugettext_lazy('Server domain'), required=False,
help_text=ugettext_lazy(
'Select your pagekite server. Set "pagekite.net" to use '
'the default pagekite.net server.'),
widget=forms.TextInput())
'the default pagekite.net server.'), widget=forms.TextInput())
server_port = forms.IntegerField(
label=ugettext_lazy('Server port'), required=False,
help_text=ugettext_lazy('Port of your pagekite server (default: 80)'))
kite_name = TrimmedCharField(
label=ugettext_lazy('Kite name'),
help_text=ugettext_lazy('Example: mybox.pagekite.me'),
validators=[
help_text=ugettext_lazy('Example: mybox.pagekite.me'), validators=[
validators.RegexValidator(r'^[\w-]{1,63}(\.[\w-]{1,63})*$',
ugettext_lazy('Invalid kite name'))])
ugettext_lazy('Invalid kite name'))
])
kite_secret = TrimmedCharField(
label=ugettext_lazy('Kite secret'),
help_text=ugettext_lazy(
label=ugettext_lazy('Kite secret'), help_text=ugettext_lazy(
'A secret associated with the kite or the default secret '
'for your account if no secret is set on the kite.'))
@ -143,9 +139,8 @@ class StandardServiceForm(forms.Form):
help_text = service['help_text'].format(kite['kite_name'])
else:
help_text = service['help_text']
self.fields[name] = forms.BooleanField(label=service['label'],
help_text=help_text,
required=False)
self.fields[name] = forms.BooleanField(
label=service['label'], help_text=help_text, required=False)
def save(self, request):
formdata = self.cleaned_data
@ -155,12 +150,15 @@ class StandardServiceForm(forms.Form):
service = json.dumps(service)
if formdata[service_name]:
utils.run(['add-service', '--service', service])
messages.success(request, _('Service enabled: {name}')
.format(name=service_name))
messages.success(
request,
_('Service enabled: {name}').format(name=service_name))
else:
utils.run(['remove-service', '--service', service])
messages.success(request, _('Service disabled: {name}')
.format(name=service_name))
messages.success(
request,
_('Service disabled: {name}').format(
name=service_name))
# Update kite services registered with Name Services module.
utils.update_names_module()
@ -169,8 +167,8 @@ class StandardServiceForm(forms.Form):
class BaseCustomServiceForm(forms.Form):
"""Basic form functionality to handle a custom service"""
choices = [('http', 'http'), ('https', 'https'), ('raw', 'raw')]
protocol = forms.ChoiceField(
choices=choices, label=ugettext_lazy('protocol'))
protocol = forms.ChoiceField(choices=choices,
label=ugettext_lazy('protocol'))
frontend_port = forms.IntegerField(
min_value=0, max_value=65535,
label=ugettext_lazy('external (frontend) port'), required=True)
@ -261,101 +259,3 @@ class AddCustomServiceForm(BaseCustomServiceForm):
messages.error(request, _('This service already exists'))
else:
raise
class FirstBootForm(forms.Form):
"""Set up freedombox.me pagekite subdomain"""
DOMAIN_APPENDIX = '.freedombox.me'
# Webservice url for domain validation and registration
service_url = 'http://freedombox.me/cgi-bin/freedomkite.pl'
code_help_text = format_lazy(
ugettext_lazy('The voucher you received with your {box_name} Danube '
'Edition'), box_name=ugettext_lazy(cfg.box_name))
code = forms.CharField(help_text=code_help_text)
domain = forms.SlugField(label=_('Subdomain'),
widget=SubdomainWidget(domain=DOMAIN_APPENDIX),
help_text=_('The subdomain you want to register'))
def __init__(self, *args, **kwargs):
"""Initialize the form."""
super().__init__(*args, **kwargs)
self.fields['code'].widget.attrs.update({'autofocus': 'autofocus'})
def clean_domain(self):
"""Append the domain to the users' subdomain"""
return self.cleaned_data['domain'] + self.DOMAIN_APPENDIX
def clean(self):
"""Validate user input (subdomain and code)"""
cleaned_data = super().clean()
# If the subdomain is wrong, don't look if the domain is
# available
if self.errors:
return cleaned_data
self.domain_already_registered = False
code = cleaned_data.get('code')
domain = cleaned_data.get('domain')
response = requests.get(self.service_url, params={'code': code}).json()
# 1. Code is invalid: {}
if 'domain' not in response:
raise ValidationError(_('This code is not valid'), code='invalid')
# 2. Code is valid, domain registered: {'domain': 'xx.freedombox.me'}
elif response['domain']:
if response['domain'] == domain:
self.domain_already_registered = True
else:
message = _('This code is bound to the domain {domain}.') \
.format(domain=response['domain'])
raise ValidationError(message, code='invalid')
# 3. Code is valid, no domain registered: {'domain': None}
elif response['domain'] is None:
# Make sure that the desired domain is available
data = {'domain': domain}
domain_response = requests.get(self.service_url, params=data)
registered_domain = domain_response.json()['domain']
if registered_domain is not None:
message = _('The requested domain is already registered.')
raise ValidationError(message, code='invalid')
return cleaned_data
def register_domain(self):
"""Register a domain (only if it's not already registered)"""
if self.domain_already_registered:
return
data = {'domain': self.cleaned_data['domain'],
'code': self.cleaned_data['code']}
response = requests.post(self.service_url, data)
if not response.ok:
message = _('Domain registration failed: {response}.').format(
response=response.text)
LOGGER.error(message)
raise DomainRegistrationError(message)
def setup_pagekite(self):
"""Configure and enable PageKite service."""
# Set kite name and secret
run(['set-kite', '--kite-name', self.cleaned_data['domain']],
input=self.cleaned_data['code'].encode())
# Set frontend
run(['set-frontend', '%s:80' % self.cleaned_data['domain']])
# Enable PageKite HTTP + HTTPS service
for service_name in ['http', 'https']:
service = PREDEFINED_SERVICES[service_name]['params']
try:
run(['add-service', '--service', json.dumps(service)])
except ActionError as err:
if 'already exists' not in str(err):
raise
run(['start-and-enable'])

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -20,8 +20,8 @@ URLs for the PageKite module
from django.conf.urls import url
from .views import StandardServiceView, CustomServiceView, ConfigurationView, \
DeleteServiceView, FirstBootView, first_boot_skip
from .views import (ConfigurationView, CustomServiceView, DeleteServiceView,
StandardServiceView)
urlpatterns = [
url(r'^sys/pagekite/$', ConfigurationView.as_view(), name='index'),
@ -31,8 +31,4 @@ urlpatterns = [
name='custom-services'),
url(r'^sys/pagekite/services/custom/delete/$', DeleteServiceView.as_view(),
name='delete-custom-service'),
url(r'^sys/pagekite/firstboot/$', FirstBootView.as_view(),
name='firstboot'),
url(r'^sys/pagekite/firstboot/skip/$', first_boot_skip,
name='firstboot-skip'),
]

View File

@ -15,21 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, View
from django.views.generic.edit import FormView
from plinth import cfg
from plinth.errors import DomainRegistrationError
from plinth.modules import first_boot, pagekite
from plinth.modules import pagekite
from . import utils
from .forms import (AddCustomServiceForm, ConfigurationForm,
DeleteCustomServiceForm, FirstBootForm,
StandardServiceForm)
DeleteCustomServiceForm, StandardServiceForm)
subsubmenu = [{
'url': reverse_lazy('pagekite:index'),
@ -55,6 +51,7 @@ class ContextMixin(object):
"""Use self.title and the module-level subsubmenu"""
context = super(ContextMixin, self).get_context_data(**kwargs)
context['title'] = pagekite.name
context['name'] = pagekite.name
context['description'] = pagekite.description
context['manual_page'] = pagekite.manual_page
context['subsubmenu'] = subsubmenu
@ -131,37 +128,3 @@ class ConfigurationView(ContextMixin, FormView):
def form_valid(self, form):
form.save(self.request)
return super(ConfigurationView, self).form_valid(form)
class FirstBootView(FormView):
"""First boot (optional) setup of the Pagekite subdomain."""
template_name = 'pagekite_firstboot.html'
form_class = FirstBootForm
def get(self, request, *args, **kwargs):
"""Skip this first boot step if it is not relevant."""
if not cfg.danube_edition:
return first_boot_skip(request)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
"""Act on valid form submission."""
try:
form.register_domain()
except DomainRegistrationError as error:
messages.error(self.request, error)
return self.form_invalid(form)
form.setup_pagekite()
first_boot.mark_step_done('pagekite_firstboot')
message = _('Pagekite setup finished. The HTTP and HTTPS services '
'are activated now.')
messages.success(self.request, message)
return HttpResponseRedirect(reverse(first_boot.next_step()))
def first_boot_skip(request):
"""Skip the first boot step."""
first_boot.mark_step_done('pagekite_firstboot')
return HttpResponseRedirect(reverse(first_boot.next_step()))

View File

@ -21,6 +21,7 @@ FreedomBox app for security configuration.
import subprocess
from collections import defaultdict
import requests
from django.utils.translation import ugettext_lazy as _
from plinth import actions
@ -121,13 +122,23 @@ def get_vulnerability_counts():
(label, package, *_) = line.split()
cves[label].add(package)
try:
past_cves = requests.get(
'https://security-tracker.debian.org/tracker/data/json').json()
except Exception:
past_cves = None
apps = {
'freedombox': {
'name': 'freedombox',
'packages': {'freedombox'},
'count': 0,
'past_count': 0 if past_cves else None,
}
}
if past_cves and 'freedombox' in past_cves:
apps['freedombox']['past_count'] = len(past_cves['freedombox'])
for module_name, module in module_loader.loaded_modules.items():
try:
packages = module.managed_packages
@ -142,8 +153,13 @@ def get_vulnerability_counts():
'name': module_name,
'packages': set(packages),
'count': 0,
'past_count': 0 if past_cves else None,
}
for package in packages:
if past_cves and package in past_cves:
apps[module_name]['past_count'] += len(past_cves[package])
for cve_packages in cves.values():
for app_ in apps.values():
if cve_packages & app_['packages']:

View File

@ -22,42 +22,9 @@
{% load i18n %}
{% block status %}
<h3>{% trans "Status" %}</h3>
<p>
{% blocktrans trimmed with count=freedombox_vulns.count %}
The installed version of FreedomBox has {{ count }} reported security
vulnerabilities.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
The following table lists the reported number of security vulnerabilities
for each installed app.
{% endblocktrans %}
</p>
<a class="btn btn-default collapsed collapsible-button" role="button"
data-toggle="collapse" href="#collapse-vulns" aria-expanded="false"
aria-controls="collapse-vulns">
<span class="fa fa-chevron-right fa-fw" aria-hidden="true"></span>
{% trans "Show security vulnerabilities" %}
<a class="btn btn-default" role="button" href="{% url 'security:report' %}"
title="{% trans 'Show security report' %}">
<span class="fa fa-line-chart" aria-hidden="true"></span>
{% trans "Show security report" %}
</a>
<div class="collapse" id="collapse-vulns">
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "App Name" %}</th>
<th>{% trans "Vulnerabilities Reported" %}</th>
</tr>
</thead>
<tbody>
{% for app in apps_vulns %}
<tr>
<td>{{ app.name }}</td>
<td>{{ app.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,56 @@
{% extends "base.html" %}
{% comment %}
#
# 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/>.
#
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% block content %}
<h3>{% trans "Security Report" %}</h3>
<p>
{% blocktrans trimmed with count=freedombox_vulns.count %}
The installed version of FreedomBox has {{ count }} reported security
vulnerabilities.
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
The following table lists the current reported number, and historical
count, of security vulnerabilities for each installed app.
{% endblocktrans %}
</p>
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>{% trans "App Name" %}</th>
<th>{% trans "Current Vulnerabilities" %}</th>
<th>{% trans "Past Vulnerabilities" %}</th>
</tr>
</thead>
<tbody>
{% for app in apps_vulns %}
<tr>
<td>{{ app.name }}</td>
<td>{{ app.count }}</td>
<td>{{ app.past_count|default_if_none:"❗"}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -26,4 +26,5 @@ from . import views
urlpatterns = [
url(r'^sys/security/$', views.index, name='index'),
url(r'^sys/security/report$', views.report, name='report'),
]

View File

@ -43,7 +43,6 @@ def index(request):
else:
form = SecurityForm(initial=status, prefix='security')
vulnerability_counts = security.get_vulnerability_counts()
return TemplateResponse(
request, 'security.html', {
'name':
@ -52,11 +51,6 @@ def index(request):
security.manual_page,
'form':
form,
'freedombox_vulns':
vulnerability_counts.pop('freedombox'),
'apps_vulns':
sorted(vulnerability_counts.values(),
key=lambda app: app['name']),
})
@ -86,3 +80,18 @@ def _apply_changes(request, old_status, new_status):
actions.superuser_run('service', ['enable', 'fail2ban'])
else:
actions.superuser_run('service', ['disable', 'fail2ban'])
def report(request):
"""Serve the security report page"""
vulnerability_counts = security.get_vulnerability_counts()
return TemplateResponse(
request, 'security_report.html', {
'title':
_('Security Report'),
'freedombox_vulns':
vulnerability_counts.pop('freedombox'),
'apps_vulns':
sorted(vulnerability_counts.values(),
key=lambda app: app['name']),
})

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.

View File

@ -35,19 +35,23 @@ from plinth.modules import storage
from . import get_configuration
from .forms import SnapshotForm
subsubmenu = [{
'url': reverse_lazy('snapshot:index'),
'text': ugettext_lazy('Configure')
}, {
'url': reverse_lazy('snapshot:manage'),
'text': ugettext_lazy('Manage Snapshots')
}]
subsubmenu = [
{
'url': reverse_lazy('snapshot:index'),
'text': ugettext_lazy('Configure')
},
{
'url': reverse_lazy('snapshot:manage'),
'text': ugettext_lazy('Manage Snapshots')
},
]
def not_supported_view(request):
"""Show that snapshots are not supported on the system."""
template_data = {
'title': snapshot_module.name,
'name': snapshot_module.name,
'description': snapshot_module.description,
'fs_type': storage.get_filesystem_type(),
'fs_types_supported': snapshot_module.fs_types_supported,
@ -72,13 +76,15 @@ def index(request):
else:
form = SnapshotForm(initial=status)
return TemplateResponse(request, 'snapshot.html', {
'title': snapshot_module.name,
'description': snapshot_module.description,
'manual_page': snapshot_module.manual_page,
'subsubmenu': subsubmenu,
'form': form
})
return TemplateResponse(
request, 'snapshot.html', {
'title': snapshot_module.name,
'name': snapshot_module.name,
'description': snapshot_module.description,
'manual_page': snapshot_module.manual_page,
'subsubmenu': subsubmenu,
'form': form
})
def manage(request):
@ -104,6 +110,7 @@ def manage(request):
return TemplateResponse(
request, 'snapshot_manage.html', {
'title': snapshot_module.name,
'name': snapshot_module.name,
'description': snapshot_module.description,
'manual_page': snapshot_module.manual_page,
'snapshots': snapshots,

View File

@ -1,4 +1,4 @@
{% extends 'app-subsubmenu.html' %}
{% extends 'base.html' %}
{% comment %}
#
# This file is part of FreedomBox.
@ -32,7 +32,9 @@
{% endblock %}
{% block configuration %}
{% block content %}
<h2>{{ title }}</h2>
{% if not is_busy %}
<p>

View File

@ -1,4 +1,4 @@
{% extends "app-subsubmenu.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -21,15 +21,9 @@
{% load bootstrap %}
{% load i18n %}
{% block configuration %}
<form class="form" method="post">
{% csrf_token %}
{{ form|bootstrap }}
<input type="submit" class="btn btn-primary btn-md"
value="{% trans "Update setup" %}"/>
</form>
{% block status %}
<a href="{% url 'upgrades:upgrade' %}" class="btn btn-default"
role="button" title="{% trans 'Manual update' %}">
{% trans 'Manual update' %}
</a>
{% endblock %}

View File

@ -22,39 +22,26 @@ from django.contrib import messages
from django.template.response import TemplateResponse
from django.urls import reverse_lazy
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views.generic.edit import FormView
from plinth import actions
from plinth.errors import ActionError
from plinth.modules import upgrades
from plinth.views import AppView
from .forms import ConfigureForm
subsubmenu = [{
'url': reverse_lazy('upgrades:index'),
'text': ugettext_lazy('Auto-update')
},
{
'url': reverse_lazy('upgrades:upgrade'),
'text': ugettext_lazy('Manual update')
}]
class UpgradesConfigurationView(FormView):
class UpgradesConfigurationView(AppView):
"""Serve configuration page."""
form_class = ConfigureForm
success_url = reverse_lazy('upgrades:index')
template_name = "upgrades_configure.html"
def get_context_data(self, *args, **kwargs):
"""Return the context data for rendering the template view."""
context = super().get_context_data(*args, **kwargs)
context['subsubmenu'] = subsubmenu
context['title'] = upgrades.name
context['description'] = upgrades.description
context['manual_page'] = upgrades.manual_page
return context
app_id = 'upgrades'
name = upgrades.name
description = upgrades.description
manual_page = upgrades.manual_page
show_status_block = False
def get_initial(self):
return {'auto_upgrades_enabled': upgrades.is_enabled()}
@ -87,7 +74,7 @@ class UpgradesConfigurationView(FormView):
else:
messages.info(self.request, _('Settings unchanged'))
return super().form_valid(form)
return FormView.form_valid(self, form)
def is_package_manager_busy():
@ -116,12 +103,8 @@ def upgrade(request):
except ActionError:
messages.error(request, _('Starting upgrade failed.'))
return TemplateResponse(
request, 'upgrades.html', {
'title': upgrades.name,
'description': upgrades.description,
'manual_page': upgrades.manual_page,
'subsubmenu': subsubmenu,
'is_busy': is_busy,
'log': get_log()
})
return TemplateResponse(request, 'upgrades.html', {
'title': _('Manual update'),
'is_busy': is_busy,
'log': get_log()
})

View File

@ -24,7 +24,8 @@ from django.utils.translation import ugettext_lazy as _
from plinth import action_utils, actions
from plinth import app as app_module
from plinth import menu
from plinth import cfg, menu
from plinth.utils import format_lazy
version = 2
@ -45,6 +46,20 @@ first_boot_steps = [
name = _('Users and Groups')
description = [
_('Create and managed user accounts. These accounts serve as centralized '
'authentication mechanism for most apps. Some apps further require a '
'user account to be part of a group to authorize the user to access the '
'app.'),
format_lazy(
_('Any user may login to {box_name} web interface to see a list of '
'apps relevant to them in the home page. However, only users of '
'the <em>admin</em> group may alter apps or system settings.'),
box_name=_(cfg.box_name))
]
manual_page = 'Users'
# All FreedomBox user groups
groups = dict()

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "app.html" %}
{% comment %}
#
# This file is part of FreedomBox.
@ -33,7 +33,17 @@
</style>
{% endblock %}
{% block content %}
{% block status %}
<a href="{% url 'users:create' %}" class="btn btn-primary"
role="button" title="{% trans 'Create User' %}">
<span class="fa fa-plus" aria-hidden="true"></span>
{% trans 'Create User' %}
</a>
{% endblock %}
{% block configuration %}
<h3>{% trans "Users" %}</h3>
<div class="row">
<div class="col-sm-6">
@ -64,7 +74,6 @@
{% endfor %}
</div>
{% include "diagnostics_button.html" with module="users" enabled=True %}
</div>
</div>

View File

@ -29,29 +29,21 @@ from django.views.generic.edit import (CreateView, DeleteView, FormView,
from plinth import actions
from plinth.errors import ActionError
from plinth.modules import first_boot
from plinth.modules import first_boot, users
from plinth.utils import is_user_admin
from plinth.views import AppView
from . import get_last_admin_user
from .forms import (CreateUserForm, FirstBootForm, UserChangePasswordForm,
UserUpdateForm)
subsubmenu = [{
'url': reverse_lazy('users:index'),
'text': ugettext_lazy('Users')
}, {
'url': reverse_lazy('users:create'),
'text': ugettext_lazy('Create User')
}]
class ContextMixin(object):
"""Mixin to add 'subsubmenu' and 'title' to the context."""
"""Mixin to add 'title' to the template context."""
def get_context_data(self, **kwargs):
"""Use self.title and the module-level subsubmenu"""
"""Add self.title to template context."""
context = super(ContextMixin, self).get_context_data(**kwargs)
context['subsubmenu'] = subsubmenu
context['title'] = getattr(self, 'title', '')
return context
@ -76,11 +68,17 @@ class UserCreate(ContextMixin, SuccessMessageMixin, CreateView):
return reverse('users:index')
class UserList(ContextMixin, django.views.generic.ListView):
class UserList(AppView, ContextMixin, django.views.generic.ListView):
"""View to list users."""
model = User
template_name = 'users_list.html'
title = ugettext_lazy('Users')
name = users.name
description = users.description
app_id = 'users'
show_status_block = False
diagnostics_module_name = 'users'
manual_page = users.manual_page
def get_context_data(self, *args, **kwargs):
context = super(UserList, self).get_context_data(*args, **kwargs)
@ -129,13 +127,6 @@ class UserUpdate(ContextMixin, SuccessMessageMixin, UpdateView):
"""Return the URL to redirect to in case of successful updation."""
return reverse('users:edit', kwargs={'slug': self.object.username})
def get_context_data(self, **kwargs):
"""Use self.title and the module-level subsubmenu"""
context = super(UserUpdate, self).get_context_data(**kwargs)
if not is_user_admin(self.request):
del context['subsubmenu']
return context
class UserDelete(ContextMixin, DeleteView):
"""Handle deleting users, showing a confirmation dialog first.
@ -207,13 +198,6 @@ class UserChangePassword(ContextMixin, SuccessMessageMixin, FormView):
update_session_auth_hash(self.request, form.user)
return super(UserChangePassword, self).form_valid(form)
def get_context_data(self, **kwargs):
"""Remove subsubmenu for non-admin users."""
context = super(UserChangePassword, self).get_context_data(**kwargs)
if not is_user_admin(self.request):
del context['subsubmenu']
return context
class FirstBootView(django.views.generic.CreateView):
"""Create user account and log the user in."""

View File

@ -1,66 +0,0 @@
{% extends "base.html" %}
{% comment %}
#
# 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/>.
#
{% endcomment %}
{% load bootstrap %}
{% load i18n %}
{% load plinth_extras %}
{% load static %}
{% block container %}
<div class="container content-container">
{% block content_row %}
{% include 'messages.html' %}
{% block content %}
{% block pagetitle %}
<h2>{{ title }}</h2>
{% endblock %}
{% block description %}
{% for paragraph in description %}
<p>{{ paragraph|safe }}</p>
{% endfor %}
{% endblock %}
{% if manual_page %}
<p class="manual-page">
<a href="{% url 'help:manual-page' manual_page %}">
{% trans 'Learn more...' %}
</a>
</p>
{% endif %}
{% include "clients.html" with clients=clients enabled=is_enabled %}
{% block subsubmenu %}
{% if subsubmenu %}
{% show_subsubmenu subsubmenu %}
{% endif %}
{% endblock %}
{% block configuration %}
{% endblock %}
{% endblock %}
{% endblock %}
</div>
{% endblock %}

View File

@ -22,6 +22,7 @@
{% load bootstrap %}
{% load i18n %}
{% load plinth_extras %}
{% load static %}
{% block content %}
@ -46,6 +47,12 @@
{% include "clients.html" with clients=clients enabled=is_enabled %}
{% block subsubmenu %}
{% if subsubmenu %}
{% show_subsubmenu subsubmenu %}
{% endif %}
{% endblock %}
{% block status %}
{% if show_status_block %}
<h3>{% trans "Status" %}</h3>

View File

@ -223,12 +223,6 @@
{% block container %}
<div class="container content-container">
{% block content_row %}
{% block subsubmenu %}
{% if subsubmenu %}
{% show_subsubmenu subsubmenu %}
{% endif %}
{% endblock %}
{% include 'messages.html' %}
{% block content %}

View File

@ -34,12 +34,3 @@ secure_proxy_ssl_header = HTTP_X_FORWARDED_PROTO
[Misc]
box_name = FreedomBox
# The danube_edition changes the firstboot process and offers entering a
# voucher for a freedombox.me sub-domain. This functionality requires
# additional debian packages to be installed:
#
# pagekite, python3-requests
#
# They are not added as dependencies to keep the normal installation images
# lean, but make sure to add them if you want to build danube-edition images.
danube_edition = False

View File

@ -1,3 +1,2 @@
[Misc]
box_name = FreedomBox
danube_edition = False

View File

@ -141,6 +141,5 @@ def compare_configurations(parser):
assert isinstance(cfg.use_x_forwarded_host, bool)
assert parser.get('Network', 'use_x_forwarded_host') == \
str(cfg.use_x_forwarded_host)
assert len(parser.items('Misc')) == 3
assert parser.get('Misc', 'danube_edition') == str(cfg.danube_edition)
assert len(parser.items('Misc')) == 2
assert parser.get('Misc', 'box_name') == cfg.box_name

View File

@ -273,6 +273,7 @@ setuptools.setup(
'pytest-cov',
'pytest-django',
'flake8',
'requests',
],
package_data={
'': ['templates/*', 'static/*', 'locale/*/LC_MESSAGES/*.[pm]o']