mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-10 11:00:22 +00:00
Merge tag 'v19.18' into debian/buster-backports
Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
commit
b62f122429
@ -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():
|
||||
|
||||
@ -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
47
debian/changelog
vendored
@ -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
3
debian/control
vendored
@ -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
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -18,4 +18,4 @@
|
||||
Package init file.
|
||||
"""
|
||||
|
||||
__version__ = '19.17'
|
||||
__version__ = '19.18'
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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']
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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 %}
|
||||
@ -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'),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
})
|
||||
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="list-group">
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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'])
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
@ -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()))
|
||||
|
||||
@ -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']:
|
||||
|
||||
@ -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 %}
|
||||
|
||||
56
plinth/modules/security/templates/security_report.html
Normal file
56
plinth/modules/security/templates/security_report.html
Normal 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 %}
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
@ -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']),
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "app-subsubmenu.html" %}
|
||||
{% extends "app.html" %}
|
||||
{% comment %}
|
||||
#
|
||||
# This file is part of FreedomBox.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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()
|
||||
})
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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."""
|
||||
|
||||
@ -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 %}
|
||||
@ -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>
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
[Misc]
|
||||
box_name = FreedomBox
|
||||
danube_edition = False
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user