diff --git a/data/etc/plinth/modules-enabled/udiskie b/data/etc/plinth/modules-enabled/udiskie
deleted file mode 100644
index bb489cbd2..000000000
--- a/data/etc/plinth/modules-enabled/udiskie
+++ /dev/null
@@ -1 +0,0 @@
-plinth.modules.udiskie
diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py
index 9be12676e..b461213b1 100644
--- a/plinth/modules/backups/__init__.py
+++ b/plinth/modules/backups/__init__.py
@@ -27,7 +27,7 @@ from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth.menu import main_menu
-from plinth.modules import udiskie
+from plinth.modules import storage
from .backups import backup_apps, restore_apps
@@ -118,8 +118,8 @@ def export_archive(name, location):
def get_export_locations():
"""Return a list of storage locations for exported backup archives."""
locations = [('/var/lib/freedombox/', _('Root Filesystem'))]
- if udiskie.is_running():
- devices = udiskie.udisks2.list_devices()
+ if storage.is_running():
+ devices = storage.udisks2.list_devices()
for device in devices:
if 'mount_points' in device and len(device['mount_points']) > 0:
name = device['label'] or device['device']
diff --git a/plinth/modules/storage/__init__.py b/plinth/modules/storage/__init__.py
index 569190d09..7a97aa9e6 100644
--- a/plinth/modules/storage/__init__.py
+++ b/plinth/modules/storage/__init__.py
@@ -17,21 +17,32 @@
"""
FreedomBox app to manage storage.
"""
-
import json
import logging
import subprocess
from django.utils.translation import ugettext_lazy as _
-from plinth import actions
+from plinth import service as service_module
+from plinth import action_utils, actions, cfg
from plinth.menu import main_menu
+from plinth.utils import format_lazy
-version = 2
+version = 3
name = _('Storage')
-description = []
+managed_services = ['freedombox-udiskie']
+
+managed_packages = ['udiskie', 'gir1.2-udisks-2.0']
+
+description = [
+ format_lazy(
+ _('This module allows you to manage storage media attached to your '
+ '{box_name}. You can view the storage media currently in use, mount '
+ 'and unmount removable media, expand the root partition etc.'),
+ box_name=_(cfg.box_name))
+]
service = None
@@ -81,8 +92,8 @@ def _get_disks_from_df():
disks = []
for line in output.splitlines()[1:]:
parts = line.split(maxsplit=6)
- keys = ('device', 'file_system_type', 'size', 'used',
- 'free', 'percent_used', 'mount_point')
+ keys = ('device', 'file_system_type', 'size', 'used', 'free',
+ 'percent_used', 'mount_point')
disk = dict(zip(keys, parts))
disk['percent_used'] = int(disk['percent_used'].rstrip('%'))
disk['size'] = int(disk['size'])
@@ -168,8 +179,37 @@ def format_bytes(size):
return _('{disk_size:.1f} TiB').format(disk_size=size)
+def is_running():
+ """Return whether the service is running."""
+ return action_utils.service_is_running('freedombox-udiskie')
+
+
+def is_enabled():
+ """Return whether the module is enabled."""
+ return action_utils.service_is_enabled('freedombox-udiskie')
+
+
+def enable():
+ """Enable the module."""
+ actions.superuser_run('udiskie', ['enable'])
+
+
+def disable():
+ """Disable the module."""
+ actions.superuser_run('udiskie', ['disable'])
+
+
def setup(helper, old_version=None):
- """Expand root parition on first setup."""
+ """Install and configure the module."""
+ helper.install(managed_packages, skip_recommends=True)
+ helper.call('post', actions.superuser_run, 'udiskie', ['enable'])
+ global service
+ if service is None:
+ service = service_module.Service(
+ managed_services[0], name, ports=[], is_external=True,
+ is_enabled=is_enabled, enable=enable, disable=disable,
+ is_running=is_running)
+ helper.call('post', service.notify_enabled, None, True)
disks = get_disks()
root_device = get_root_device(disks)
if is_expandable(root_device):
diff --git a/plinth/modules/storage/templates/storage.html b/plinth/modules/storage/templates/storage.html
index 69b06b2da..fa9e3710d 100644
--- a/plinth/modules/storage/templates/storage.html
+++ b/plinth/modules/storage/templates/storage.html
@@ -36,6 +36,12 @@
{{ title }}
{% endblock %}
+ {% block description %}
+ {% for paragraph in description %}
+ {{ paragraph|safe }}
+ {% endfor %}
+ {% endblock %}
+
{% if manual_page %}
@@ -44,7 +50,7 @@
{% endif %}
- {% trans "The following disks are in use:" %}
+ {% trans "The following storage devices are in use:" %}
@@ -66,17 +72,17 @@
{% if disk.percent_used < 75 %}
- {{ disk.percent_used }}%
-
-
- {{ disk.used_str }} / {{ disk.size_str }}
+ role="progressbar" aria-valuenow="disk.percent_used"
+ aria-valuemin="0" aria-valuemax="100"
+ style="width: {{ disk.percent_used }}%;">
+ {{ disk.percent_used }}%
+
+
+ {{ disk.used_str }} / {{ disk.size_str }}
{% endfor %}
@@ -100,4 +106,55 @@
{% endif %}
+ {% block status %}
+
+ {{ block.super }}
+
+ {% trans "Removable Devices" %}
+
+ {% if not devices %}
+
+ {% blocktrans trimmed %}
+ There are no additional storage devices attached.
+ {% endblocktrans %}
+
+ {% else %}
+
+
+
+ | {% trans "Device" %} |
+ {% trans "Label" %} |
+ {% trans "Mount Point" %} |
+ {% trans "Type" %} |
+ {% trans "Size" %} |
+ {% trans "Actions" %} |
+
+
+
+ {% for device in devices %}
+
+ | {{ device.device }} |
+ {{ device.label }} |
+ {{ device.mount_points|join:', ' }} |
+ {{ device.filesystem_type }} |
+ {{ device.size }} |
+
+ {% if device.mount_points %}
+
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endblock %}
{% endblock %}
diff --git a/plinth/modules/udiskie/udisks2.py b/plinth/modules/storage/udisks2.py
similarity index 100%
rename from plinth/modules/udiskie/udisks2.py
rename to plinth/modules/storage/udisks2.py
diff --git a/plinth/modules/storage/urls.py b/plinth/modules/storage/urls.py
index 6f005e079..1eda3efa0 100644
--- a/plinth/modules/storage/urls.py
+++ b/plinth/modules/storage/urls.py
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
#
-
"""
URLs for the disks module.
"""
@@ -23,8 +22,9 @@ from django.conf.urls import url
from . import views
-
urlpatterns = [
url(r'^sys/storage/$', views.index, name='index'),
url(r'^sys/storage/expand$', views.expand, name='expand'),
+ url(r'^sys/storage/eject/(?P[\w%]+)/$', views.eject,
+ name='eject')
]
diff --git a/plinth/modules/storage/views.py b/plinth/modules/storage/views.py
index b3738cbf5..80db7f7f0 100644
--- a/plinth/modules/storage/views.py
+++ b/plinth/modules/storage/views.py
@@ -19,16 +19,20 @@ Views for storage module.
"""
import logging
+import urllib.parse
from django.contrib import messages
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.translation import ugettext as _
+from django.views.decorators.http import require_POST
from plinth.modules import storage
from plinth.utils import format_lazy, is_user_admin
+from . import udisks2
+
logger = logging.getLogger(__name__)
@@ -44,7 +48,9 @@ def index(request):
return TemplateResponse(
request, 'storage.html', {
'title': _('Storage'),
+ 'description': storage.description,
'disks': disks,
+ 'devices': udisks2.list_devices(),
'manual_page': storage.manual_page,
'expandable_root_size': expandable_root_size
})
@@ -73,9 +79,10 @@ def expand_partition(request, device):
try:
storage.expand_partition(device)
except Exception as exception:
- messages.error(request,
- _('Error expanding partition: {exception}')
- .format(exception=exception))
+ messages.error(
+ request,
+ _('Error expanding partition: {exception}')
+ .format(exception=exception))
else:
messages.success(request, _('Partition expanded successfully.'))
@@ -106,3 +113,37 @@ def warn_about_low_disk_space(request):
messages.error(request, message)
elif percent_used > 75 or free_gib < 2:
messages.warning(request, message)
+
+
+@require_POST
+def eject(request, device_path):
+ """Eject a device, given its path.
+
+ Device path is quoted with slashes written as %2F.
+
+ """
+ device_path = urllib.parse.unquote(device_path)
+
+ try:
+ drive = udisks2.eject_drive_of_device(device_path)
+ if drive:
+ messages.success(
+ request,
+ _('{drive_vendor} {drive_model} can be safely unplugged.')
+ .format(drive_vendor=drive['vendor'],
+ drive_model=drive['model']))
+ else:
+ messages.success(request, _('Device can be safely unplugged.'))
+ except Exception as exception:
+ try:
+ message = udisks2.get_error_message(exception)
+ except AttributeError:
+ message = str(exception)
+
+ logger.exception('Error ejecting device - %s', message)
+ messages.error(
+ request,
+ _('Error ejecting device: {error_message}').format(
+ error_message=message))
+
+ return redirect(reverse('storage:index'))
diff --git a/plinth/modules/udiskie/__init__.py b/plinth/modules/udiskie/__init__.py
deleted file mode 100644
index 1c8d531cd..000000000
--- a/plinth/modules/udiskie/__init__.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# 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 .
-#
-"""
-FreedomBox app for udiskie.
-"""
-
-from django.utils.translation import ugettext_lazy as _
-
-from plinth import action_utils, actions
-from plinth import service as service_module
-from plinth.menu import main_menu
-
-version = 1
-
-managed_services = ['freedombox-udiskie']
-
-managed_packages = ['udiskie', 'gir1.2-udisks-2.0']
-
-name = _('udiskie')
-
-short_description = _('Removable Media')
-
-description = [
- _('udiskie allows automatic mounting of removable media, such as flash '
- 'drives.'),
-]
-
-service = None
-
-
-def init():
- """Intialize the module."""
- menu = main_menu.get('system')
- menu.add_urlname(name, 'glyphicon-floppy-disk', 'udiskie:index',
- short_description)
-
- global service
- setup_helper = globals()['setup_helper']
- if setup_helper.get_state() != 'needs-setup':
- service = service_module.Service(
- managed_services[0], name, ports=[], is_external=True,
- is_enabled=is_enabled, enable=enable, disable=disable,
- is_running=is_running)
-
-
-def setup(helper, old_version=None):
- """Install and configure the module."""
- helper.install(managed_packages, skip_recommends=True)
- helper.call('post', actions.superuser_run, 'udiskie', ['enable'])
- global service
- if service is None:
- service = service_module.Service(
- managed_services[0], name, ports=[], is_external=True,
- is_enabled=is_enabled, enable=enable, disable=disable,
- is_running=is_running)
- helper.call('post', service.notify_enabled, None, True)
-
-
-def is_running():
- """Return whether the service is running."""
- return action_utils.service_is_running('freedombox-udiskie')
-
-
-def is_enabled():
- """Return whether the module is enabled."""
- return action_utils.service_is_enabled('freedombox-udiskie')
-
-
-def enable():
- """Enable the module."""
- actions.superuser_run('udiskie', ['enable'])
-
-
-def disable():
- """Disable the module."""
- actions.superuser_run('udiskie', ['disable'])
diff --git a/plinth/modules/udiskie/templates/udiskie.html b/plinth/modules/udiskie/templates/udiskie.html
deleted file mode 100644
index d30ae9126..000000000
--- a/plinth/modules/udiskie/templates/udiskie.html
+++ /dev/null
@@ -1,75 +0,0 @@
-{% extends "service.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 .
-#
-{% endcomment %}
-
-{% load bootstrap %}
-{% load i18n %}
-{% load static %}
-
-{% block status %}
-
- {{ block.super }}
-
- {% trans "Devices" %}
-
- {% if not devices %}
-
- {% blocktrans trimmed %}
- There are no additional storage devices attached.
- {% endblocktrans %}
-
- {% else %}
-
-
-
- | {% trans "Device" %} |
- {% trans "Label" %} |
- {% trans "Size" %} |
- {% trans "Filesystem" %} |
- {% trans "Mount Point" %} |
- {% trans "Actions" %} |
-
-
-
- {% for device in devices %}
-
- | {{ device.device }} |
- {{ device.label }} |
- {{ device.size }} |
- {{ device.filesystem_type }} |
- {{ device.mount_points|join:', ' }} |
-
- {% if device.mount_points %}
-
- {% endif %}
- |
-
- {% endfor %}
-
-
- {% endif %}
-
-{% endblock %}
diff --git a/plinth/modules/udiskie/urls.py b/plinth/modules/udiskie/urls.py
deleted file mode 100644
index 519acb26b..000000000
--- a/plinth/modules/udiskie/urls.py
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# 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 .
-#
-"""
-URLs for the udiskie module.
-"""
-
-from django.conf.urls import url
-
-from plinth.modules import udiskie
-
-from .views import Index, eject
-
-urlpatterns = [
- url(
- r'^sys/udiskie/$',
- Index.as_view(service_id=udiskie.managed_services[0],
- description=udiskie.description, show_status_block=True),
- name='index'),
- url(r'^sys/udiskie/eject/(?P[\w%]+)/$', eject, name='eject'),
-]
diff --git a/plinth/modules/udiskie/views.py b/plinth/modules/udiskie/views.py
deleted file mode 100644
index c00cbb689..000000000
--- a/plinth/modules/udiskie/views.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#
-# 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 .
-#
-"""
-Views for udiskie module.
-"""
-
-import urllib.parse
-from logging import Logger
-
-from django.contrib import messages
-from django.shortcuts import redirect
-from django.urls import reverse
-from django.utils.translation import ugettext as _
-from django.views.decorators.http import require_POST
-
-from plinth.views import ServiceView
-
-from . import udisks2
-
-logger = Logger(__name__)
-
-
-class Index(ServiceView):
- """View to show devices."""
- template_name = 'udiskie.html'
-
- def get_context_data(self, *args, **kwargs):
- """Return the context data rendering the template."""
- context = super().get_context_data(**kwargs)
- context['devices'] = udisks2.list_devices()
- return context
-
-
-@require_POST
-def eject(request, device_path):
- """Eject a device, given its path.
-
- Device path is quoted with slashes written as %2F.
-
- """
- device_path = urllib.parse.unquote(device_path)
-
- try:
- drive = udisks2.eject_drive_of_device(device_path)
- if drive:
- messages.success(
- request,
- _('{drive_vendor} {drive_model} can be safely unplugged.')
- .format(drive_vendor=drive['vendor'],
- drive_model=drive['model']))
- else:
- messages.success(request, _('Device can be safely unplugged.'))
- except Exception as exception:
- try:
- message = udisks2.get_error_message(exception)
- except AttributeError:
- message = str(exception)
-
- logger.exception('Error ejecting device - %s', message)
- messages.error(
- request,
- _('Error ejecting device: {error_message}').format(
- error_message=message))
-
- return redirect(reverse('udiskie:index'))