(feature) Show next scheduled run and backup run type in the UI
Add a scheduler helper that calculates the next due time for a cron-style schedule expression and surface that value on the dashboard and host detail pages. Show the latest run type in host summaries and backup trend tables so manual and scheduled backups are distinguishable in the Django UI. Keep the calculation derived from existing ScheduleConfig data without adding a migration.
This commit is contained in:
@@ -9,7 +9,7 @@ from django.test import SimpleTestCase, TestCase
|
||||
|
||||
from pobsync_backend.management.commands.run_pobsync_scheduler import Command
|
||||
from pobsync_backend.models import HostConfig, ScheduleConfig
|
||||
from pobsync_backend.scheduler import due_key, is_due
|
||||
from pobsync_backend.scheduler import due_key, is_due, next_due_after
|
||||
|
||||
|
||||
class SchedulerTests(SimpleTestCase):
|
||||
@@ -36,6 +36,12 @@ class SchedulerTests(SimpleTestCase):
|
||||
|
||||
self.assertEqual(due_key(moment), "202605190215")
|
||||
|
||||
def test_next_due_after_returns_next_matching_minute(self) -> None:
|
||||
moment = datetime(2026, 5, 19, 2, 15, 45, tzinfo=ZoneInfo("UTC"))
|
||||
|
||||
self.assertEqual(next_due_after("30 2 * * *", moment), datetime(2026, 5, 19, 2, 30, tzinfo=ZoneInfo("UTC")))
|
||||
self.assertEqual(next_due_after("15 2 * * *", moment), datetime(2026, 5, 20, 2, 15, tzinfo=ZoneInfo("UTC")))
|
||||
|
||||
|
||||
class SchedulerCommandTests(TestCase):
|
||||
def test_run_due_executes_schedule_once_per_minute(self) -> None:
|
||||
|
||||
@@ -37,6 +37,7 @@ class ViewTests(TestCase):
|
||||
snapshot = self._snapshot(host, "20260519-021500Z__ABCDEFGH")
|
||||
run = BackupRun.objects.create(
|
||||
host=host,
|
||||
run_type=BackupRun.RunType.MANUAL,
|
||||
status=BackupRun.Status.SUCCESS,
|
||||
snapshot=snapshot,
|
||||
started_at=datetime(2026, 5, 19, 2, 15, tzinfo=timezone.utc),
|
||||
@@ -57,6 +58,7 @@ class ViewTests(TestCase):
|
||||
snapshot = self._snapshot(host, "20260519-021500Z__ABCDEFGH")
|
||||
run = BackupRun.objects.create(
|
||||
host=host,
|
||||
run_type=BackupRun.RunType.MANUAL,
|
||||
status=BackupRun.Status.SUCCESS,
|
||||
snapshot=snapshot,
|
||||
started_at=datetime(2026, 5, 19, 2, 15, tzinfo=timezone.utc),
|
||||
@@ -79,6 +81,7 @@ class ViewTests(TestCase):
|
||||
},
|
||||
},
|
||||
)
|
||||
ScheduleConfig.objects.create(host=host, cron_expr="* * * * *", enabled=True)
|
||||
|
||||
response = self.client.get(reverse("dashboard"))
|
||||
|
||||
@@ -87,8 +90,10 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Runs Until Full")
|
||||
self.assertContains(response, "Avg Daily New")
|
||||
self.assertContains(response, "Days Until Full")
|
||||
self.assertContains(response, "Next Run")
|
||||
self.assertContains(response, "10")
|
||||
self.assertContains(response, f"Run {run.id}")
|
||||
self.assertContains(response, "manual")
|
||||
self.assertContains(response, "1000")
|
||||
|
||||
def test_dashboard_links_latest_snapshot_for_each_host(self) -> None:
|
||||
@@ -548,6 +553,7 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "15 2 * * *")
|
||||
self.assertContains(response, "Schedule expression")
|
||||
self.assertContains(response, "Evaluated by the pobsync scheduler service.")
|
||||
self.assertContains(response, "Next run:")
|
||||
self.assertContains(response, "20260519-021500Z__ABCDEFGH")
|
||||
self.assertContains(response, "Discover snapshots")
|
||||
self.assertContains(response, "Edit schedule")
|
||||
@@ -570,6 +576,7 @@ class ViewTests(TestCase):
|
||||
snapshot = self._snapshot(host, "20260519-021500Z__ABCDEFGH")
|
||||
BackupRun.objects.create(
|
||||
host=host,
|
||||
run_type=BackupRun.RunType.MANUAL,
|
||||
status=BackupRun.Status.SUCCESS,
|
||||
snapshot=snapshot,
|
||||
started_at=datetime(2026, 5, 19, 2, 15, tzinfo=timezone.utc),
|
||||
@@ -611,6 +618,7 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Backup Trends")
|
||||
self.assertContains(response, "Avg New Data")
|
||||
self.assertContains(response, "Avg Daily New")
|
||||
self.assertContains(response, "manual")
|
||||
self.assertContains(response, "45s")
|
||||
self.assertContains(response, "250")
|
||||
self.assertContains(response, "2.0")
|
||||
|
||||
Reference in New Issue
Block a user