snapshot: preserve backup UX, remote target setup, and docs updates

This commit is contained in:
Md Bayazid Bostame
2026-03-26 01:53:44 +01:00
parent 2a372fdb15
commit 438334bd92
26 changed files with 1737 additions and 383 deletions

View File

@@ -18,6 +18,7 @@ from django.utils import timezone
from django.utils.translation import gettext as _, gettext_lazy
from django.utils.translation import get_language, override
from .backup_ops import create_backup_bundle, delete_backup_bundle, list_backup_bundles, verify_backup_bundle
from .forms import OffboardingRequestForm, OnboardingRequestForm
from .form_builder import (
DEFAULT_FIELD_ORDER,
@@ -217,6 +218,10 @@ def _audit_action_label(action: str) -> str:
'mail_settings_saved': _('Mail-Einstellungen gespeichert'),
'email_routing_saved': _('E-Mail-Routing gespeichert'),
'notification_rules_saved': _('Benachrichtigungsregeln gespeichert'),
'backup_created': _('Backup erstellt'),
'backup_verified': _('Backup verifiziert'),
'backup_deleted': _('Backup gelöscht'),
'backup_settings_saved': _('Backup-Einstellungen gespeichert'),
}
return labels.get(action, action.replace('_', ' ').strip().capitalize())
@@ -375,6 +380,75 @@ def audit_log_page(request):
)
@login_required
@user_passes_test(_is_staff)
def backup_recovery_page(request):
return render(
request,
'workflows/backup_recovery.html',
{
'rows': list_backup_bundles(),
},
)
@login_required
@user_passes_test(_is_staff)
@require_POST
def create_backup_from_admin(request):
try:
result = create_backup_bundle()
_audit(
request,
'backup_created',
target_type='backup_bundle',
target_label=result['name'],
details={'path': result['path']},
)
messages.success(request, _('Backup wurde erstellt: %(name)s') % {'name': result['name']})
except Exception as exc:
messages.error(request, _('Backup konnte nicht erstellt werden: %(error)s') % {'error': exc})
return redirect('backup_recovery_page')
@login_required
@user_passes_test(_is_staff)
@require_POST
def verify_backup_from_admin(request, backup_name: str):
try:
result = verify_backup_bundle(backup_name)
_audit(
request,
'backup_verified',
target_type='backup_bundle',
target_label=backup_name,
details={'summary': result['summary']},
)
messages.success(request, _('Backup wurde verifiziert: %(name)s') % {'name': result['name']})
except Exception as exc:
messages.error(request, _('Backup-Verifikation fehlgeschlagen: %(error)s') % {'error': exc})
return redirect('backup_recovery_page')
@login_required
@user_passes_test(_is_staff)
@require_POST
def delete_backup_from_admin(request, backup_name: str):
try:
result = delete_backup_bundle(backup_name)
_audit(
request,
'backup_deleted',
target_type='backup_bundle',
target_label=backup_name,
details={},
)
messages.success(request, _('Backup wurde gelöscht: %(name)s') % {'name': result['name']})
except Exception as exc:
messages.error(request, _('Backup konnte nicht gelöscht werden: %(error)s') % {'error': exc})
return redirect('backup_recovery_page')
@login_required
@user_passes_test(_is_staff)
def request_timeline_page(request, kind: str, request_id: int):
@@ -1242,7 +1316,7 @@ def intro_builder_page(request):
def integrations_setup_page(request):
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
kind = (request.GET.get('kind') or 'nextcloud').strip().lower()
if kind not in {'nextcloud', 'mail', 'emails', 'rules'}:
if kind not in {'nextcloud', 'mail', 'emails', 'rules', 'backup'}:
kind = 'nextcloud'
templates = list(NotificationTemplate.objects.all().order_by('key'))
system_email_config = (
@@ -1263,6 +1337,7 @@ def integrations_setup_page(request):
'rule_event_choices': NotificationRule.EVENT_CHOICES,
'rule_operator_choices': NotificationRule.OPERATOR_CHOICES,
'template_choices': NotificationTemplate.TEMPLATE_CHOICES,
'remote_backup_target_choices': WorkflowConfig.REMOTE_BACKUP_TARGET_CHOICES,
},
)
@@ -1794,12 +1869,67 @@ def save_workflow_rules(request):
'workflow_rules_saved',
target_type='workflow_config',
target_label='workflow_rules',
details={'device_handover_lead_days': config.device_handover_lead_days},
details={
'device_handover_lead_days': config.device_handover_lead_days,
},
)
messages.success(request, 'Workflow-Regeln wurden gespeichert.')
return redirect('/admin-tools/integrations/?kind=rules')
@login_required
@user_passes_test(_is_staff)
@require_POST
def save_backup_settings(request):
config, _ = WorkflowConfig.objects.get_or_create(name='Default')
target_type = (request.POST.get('remote_backup_target_type') or config.remote_backup_target_type or 'nextcloud').strip().lower()
if target_type not in {choice for choice, _ in WorkflowConfig.REMOTE_BACKUP_TARGET_CHOICES}:
target_type = 'nextcloud'
remote_backup_enabled = request.POST.get('remote_backup_enabled') == 'on'
remote_backup_nextcloud_directory = request.POST.get('remote_backup_nextcloud_directory', '').strip()
primary_nextcloud_directory = (
(config.nextcloud_directory_override or '').strip()
or settings.NEXTCLOUD_DIRECTORY.strip()
).strip('/')
if remote_backup_enabled and target_type == 'nextcloud':
if not remote_backup_nextcloud_directory:
messages.error(request, 'Bitte ein separates Nextcloud Backup-Verzeichnis angeben.')
return redirect('/admin-tools/integrations/?kind=backup')
if remote_backup_nextcloud_directory.strip('/') == primary_nextcloud_directory:
messages.error(request, 'Das Backup-Verzeichnis muss vom normalen Nextcloud Dokumentenordner getrennt sein.')
return redirect('/admin-tools/integrations/?kind=backup')
config.remote_backup_enabled = remote_backup_enabled
config.remote_backup_target_type = target_type
config.remote_backup_nextcloud_directory = remote_backup_nextcloud_directory
config.remote_backup_s3_bucket = request.POST.get('remote_backup_s3_bucket', '').strip()
config.remote_backup_nfs_path = request.POST.get('remote_backup_nfs_path', '').strip()
config.save(
update_fields=[
'device_handover_lead_days',
'remote_backup_enabled',
'remote_backup_target_type',
'remote_backup_nextcloud_directory',
'remote_backup_s3_bucket',
'remote_backup_nfs_path',
]
)
_audit(
request,
'backup_settings_saved',
target_type='workflow_config',
target_label='backup_settings',
details={
'remote_backup_enabled': config.remote_backup_enabled,
'remote_backup_target_type': config.remote_backup_target_type,
'remote_backup_nextcloud_directory': config.remote_backup_nextcloud_directory,
},
)
messages.success(request, 'Backup-Einstellungen wurden gespeichert.')
return redirect('/admin-tools/integrations/?kind=backup')
@login_required
@user_passes_test(_is_staff)
@require_POST