fix(prometheus, syslog): a target or pattern pauses with a Pause/Resume button, not an "on" tick box in its form (#6419)
CI / lint (push) Successful in 2s
CI / unit (push) Failing after 1m42s
CI / integration (push) Successful in 1m45s
CI / publish (push) Skipped

Monitors, schedules and maintenance plans already pause with a
Pause/Resume button and say "Paused" where they're listed. Prometheus
targets and syslog patterns only had a tick box inside the edit form,
so a target refused by its endpoint couldn't be stopped from its page.

- Both get a Pause/Resume button (target page and list; patterns list)
  posting to /toggle, recorded in the audit log, and a Paused badge.
- The forms lose the tick box. A new one starts running; an edit keeps
  whether it is paused.
- A target's "off" state is now "paused", in its pages and MCP tools.
  A resumed target reads "not scraped yet" until its next scrape, and its
  counter rates start over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 17:48:31 -04:00
co-authored by Claude Opus 5.5
parent 0b787a0676
commit d20d78498e
15 changed files with 104 additions and 51 deletions
+7 -7
View File
@@ -197,11 +197,10 @@ def form_values(target: PrometheusTarget | None) -> dict:
"""The form's fields for an existing target. The token is never sent back
to the browser; a blank token field on save keeps the stored one."""
if target is None:
return {"interval_seconds": 60, "series_cap": 1000, "verify_tls": True, "enabled": True,
"keep": "", "drop": ""}
return {"interval_seconds": 60, "series_cap": 1000, "verify_tls": True, "keep": "", "drop": ""}
return {"name": target.name, "url": target.url, "interval_seconds": target.interval_seconds,
"series_cap": target.series_cap, "verify_tls": target.verify_tls,
"enabled": target.enabled, "keep": "\n".join(target.keep or []),
"keep": "\n".join(target.keep or []),
"drop": "\n".join(target.drop or []), "has_token": bool(target.token)}
@@ -217,7 +216,8 @@ def _bounded(form, key: str, default: int, low: int, high: int, says: str) -> in
def parse_form(form, existing: PrometheusTarget | None = None) -> dict:
"""The submitted form as the columns to store, the token encrypted.
Raises FormError saying what to fix."""
Whether it is paused isn't among them: Pause/Resume sets that, and a new
target starts scraping. Raises FormError saying what to fix."""
name = (form.get("name") or "").strip()
url = (form.get("url") or "").strip()
if not name:
@@ -241,7 +241,7 @@ def parse_form(form, existing: PrometheusTarget | None = None) -> dict:
return {"name": name, "url": url, "token": token,
"verify_tls": bool(form.get("verify_tls")), "interval_seconds": interval,
"keep": patterns(form.get("keep") or ""), "drop": patterns(form.get("drop") or ""),
"series_cap": cap, "enabled": bool(form.get("enabled"))}
"series_cap": cap}
# ── What the newest scrape says ──────────────────────────────────────────────
@@ -253,13 +253,13 @@ STALE_SCRAPES = 3
@dataclass
class TargetState:
reach: str # up | down | stale | pending | off
reach: str # up | down | stale | pending | paused
says: str # one line, in words
def target_state(target: PrometheusTarget, now: datetime) -> TargetState:
if not target.enabled:
return TargetState("off", "Not scraped: turned off.")
return TargetState("paused", "Paused: not scraped until it's resumed.")
if target.last_scraped_at is None:
return TargetState("pending", "Not scraped yet.")
if (now - target.last_scraped_at).total_seconds() > STALE_SCRAPES * max(target.interval_seconds, 1):