mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
Backups, remote repositories: re-use template for root location
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
e3817a1a31
commit
27fbc982c7
@ -24,9 +24,10 @@ import os
|
|||||||
from django.utils.text import get_valid_filename
|
from django.utils.text import get_valid_filename
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from plinth import actions
|
from plinth import actions, cfg
|
||||||
from plinth.menu import main_menu
|
|
||||||
from plinth.errors import ActionError
|
from plinth.errors import ActionError
|
||||||
|
from plinth.menu import main_menu
|
||||||
|
from plinth.utils import format_lazy
|
||||||
from .errors import BorgError, BorgRepositoryDoesNotExistError
|
from .errors import BorgError, BorgRepositoryDoesNotExistError
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
@ -97,6 +98,19 @@ def list_archives(repository, access_params=None):
|
|||||||
return json.loads(output)['archives']
|
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):
|
def get_archive(name):
|
||||||
# TODO: can't we get this archive directly?
|
# TODO: can't we get this archive directly?
|
||||||
for archive in list_archives():
|
for archive in list_archives():
|
||||||
|
|||||||
24
plinth/modules/backups/location.py
Normal file
24
plinth/modules/backups/location.py
Normal 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
|
||||||
@ -73,7 +73,7 @@ def delete(uuid):
|
|||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
|
||||||
|
|
||||||
def get_archives(uuid=None):
|
def get_locations_content(uuid=None):
|
||||||
"""
|
"""
|
||||||
Get archives of one or all locations.
|
Get archives of one or all locations.
|
||||||
returns: {
|
returns: {
|
||||||
@ -81,7 +81,7 @@ def get_archives(uuid=None):
|
|||||||
'path': path,
|
'path': path,
|
||||||
'type': type,
|
'type': type,
|
||||||
'archives': [],
|
'archives': [],
|
||||||
'error': error_message
|
'error': error_message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@ -89,7 +89,7 @@ def get_archives(uuid=None):
|
|||||||
for location in get_locations():
|
for location in get_locations():
|
||||||
mountpoint = os.path.join(MOUNTPOINT, location['uuid'])
|
mountpoint = os.path.join(MOUNTPOINT, location['uuid'])
|
||||||
new_location = {
|
new_location = {
|
||||||
'path': location['path'],
|
'name': location['path'],
|
||||||
'mounted': uuid_is_mounted(location['uuid']),
|
'mounted': uuid_is_mounted(location['uuid']),
|
||||||
}
|
}
|
||||||
if new_location['mounted']:
|
if new_location['mounted']:
|
||||||
|
|||||||
@ -52,45 +52,10 @@
|
|||||||
|
|
||||||
<h3>{% trans 'Existing backups' %}</h3>
|
<h3>{% trans 'Existing backups' %}</h3>
|
||||||
|
|
||||||
{% if not archives %}
|
{% include "backups_location.inc" with location=root_location uuid='root' editable=False %}
|
||||||
<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>
|
|
||||||
|
|
||||||
<tbody>
|
{% for uuid,location in remote_locations.items %}
|
||||||
{% for archive in archives %}
|
{% include "backups_location.inc" with editable=True %}
|
||||||
<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" %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@ -24,34 +24,46 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{{ location.path }}
|
|
||||||
{% if location.mounted %}
|
{{ location.name }}
|
||||||
<form action="{% url 'backups:location-umount' uuid %}" method="POST"
|
|
||||||
class="inline-block" >
|
{% if editable %}
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-sm btn-default"
|
{% if location.mounted %}
|
||||||
title="{% trans 'Unount Location' %}">
|
|
||||||
<span class="glyphicon glyphicon-eject" aria-hidden="true">
|
<!-- With GET redirects the browser URL would be pointing to the
|
||||||
</button>
|
redirected page - use POST instead.
|
||||||
</form>
|
-->
|
||||||
{% else %}
|
<form action="{% url 'backups:location-umount' uuid %}" method="POST"
|
||||||
<!-- With GET redirects the browser URL would be pointing to the
|
class="inline-block" >
|
||||||
redirected page - use POST instead.
|
{% csrf_token %}
|
||||||
-->
|
<button type="submit" class="btn btn-sm btn-default"
|
||||||
<form action="{% url 'backups:location-mount' uuid %}" method="POST"
|
title="{% trans 'Unount Location' %}">
|
||||||
class="inline-block" >
|
<span class="glyphicon glyphicon-eject" aria-hidden="true">
|
||||||
{% csrf_token %}
|
</button>
|
||||||
<button type="submit" class="btn btn-sm btn-default"
|
</form>
|
||||||
title="{% trans 'Mount Location' %}">
|
|
||||||
<span class="glyphicon glyphicon-eye-open" aria-hidden="true">
|
{% else %}
|
||||||
</button>
|
|
||||||
</form>
|
<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 %}
|
{% 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>
|
<th>
|
||||||
</th>
|
</th>
|
||||||
@ -59,25 +71,33 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for archive in location.archives %}
|
{% if location.mounted %}
|
||||||
<tr id="archive-{{ archive.name }}" class="archive">
|
|
||||||
<td class="archive-name">{{ archive.name }}</td>
|
{% for archive in location.archives %}
|
||||||
<td class="archive-operations">
|
<tr id="archive-{{ archive.name }}" class="archive">
|
||||||
<a class="archive-export btn btn-sm btn-default" target="_blank"
|
<td class="archive-name">{{ archive.name }}</td>
|
||||||
href="{% url 'backups:export-and-download' archive.name %}">
|
<td class="archive-operations">
|
||||||
{% trans "Download" %}
|
<a class="archive-export btn btn-sm btn-default" target="_blank"
|
||||||
</a>
|
href="{% url 'backups:export-and-download' uuid archive.name %}">
|
||||||
<a class="archive-export btn btn-sm btn-default"
|
{% trans "Download" %}
|
||||||
href="{% url 'backups:restore-archive' archive.name %}">
|
</a>
|
||||||
{% trans "Restore" %}
|
<a class="archive-export btn btn-sm btn-default"
|
||||||
</a>
|
href="{% url 'backups:restore-archive' archive.name %}">
|
||||||
<a class="archive-delete btn btn-sm btn-default"
|
{% trans "Restore" %}
|
||||||
href="{% url 'backups:delete' archive.name %}">
|
</a>
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true">
|
<a class="archive-delete btn btn-sm btn-default"
|
||||||
</span>
|
href="{% url 'backups:delete' archive.name %}">
|
||||||
</a>
|
<span class="glyphicon glyphicon-trash" aria-hidden="true">
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
</a>
|
||||||
{% endfor %}
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if not location.archives %}
|
||||||
|
<p>{% trans 'No archives currently exist.' %}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -28,7 +28,7 @@ from .views import IndexView, CreateArchiveView, AddLocationView, \
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
||||||
url(r'^sys/backups/create/$', CreateArchiveView.as_view(), name='create'),
|
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'),
|
ExportAndDownloadView.as_view(), name='export-and-download'),
|
||||||
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
||||||
DeleteArchiveView.as_view(), name='delete'),
|
DeleteArchiveView.as_view(), name='delete'),
|
||||||
|
|||||||
@ -73,8 +73,8 @@ class IndexView(TemplateView):
|
|||||||
context['title'] = backups.name
|
context['title'] = backups.name
|
||||||
context['description'] = backups.description
|
context['description'] = backups.description
|
||||||
context['info'] = backups.get_info(REPOSITORY)
|
context['info'] = backups.get_info(REPOSITORY)
|
||||||
context['archives'] = backups.list_archives(REPOSITORY)
|
context['root_location'] = backups.get_root_location_content()
|
||||||
context['remote_archives'] = remote_locations.get_archives()
|
context['remote_locations'] = remote_locations.get_locations_content()
|
||||||
context['subsubmenu'] = subsubmenu
|
context['subsubmenu'] = subsubmenu
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -275,7 +275,8 @@ class ZipStream(object):
|
|||||||
|
|
||||||
class ExportAndDownloadView(View):
|
class ExportAndDownloadView(View):
|
||||||
"""View to export and download an archive as stream."""
|
"""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)
|
name = unquote(name)
|
||||||
filename = "%s.tar.gz" % name
|
filename = "%s.tar.gz" % name
|
||||||
args = ['export-tar', '--name', name]
|
args = ['export-tar', '--name', name]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user