A metric that only ever reads 0 or 1 (up == 0, anything with bool) gave the
line chart nothing to show: a flat line or a square wave on a y axis padded
to -1..2. When every drawn series is 0/1 the incident page now draws one row
per series instead, filled where the value was 1, a thin mark where it was
0, and the bare track where Prometheus returned no samples, which for these
expressions means the condition did not hold.
The decision is per chart, so one chart never mixes styles; any other value
keeps the line chart as before. Frontend only: the endpoint already returns
the points and step_seconds, which pick() now passes on so the last sample
gets a width and a gap in the samples shows as a gap.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
Each alert carries a generatorURL pointing at the Prometheus rule that fired
it. The server now parses that link, queries the same Prometheus for the
rule's expression and draws it under the alert, with the current value
beside it. The browser never talks to Prometheus, so the CSP stays at
connect-src 'self'.
The URL comes out of a webhook payload, so whoever holds an integration key
chooses it. Querying it is therefore opt-in and bounded by an allowlist,
TERDUT_PROMETHEUS_ALLOWED_HOSTS (names, "*" as one label, a leading "**" as
several, IPs, CIDRs). The check runs on the address the dialer is about to
use, a name that resolves to loopback or link-local is refused even when it
matches, and redirects and proxy variables are ignored.
A worker takes a snapshot when an incident opens and another when it
closes, so the graph outlives Prometheus' retention. It finds its work in
the database rather than being triggered from the webhook, so ingestion is
untouched and a restart loses nothing. While an incident is open the
metrics endpoint also queries live, cached for 15 seconds.
The rule expression keeps its threshold, so Prometheus returns data only
while the alert condition holds; "no data" for the current value means the
condition cleared. Stripping the threshold needs a PromQL parser and is left
out.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN