feat(web): dashboard redesign — Gitea palette, health panels, VM/audit enrichment (stages A–C)
Stage A: swap CSS tokens to the real Gitea dark palette (accent blue #4183c4); add Jinja formatting filters (humanbytes/duration/relative_time/localtime/shorten_path/thousands), a health status-header, and an inline SVG icon sprite; fix two UI bugs — unthemed action pages (htmx swap into #action-slot with a full-page HX-Request fallback) and polling wiping form input (hx-preserve). Stage B: four read-only panels that degrade to notices (never 500) — host resources (/proc, two-sample CPU%), VMware module health, template inventory (.vmem warn + last backup), and a path-contained file browser; backup card becomes a validated template dropdown; expensive dir-size walk is TTL-cached. Adds operations/inventory.py plus host_resources()/vmware_module_health() in operations/host.py. Stage C: parse_clone_name() adds a Job column (job_id + started) to the VM table; nav gains a Gitea link-out derived from the OIDC issuer origin (no fabricated run URLs); audit page gets action icons, relative time, and a client-side filter. Gates: 989 tests pass, coverage 94.12%, ruff + mypy --strict clean. Stage D (service management) intentionally deferred.
This commit is contained in:
@@ -759,7 +759,11 @@ def test_host_backup_missing_template_field_is_400(tmp_path: Path) -> None:
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
def test_host_backup_preview_missing_template_dir_is_error_partial(tmp_path: Path) -> None:
|
||||
def test_host_backup_unknown_template_name_is_400_not_audited(tmp_path: Path) -> None:
|
||||
"""B6: a template name absent from ``list_templates()`` is rejected before
|
||||
``backup_template`` is ever called -- a pure validation refusal, so (like
|
||||
the VM-remove path-containment check) it is never audited.
|
||||
"""
|
||||
config = _make_config(tmp_path)
|
||||
app = create_app(config)
|
||||
client = TestClient(app)
|
||||
@@ -767,10 +771,8 @@ def test_host_backup_preview_missing_template_dir_is_error_partial(tmp_path: Pat
|
||||
|
||||
resp = client.post("/host/backup", data={"csrf": csrf, "template": "NoSuchTemplate"})
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert "error" in resp.text.lower()
|
||||
entry = _last_audit_entry(config.web.audit_log)
|
||||
assert entry.result == "error"
|
||||
assert resp.status_code == 400
|
||||
assert not config.web.audit_log.exists()
|
||||
|
||||
|
||||
def test_host_backup_preview_then_execute_full_cycle(
|
||||
@@ -812,6 +814,9 @@ def test_host_backup_execute_exception_never_500s(
|
||||
raise RuntimeError("disk exploded")
|
||||
|
||||
monkeypatch.setattr(routes_module, "backup_template", _boom)
|
||||
# Must be a name list_templates() actually discovers, or the B6
|
||||
# known-template check 400s before backup_template is ever reached.
|
||||
(tmp_path / "templates" / "X").mkdir(parents=True)
|
||||
config = _make_config(tmp_path)
|
||||
app = create_app(config)
|
||||
client = TestClient(app)
|
||||
|
||||
Reference in New Issue
Block a user