mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-08-26 12:46:08 +00:00
Backups: first UI sceleton for remote / encrypted backups
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
9e4fb5eb59
commit
b6d1237c0c
@ -23,6 +23,9 @@ from django.core import validators
|
|||||||
from django.core.validators import FileExtensionValidator
|
from django.core.validators import FileExtensionValidator
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
|
from plinth.utils import format_lazy
|
||||||
|
from plinth import cfg
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
|
|
||||||
@ -77,3 +80,43 @@ class UploadForm(forms.Form):
|
|||||||
validators=[FileExtensionValidator(['gz'],
|
validators=[FileExtensionValidator(['gz'],
|
||||||
'Backup files have to be in .tar.gz format')],
|
'Backup files have to be in .tar.gz format')],
|
||||||
help_text=_('Select the backup file you want to upload'))
|
help_text=_('Select the backup file you want to upload'))
|
||||||
|
|
||||||
|
|
||||||
|
class CreateRepositoryForm(forms.Form):
|
||||||
|
repository = forms.CharField(
|
||||||
|
label=_('Repository path'), strip=True,
|
||||||
|
help_text=_('Path of the new repository.'))
|
||||||
|
encryption = forms.ChoiceField(
|
||||||
|
label=_('Encryption'),
|
||||||
|
help_text=format_lazy(_('"Key in Repository" means that a '
|
||||||
|
'password-protected key is stored with the backup. <br />'
|
||||||
|
'<b>You need this password to restore a backup!</b>')),
|
||||||
|
choices=[('repokey', 'Key in Repository'), ('none', 'None')]
|
||||||
|
)
|
||||||
|
passphrase = forms.CharField(
|
||||||
|
label=_('Passphrase'),
|
||||||
|
help_text=_('Passphrase; Only needed when using encryption.'),
|
||||||
|
widget=forms.PasswordInput()
|
||||||
|
)
|
||||||
|
confirm_passphrase = forms.CharField(
|
||||||
|
label=_('Confirm Passphrase'),
|
||||||
|
help_text=_('Repeat the passphrase.'),
|
||||||
|
widget=forms.PasswordInput()
|
||||||
|
)
|
||||||
|
store_passphrase = forms.BooleanField(
|
||||||
|
label=_('Store passphrase on FreedomBox'),
|
||||||
|
help_text=format_lazy(_('Store the passphrase on your {box_name}.'
|
||||||
|
'<br />You need to store the passphrase if you want to run '
|
||||||
|
'recurrent backups.'), box_name=_(cfg.box_name)),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(CreateRepositoryForm, self).clean()
|
||||||
|
passphrase = cleaned_data.get("passphrase")
|
||||||
|
confirm_passphrase = cleaned_data.get("confirm_passphrase")
|
||||||
|
|
||||||
|
if passphrase != confirm_passphrase:
|
||||||
|
raise forms.ValidationError(
|
||||||
|
"passphrase and confirm_passphrase do not match"
|
||||||
|
)
|
||||||
|
|||||||
39
plinth/modules/backups/templates/backups_repositories.html
Normal file
39
plinth/modules/backups/templates/backups_repositories.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{% 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 i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>{% trans 'Backup repositories' %}</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% for repository in repositories %}
|
||||||
|
Repository: {{ repository.repository }}<br />
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a title="{% trans 'Create new repository' %}"
|
||||||
|
role="button" class="btn btn-primary"
|
||||||
|
href="{% url 'backups:create-repository' %}">
|
||||||
|
{% trans 'Add Repository' %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -20,9 +20,9 @@ URLs for the backups module.
|
|||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from .views import IndexView, CreateArchiveView, DeleteArchiveView, \
|
from .views import IndexView, CreateArchiveView, CreateRepositoryView, \
|
||||||
UploadArchiveView, ExportAndDownloadView, RestoreArchiveView, \
|
DeleteArchiveView, UploadArchiveView, ExportAndDownloadView, \
|
||||||
RestoreFromUploadView
|
RepositoriesView, RestoreArchiveView, RestoreFromUploadView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
||||||
@ -36,4 +36,8 @@ urlpatterns = [
|
|||||||
RestoreArchiveView.as_view(), name='restore-archive'),
|
RestoreArchiveView.as_view(), name='restore-archive'),
|
||||||
url(r'^sys/backups/restore-from-upload/$',
|
url(r'^sys/backups/restore-from-upload/$',
|
||||||
RestoreFromUploadView.as_view(), name='restore-from-upload'),
|
RestoreFromUploadView.as_view(), name='restore-from-upload'),
|
||||||
|
url(r'^sys/backups/repositories/$',
|
||||||
|
RepositoriesView.as_view(), name='repositories'),
|
||||||
|
url(r'^sys/backups/repositories/create/$',
|
||||||
|
CreateRepositoryView.as_view(), name='create-repository'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -20,11 +20,13 @@ Views for the backups app.
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import gzip
|
import gzip
|
||||||
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from uuid import uuid1
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -37,7 +39,7 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from django.views.generic import View, FormView, TemplateView
|
from django.views.generic import View, FormView, TemplateView
|
||||||
|
|
||||||
from plinth import actions
|
from plinth import actions, kvstore
|
||||||
from plinth.errors import PlinthError
|
from plinth.errors import PlinthError
|
||||||
from plinth.modules import backups, storage
|
from plinth.modules import backups, storage
|
||||||
|
|
||||||
@ -51,10 +53,13 @@ subsubmenu = [{
|
|||||||
'text': ugettext_lazy('Backups')
|
'text': ugettext_lazy('Backups')
|
||||||
}, {
|
}, {
|
||||||
'url': reverse_lazy('backups:upload'),
|
'url': reverse_lazy('backups:upload'),
|
||||||
'text': ugettext_lazy('Upload backup')
|
'text': ugettext_lazy('Upload')
|
||||||
}, {
|
}, {
|
||||||
'url': reverse_lazy('backups:create'),
|
'url': reverse_lazy('backups:create'),
|
||||||
'text': ugettext_lazy('Create backup')
|
'text': ugettext_lazy('Create')
|
||||||
|
}, {
|
||||||
|
'url': reverse_lazy('backups:repositories'),
|
||||||
|
'text': ugettext_lazy('Repositories')
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
||||||
@ -284,3 +289,49 @@ class ExportAndDownloadView(View):
|
|||||||
response['Content-Disposition'] = 'attachment; filename="%s"' % \
|
response['Content-Disposition'] = 'attachment; filename="%s"' % \
|
||||||
filename
|
filename
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class RepositoriesView(TemplateView):
|
||||||
|
"""View list of repositories."""
|
||||||
|
template_name = 'backups_repositories.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = 'Backup repositories'
|
||||||
|
repositories = kvstore.get_default('backups_repositories', [])
|
||||||
|
context['repositories'] = json.loads(repositories)
|
||||||
|
context['subsubmenu'] = subsubmenu
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CreateRepositoryView(SuccessMessageMixin, FormView):
|
||||||
|
"""View to create a new repository."""
|
||||||
|
form_class = forms.CreateRepositoryForm
|
||||||
|
prefix = 'backups'
|
||||||
|
template_name = 'backups_form.html'
|
||||||
|
success_url = reverse_lazy('backups:repositories')
|
||||||
|
success_message = _('Created new repository.')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Return additional context for rendering the template."""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = _('Create new repository')
|
||||||
|
context['subsubmenu'] = subsubmenu
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Restore files from the archive on valid form submission."""
|
||||||
|
repositories = kvstore.get_default('backups_repositories', [])
|
||||||
|
if repositories:
|
||||||
|
repositories = json.loads(repositories)
|
||||||
|
new_repo = {
|
||||||
|
'uuid': str(uuid1()),
|
||||||
|
'repository': form.cleaned_data['repository'],
|
||||||
|
'encryption': form.cleaned_data['encryption'],
|
||||||
|
}
|
||||||
|
if form.cleaned_data['store_passphrase']:
|
||||||
|
new_repo['passphrase'] = form.cleaned_data['passphrase']
|
||||||
|
repositories.append(new_repo)
|
||||||
|
kvstore.set('backups_repositories', json.dumps(repositories))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user