"""The Prometheus dashboard widget (#5691): one target drawn by one template, in widget shape, and the dashboard pieces it needs (settings read when the picker opens, a Details link naming the target).""" import asyncio from datetime import timedelta from plugins.prometheus import library from plugins.prometheus.routes import spark_heights from plugins.prometheus.template_format import check from plugins.prometheus.template_view import render from steward.dashboard.routes import detail_url, with_options from tests._templates import render_plugin_template from tests.plugins.prometheus.test_templates import NOW, SERIES, TEMPLATE, T, latest, states from tests.test_style_tokens import hex_colours def test_every_bundled_template_is_a_template(): entries = library.bundled() assert [e.key for e in entries] == ["bundled:fabled_archives"] for e in entries: assert e.template is not None, [str(p) for p in e.problems] assert entries[0].name == "Fabled Archives" and entries[0].template.expects == "fabledarchives_" def test_settings_read_when_the_picker_opens(): async def targets(db): return [{"value": "t1", "label": "Archives"}] async def none(db): return [] widget = {"key": "w", "params": [ {"key": "target", "type": "select", "default": "", "options_from": targets}, {"key": "template", "type": "select", "default": "", "options_from": none, "empty": "No templates yet."}, {"key": "hours", "type": "select", "default": 6, "options": [{"value": 6, "label": "6h"}]}]} filled = asyncio.run(with_options(None, widget)) assert filled["params"][0]["options"] == [{"value": "t1", "label": "Archives"}] assert filled["params"][1]["options"] == [] assert filled["params"][2] is widget["params"][2] assert "options" not in widget["params"][0] # the registry isn't changed plain = {"key": "x", "params": []} assert asyncio.run(with_options(None, plain)) is plain def test_details_names_the_target_or_falls_back_to_the_section(): defn = {"detail_url": "/plugins/prometheus/targets/{target}"} assert detail_url(defn, {"target": "a b/c", "template": "x"}) == "/plugins/prometheus/targets/a%20b%2Fc" assert detail_url(defn, {}) == "/plugins/prometheus/" assert detail_url({"detail_url": "/monitors/"}, {"limit": 5}) == "/monitors/" def test_bars_scale_to_their_own_peak(): assert spark_heights([1.0, 2.0, 4.0]) == [25.0, 50.0, 100.0] assert spark_heights([0.0, 0.0]) == [0.0, 0.0] and spark_heights([]) == [] def _target(reach="up", ok_minutes=1): at = NOW - timedelta(minutes=ok_minutes) return {"id": "t1", "name": T, "reach": reach, "says": "", "ok_at": at, "scraped_at": NOW} async def _page(app, user=True, **ctx): from quart import session async with app.test_request_context("/plugins/prometheus/widget"): if user: session["user_id"] = "u1" session["user_role"] = "viewer" ctx.setdefault("spark_hours", 24) page = await render_plugin_template(app, "prometheus", "prometheus/widget.html", **ctx) return " ".join(page.split()) async def test_the_widget_leads_with_the_verdict_then_problems_first(app): view = render(check(TEMPLATE), T, latest(SERIES), NOW, history={(T, "fa_disk_free_bytes"): [50.0, 100.0]}) page = await _page(app, problem="", view=view, target=_target()) assert page.index("3 need a look. Backup targets: Photos is late.") < page.index("Backup targets") assert 'class="w-verdict tone-warn"' in page # Photos (late) before Steward (fresh) before Old (disabled, quiet). targets = page[page.index("Backup targets"):page.index("Storage")] assert targets.index("Photos") < targets.index("Steward") < targets.index("Old") assert 'is late' in page assert "Now" in page and 'Last backup' in page assert "16.2 TiB of 41.8 TiB" in page and 'class="meter-fill meter-ok" style="width:38.7%"' in page assert 'filestrash" in page and "437.7 GiB" in page assert "version 2026.10.12.1717 ยท commit 3605d0a" in page assert 'Archives' in page assert not hex_colours(page), hex_colours(page) async def test_the_healthy_rest_folds_after_six(app): many = {**SERIES} for i in range(9): many.update(states(f"target=T{i},target_id=x{i}", "fresh", ["fresh", "late"])) view = render(check(TEMPLATE), T, latest(many), NOW) page = await _page(app, problem="", view=view, target=_target()) # 1 late, 10 fresh, 1 quiet in Backup targets: the late one, six of the eleven others. assert "+5 more healthy" in page async def test_a_target_not_answering_says_so_first(app): view = render(check(TEMPLATE), T, latest(SERIES), NOW) page = await _page(app, problem="", view=view, target=_target("down", ok_minutes=90)) assert '
Archives isn\'t answering; these values are from' in page assert page.index("isn't answering") < page.index("3 need a look") async def test_a_widget_that_cant_draw_says_why(app): page = await _page(app, problem="The target this widget showed has been deleted.", view=None, target=None) assert '
The target this widget showed has been deleted.
' in page async def test_a_shared_dashboard_gets_no_links(app): view = render(check(TEMPLATE), T, latest(SERIES), NOW) page = await _page(app, user=False, problem="", view=view, target=_target()) assert "/plugins/prometheus/targets/t1" not in page and "Archives, scraped" in page def test_the_widget_names_itself_after_its_template(): assert asyncio.run(library.default_title(None, {"template": "bundled:fabled_archives"})) == "Fabled Archives" assert asyncio.run(library.default_title(None, {"template": "bundled:nope"})) is None