## Summary
- Add a dedicated `/hosts/` page with host cards and enabled/disabled filtering. - Link the dashboard Hosts metric and top navigation to the new page. - Add host enable/disable plus schedule and scheduled-retention pause/resume actions. ## Tests - `.venv/bin/python manage.py test src.pobsync_backend.tests.test_views.ViewTests.test_base_navigation_groups_primary_and_system_links src.pobsync_backend.tests.test_views.ViewTests.test_dashboard_renders_hosts_and_latest_runs src.pobsync_backend.tests.test_views.ViewTests.test_dashboard_hosts_live_returns_hosts_partial src.pobsync_backend.tests.test_views.ViewTests.test_hosts_list_renders_host_cards_and_controls src.pobsync_backend.tests.test_views.ViewTests.test_hosts_list_filters_by_enabled_state src.pobsync_backend.tests.test_views.ViewTests.test_update_host_state_toggles_host_schedule_and_retention --verbosity 2` - `.venv/bin/python manage.py check` - `.venv/bin/python manage.py test src.pobsync_backend --verbosity 2` Closes #48 Closes #49
This commit is contained in:
@@ -17,6 +17,7 @@ from django.db.models import Count, Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from pobsync import __version__
|
||||
@@ -62,8 +63,7 @@ def dashboard_hosts_live(request):
|
||||
return render(request, "pobsync_backend/partials/dashboard_hosts.html", _dashboard_context())
|
||||
|
||||
|
||||
def _dashboard_context() -> dict[str, object]:
|
||||
global_config = GlobalConfig.objects.filter(name="default").first()
|
||||
def _host_cards_context(*, enabled: str = "") -> dict[str, object]:
|
||||
hosts = list(
|
||||
HostConfig.objects.select_related("schedule")
|
||||
.annotate(
|
||||
@@ -84,6 +84,11 @@ def _dashboard_context() -> dict[str, object]:
|
||||
)
|
||||
.order_by("host")
|
||||
)
|
||||
if enabled == "yes":
|
||||
hosts = [host for host in hosts if host.enabled]
|
||||
elif enabled == "no":
|
||||
hosts = [host for host in hosts if not host.enabled]
|
||||
|
||||
for host_config in hosts:
|
||||
host_config.latest_snapshot = (
|
||||
host_config.snapshots.select_related("base")
|
||||
@@ -92,6 +97,22 @@ def _dashboard_context() -> dict[str, object]:
|
||||
)
|
||||
host_config.next_run_at = _next_run_for_host(host_config)
|
||||
host_config.retention_warning = _retention_warning_for_host(host_config, _schedule_for_host(host_config))
|
||||
return {
|
||||
"hosts": hosts,
|
||||
"scheduler_timezone": timezone.get_current_timezone_name(),
|
||||
"selected_enabled": enabled,
|
||||
"counts": {
|
||||
"hosts": len(hosts),
|
||||
"enabled_hosts": sum(1 for host in hosts if host.enabled),
|
||||
"disabled_hosts": sum(1 for host in hosts if not host.enabled),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _dashboard_context() -> dict[str, object]:
|
||||
global_config = GlobalConfig.objects.filter(name="default").first()
|
||||
host_context = _host_cards_context()
|
||||
hosts = host_context["hosts"]
|
||||
action_items = _dashboard_action_items(hosts)
|
||||
next_schedule_rows = _dashboard_next_schedule_rows()
|
||||
recent_runs = BackupRun.objects.select_related("host", "snapshot").order_by("-created_at", "-id")[:6]
|
||||
@@ -100,7 +121,7 @@ def _dashboard_context() -> dict[str, object]:
|
||||
"hosts": hosts,
|
||||
"global_config": global_config,
|
||||
"stats_summary": stats_summary,
|
||||
"scheduler_timezone": timezone.get_current_timezone_name(),
|
||||
"scheduler_timezone": host_context["scheduler_timezone"],
|
||||
"action_items": action_items,
|
||||
"next_schedule_rows": next_schedule_rows,
|
||||
"recent_runs": recent_runs,
|
||||
@@ -127,6 +148,69 @@ def _dashboard_context() -> dict[str, object]:
|
||||
return context
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def hosts_list(request):
|
||||
enabled = request.GET.get("enabled", "").strip()
|
||||
if enabled not in {"", "yes", "no"}:
|
||||
enabled = ""
|
||||
global_config = GlobalConfig.objects.filter(name="default").first()
|
||||
context = _host_cards_context(enabled=enabled)
|
||||
collect_dashboard_stats(hosts=context["hosts"], global_config=global_config)
|
||||
return render(
|
||||
request,
|
||||
"pobsync_backend/hosts_list.html",
|
||||
{
|
||||
**context,
|
||||
"global_config": global_config,
|
||||
"show_host_controls": True,
|
||||
"total_count": HostConfig.objects.count(),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@staff_member_required
|
||||
@require_POST
|
||||
def update_host_state(request, host: str):
|
||||
host_config = get_object_or_404(HostConfig, host=host)
|
||||
action = request.POST.get("action", "").strip()
|
||||
next_url = request.POST.get("next") or reverse("hosts_list")
|
||||
if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
|
||||
next_url = reverse("hosts_list")
|
||||
|
||||
if action == "enable_host":
|
||||
host_config.enabled = True
|
||||
host_config.save(update_fields=["enabled", "updated_at"])
|
||||
messages.success(request, f"Enabled host {host_config.host}.")
|
||||
elif action == "disable_host":
|
||||
host_config.enabled = False
|
||||
host_config.save(update_fields=["enabled", "updated_at"])
|
||||
messages.success(request, f"Disabled host {host_config.host}.")
|
||||
elif action in {"enable_schedule", "disable_schedule", "enable_prune", "disable_prune"}:
|
||||
try:
|
||||
schedule = host_config.schedule
|
||||
except ScheduleConfig.DoesNotExist:
|
||||
messages.warning(request, f"{host_config.host} does not have a schedule yet.")
|
||||
else:
|
||||
if action == "enable_schedule":
|
||||
schedule.enabled = True
|
||||
message = f"Enabled backup schedule for {host_config.host}."
|
||||
elif action == "disable_schedule":
|
||||
schedule.enabled = False
|
||||
message = f"Paused backup schedule for {host_config.host}."
|
||||
elif action == "enable_prune":
|
||||
schedule.prune = True
|
||||
message = f"Enabled scheduled retention for {host_config.host}."
|
||||
else:
|
||||
schedule.prune = False
|
||||
message = f"Paused scheduled retention for {host_config.host}."
|
||||
schedule.save(update_fields=["enabled", "prune", "updated_at"])
|
||||
messages.success(request, message)
|
||||
else:
|
||||
messages.error(request, "Unknown host state action.")
|
||||
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
def _dashboard_action_items(hosts: list[HostConfig]) -> list[dict[str, object]]:
|
||||
action_items: list[dict[str, object]] = []
|
||||
for host_config in hosts:
|
||||
|
||||
Reference in New Issue
Block a user