Backups, remote repositories: re-use template for root location

Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Michael Pimmer 2018-11-28 18:09:03 +00:00 committed by James Valleroy
parent e3817a1a31
commit 27fbc982c7
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
7 changed files with 118 additions and 94 deletions

View File

@ -24,9 +24,10 @@ import os
from django.utils.text import get_valid_filename
from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth.menu import main_menu
from plinth import actions, cfg
from plinth.errors import ActionError
from plinth.menu import main_menu
from plinth.utils import format_lazy
from .errors import BorgError, BorgRepositoryDoesNotExistError
from . import api
@ -97,6 +98,19 @@ def list_archives(repository, access_params=None):
return json.loads(output)['archives']
def get_root_location_content():
"""
Get information about the root backup location in the same format
that the remote repositories use
"""
return {
'name': format_lazy(_('{box_name} storage'), box_name=cfg.box_name),
'mounted': True,
'archives': list_archives(REPOSITORY),
'type': 'rootfs',
}
def get_archive(name):
# TODO: can't we get this archive directly?
for archive in list_archives():

View File

@ -0,0 +1,24 @@
#
# 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/>.
#
"""
Manage borg backup locations
"""
class Location(object):
def __init__(self, uuid):
pass

View File

@ -73,7 +73,7 @@ def delete(uuid):
logger.error(err)
def get_archives(uuid=None):
def get_locations_content(uuid=None):
"""
Get archives of one or all locations.
returns: {
@ -81,7 +81,7 @@ def get_archives(uuid=None):
'path': path,
'type': type,
'archives': [],
'error': error_message
'error': error_message,
}
}
"""
@ -89,7 +89,7 @@ def get_archives(uuid=None):
for location in get_locations():
mountpoint = os.path.join(MOUNTPOINT, location['uuid'])
new_location = {
'path': location['path'],
'name': location['path'],
'mounted': uuid_is_mounted(location['uuid']),
}
if new_location['mounted']:

View File

@ -52,45 +52,10 @@
<h3>{% trans 'Existing backups' %}</h3>
{% if not archives %}
<p>{% trans 'No archives currently exist.' %}</p>
{% else %}
<table class="table table-bordered table-condensed table-striped"
id="archives-list">
<thead>
<tr>
<th>{{ box_name }} storage</th>
<th></th>
</tr>
</thead>
{% include "backups_location.inc" with location=root_location uuid='root' editable=False %}
<tbody>
{% for archive in archives %}
<tr id="archive-{{ archive.name }}" class="archive">
<td class="archive-name">{{ archive.name }}</td>
<td class="archive-operations">
<a class="archive-export btn btn-sm btn-default" target="_blank"
href="{% url 'backups:export-and-download' archive.name %}">
{% trans "Download" %}
</a>
<a class="archive-export btn btn-sm btn-default"
href="{% url 'backups:restore-archive' archive.name %}">
{% trans "Restore" %}
</a>
<a class="archive-delete btn btn-sm btn-default"
href="{% url 'backups:delete' archive.name %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% for uuid,location in remote_archives.items %}
{% include "backups_location.inc" %}
{% for uuid,location in remote_locations.items %}
{% include "backups_location.inc" with editable=True %}
{% endfor %}
<br />

View File

