(feature) add Django setup flow for initial pobsync configuration

Add staff-only UI routes for creating/editing the default GlobalConfig
and creating the first HostConfig from the dashboard.

Improve the empty dashboard state so a fresh database guides the user
towards the next useful setup action instead of only showing empty tables.

Cover the setup flow with view tests for empty state prompts, global
config creation, and host creation.
This commit is contained in:
2026-05-19 12:25:45 +02:00
parent 4dbde43465
commit 6bcc15c174
8 changed files with 290 additions and 8 deletions

View File

@@ -8,8 +8,8 @@ from django.views.decorators.http import require_POST
from pobsync.errors import ConfigError
from .forms import HostConfigForm, ScheduleConfigForm
from .models import BackupRun, HostConfig, ScheduleConfig, SnapshotRecord
from .forms import CreateHostConfigForm, GlobalConfigForm, HostConfigForm, ScheduleConfigForm
from .models import BackupRun, GlobalConfig, HostConfig, ScheduleConfig, SnapshotRecord
from .retention import run_sql_retention_plan
from .snapshot_discovery import discover_snapshots
@@ -22,8 +22,10 @@ def dashboard(request):
)
context = {
"hosts": host_qs,
"global_config": GlobalConfig.objects.filter(name="default").first(),
"latest_runs": BackupRun.objects.select_related("host", "snapshot").order_by("-created_at")[:10],
"counts": {
"global_configs": GlobalConfig.objects.count(),
"hosts": HostConfig.objects.count(),
"enabled_hosts": HostConfig.objects.filter(enabled=True).count(),
"schedules": ScheduleConfig.objects.count(),
@@ -37,6 +39,49 @@ def dashboard(request):
return render(request, "pobsync_backend/dashboard.html", context)
@staff_member_required
def edit_global_config(request):
global_config = GlobalConfig.objects.filter(name="default").first()
if request.method == "POST":
form = GlobalConfigForm(request.POST, instance=global_config)
if form.is_valid():
saved_config = form.save()
messages.success(request, f"Global config saved for {saved_config.name}.")
return redirect("dashboard")
else:
form = GlobalConfigForm(instance=global_config, initial=_default_global_initial())
return render(
request,
"pobsync_backend/global_form.html",
{
"global_config": global_config,
"form": form,
},
)
@staff_member_required
def create_host_config(request):
if request.method == "POST":
form = CreateHostConfigForm(request.POST)
if form.is_valid():
host_config = form.save()
messages.success(request, f"Host config created for {host_config.host}.")
return redirect("host_detail", host=host_config.host)
else:
form = CreateHostConfigForm(initial=_default_host_initial())
return render(
request,
"pobsync_backend/host_form.html",
{
"host": None,
"form": form,
},
)
@staff_member_required
def host_detail(request, host: str):
host_config = get_object_or_404(HostConfig, host=host)
@@ -158,3 +203,29 @@ def _default_schedule_initial() -> dict[str, object]:
"enabled": True,
"prune_max_delete": 10,
}
def _default_global_initial() -> dict[str, object]:
return {
"name": "default",
"backup_root": "/opt/pobsync/backups",
"pobsync_home": "/opt/pobsync",
"ssh_user": "root",
"ssh_port": 22,
"rsync_binary": "rsync",
"default_source_root": "/",
"retention_daily": 14,
"retention_weekly": 8,
"retention_monthly": 12,
"retention_yearly": 0,
}
def _default_host_initial() -> dict[str, object]:
return {
"enabled": True,
"retention_daily": 14,
"retention_weekly": 8,
"retention_monthly": 12,
"retention_yearly": 0,
}