@ -24,34 +24,46 @@
<thead>
<tr>
<th>
{{ location.path }}
{% if location.mounted %}
<form action="{% url 'backups:location-umount' uuid %}" method="POST"
class="inline-block" >
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-default"
title="{% trans 'Unount Location' %}">
<span class="glyphicon glyphicon-eject" aria-hidden="true">
</button>
</form>
{% else %}
<!-- With GET redirects the browser URL would be pointing to the
redirected page - use POST instead.
-->
<form action="{% url 'backups:location-mount' uuid %}" method="POST"
class="inline-block" >
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-default"
title="{% trans 'Mount Location' %}">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true">
</button>
</form>
{{ location.name }}
{% if editable %}
{% if location.mounted %}
<!-- With GET redirects the browser URL would be pointing to the
redirected page - use POST instead.
-->
<form action="{% url 'backups:location-umount' uuid %}" method="POST"
class="inline-block" >
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-default"
title="{% trans 'Unount Location' %}">
<span class="glyphicon glyphicon-eject" aria-hidden="true">
</button>
</form>
{% else %}
<form action="{% url 'backups:location-mount' uuid %}" method="POST"
class="inline-block" >
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-default"
title="{% trans 'Mount Location' %}">
<span class="glyphicon glyphicon-eye-open" aria-hidden="true">
</button>
</form>
{% endif %}
<a title="{% trans 'Remove Location. This will not delete the remote backup.' %}"
role="button" class="location-remove btn btn-sm btn-default"
href="{% url 'backups:location-remove' uuid %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</a>
{% endif %}
<a title="{% trans 'Remove Location. This will not delete the remote backup.' %}"
role="button" class="location-remove btn btn-sm btn-default"
href="{% url 'backups:location-remove' uuid %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</a>
</th>
<th>
</th>
@ -59,25 +71,33 @@
</thead>
<tbody>
{% for archive in location.archives %}
<tr id="archive-{{ archive.name }}" class="archive">
<td class="archive-name">{{ archive.name }}</td>
<td class="archive-operations">
<a class="archive-export btn btn-sm btn-default" target="_blank"
href="{% url 'backups:export-and-download' archive.name %}">
{% trans "Download" %}
</a>
<a class="archive-export btn btn-sm btn-default"
href="{% url 'backups:restore-archive' archive.name %}">
{% trans "Restore" %}
</a>
<a class="archive-delete btn btn-sm btn-default"
href="{% url 'backups:delete' archive.name %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</span>
</a>
</td>
</tr>
{% endfor %}
{% if location.mounted %}
{% for archive in location.archives %}
<tr id="archive-{{ archive.name }}" class="archive">
<td class="archive-name">{{ archive.name }}</td>
<td class="archive-operations">
<a class="archive-export btn btn-sm btn-default" target="_blank"
href="{% url 'backups:export-and-download' uuid archive.name %}">
{% trans "Download" %}
</a>
<a class="archive-export btn btn-sm btn-default"
href="{% url 'backups:restore-archive' archive.name %}">
{% trans "Restore" %}
</a>
<a class="archive-delete btn btn-sm btn-default"
href="{% url 'backups:delete' archive.name %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true">
</span>
</a>
</td>
</tr>
{% endfor %}
{% if not location.archives %}
<p>{% trans 'No archives currently exist.' %}</p>
{% endif %}
{% endif %}
</tbody>
</table>

View File

@ -28,7 +28,7 @@ from .views import IndexView, CreateArchiveView, AddLocationView, \
urlpatterns = [
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
url(r'^sys/backups/create/$', CreateArchiveView.as_view(), name='create'),
url(r'^sys/backups/export-and-download/(?P<name>[^/]+)/$',
url(r'^sys/backups/export-and-download/(?P<uuid>[^/]+)/(?P<name>[^/]+)/$',
ExportAndDownloadView.as_view(), name='export-and-download'),
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
DeleteArchiveView.as_view(), name='delete'),

View File

@ -73,8 +73,8 @@ class IndexView(TemplateView):
context['title'] = backups.name
context['description'] = backups.description
context['info'] = backups.get_info(REPOSITORY)
context['archives'] = backups.list_archives(REPOSITORY)
context['remote_archives'] = remote_locations.get_archives()
context['root_location'] = backups.get_root_location_content()
context['remote_locations'] = remote_locations.get_locations_content()
context['subsubmenu'] = subsubmenu
return context
@ -275,7 +275,8 @@ class ZipStream(object):
class ExportAndDownloadView(View):
"""View to export and download an archive as stream."""
def get(self, request, name):
def get(self, request, uuid, name):
# The uuid is 'root' for the root repository
name = unquote(name)
filename = "%s.tar.gz" % name
args = ['export-tar', '--name', name]