feat(ui): audit, alert rules and the rule form say things in words (#5212)
#5212 "Plain labels: no raw ids, scopes, metric keys or unexplained jargon", batch 1: - Audit log: each action reads as a sentence ("paused monitor"), with the code as its tooltip. Detail shows flags as yes/no, lists joined, a run's scope by name, spans as spans, and no ids the entity column already gives. The filter is now a picker of areas. - Alert rules, history and Status say the condition in words: "is down", "Fullest disk >= 85%". Readings use metric_value, and history times use |ago. - Rule form: sources and metrics by name, the threshold's unit, "fires when it is down/up" for an up/down metric instead of 0.5, and a metric reference built from the catalog. - Schedules and Maintenance: viewers no longer see Run now, Pause, Edit, Delete or New. Intervals and next runs read as "every 2h" and "in 3h"; a scope reads "Every alert on Host agent". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -184,3 +184,85 @@ def metric_value(name: str, value: Any) -> str:
|
||||
if n.endswith("_days"):
|
||||
return f"{value:.0f} days"
|
||||
return f"{value:g}"
|
||||
|
||||
|
||||
# What each alertable metric is called on a page, and what it measures. The
|
||||
# key is the name a source records; a name not listed reads as itself with
|
||||
# its underscores spaced (SNMP's OID labels are the operator's own words).
|
||||
METRICS: dict[str, tuple[str, str]] = {
|
||||
"is_up": ("Up or down", "whether the check passed"),
|
||||
"is_healthy": ("Health check", "the container's own HEALTHCHECK result"),
|
||||
"response_ms": ("Response time", "how long the check took"),
|
||||
"open_fds": ("Open files", "files Steward itself has open"),
|
||||
"open_fds_pct": ("Open files (% of limit)", "files Steward has open, against its limit"),
|
||||
"request_rate": ("Requests per second", "requests through this router"),
|
||||
"error_rate_4xx_pct": ("4xx responses", "share of responses that were client errors"),
|
||||
"error_rate_5xx_pct": ("5xx responses", "share of responses that were server errors"),
|
||||
"latency_p50_ms": ("Response time (median)", "half of requests were faster than this"),
|
||||
"latency_p95_ms": ("Response time (95th percentile)", "95% of requests were faster than this"),
|
||||
"latency_p99_ms": ("Response time (99th percentile)", "99% of requests were faster than this"),
|
||||
"response_bytes_rate": ("Response throughput", "bytes per second sent back"),
|
||||
"cert_expiry_days": ("Certificate expires in", "days until the TLS certificate expires"),
|
||||
"latency_ms": ("WAN latency", "round trip from the gateway"),
|
||||
"total_clients": ("Connected clients", "devices on the network"),
|
||||
"cpu_pct": ("CPU", "CPU in use; for a container, 100% is one core"),
|
||||
"mem_pct": ("Memory (% of limit)", "memory in use, against the container's limit or the host's RAM"),
|
||||
"restart_count": ("Restarts", "times the container has restarted, in total"),
|
||||
"mem_used_pct": ("Memory used", "memory in use"),
|
||||
"mem_available_bytes": ("Memory available", "memory free for new work"),
|
||||
"swap_used_bytes": ("Swap used", "memory paged out to disk"),
|
||||
"disk_used_pct_worst": ("Fullest disk", "the most-full mount on the host"),
|
||||
"disk_used_pct": ("Disk used", "one mount (resource host:mount)"),
|
||||
"load_1m": ("Load (1 min)", "runnable processes, averaged over a minute"),
|
||||
"load_5m": ("Load (5 min)", "runnable processes, averaged over 5 minutes"),
|
||||
"load_15m": ("Load (15 min)", "runnable processes, averaged over 15 minutes"),
|
||||
"uptime_secs": ("Uptime", "time since the host booted"),
|
||||
"seconds_since_seen": ("Time since the agent reported", "over 3 minutes means the agent is offline"),
|
||||
}
|
||||
|
||||
# Where a rule's metric comes from, as the picker and the rules list say it.
|
||||
SOURCES: dict[str, str] = {
|
||||
"icmp": "Ping monitors", "tcp": "TCP monitors", "dns": "DNS monitors",
|
||||
"http": "HTTP monitors", "steward": "Steward itself", "host_agent": "Host agent",
|
||||
"docker": "Docker containers", "traefik": "Traefik routers", "unifi": "UniFi",
|
||||
"snmp": "SNMP devices",
|
||||
}
|
||||
|
||||
|
||||
def metric_label(name: str) -> str:
|
||||
entry = METRICS.get(name or "")
|
||||
return entry[0] if entry else (name or "").replace("_", " ")
|
||||
|
||||
|
||||
def metric_about(name: str) -> str:
|
||||
entry = METRICS.get(name or "")
|
||||
return entry[1] if entry else ""
|
||||
|
||||
|
||||
def source_label(source: str) -> str:
|
||||
return SOURCES.get(source or "", (source or "").replace("_", " "))
|
||||
|
||||
|
||||
def metric_unit(name: str) -> str:
|
||||
"""The unit a threshold for this metric is written in, from the same
|
||||
suffixes metric_value reads; "" for a count or a plain number."""
|
||||
n = name or ""
|
||||
if n in ("is_up", "is_healthy"):
|
||||
return ""
|
||||
if n.endswith("_pct") or "_pct_" in n:
|
||||
return "%"
|
||||
if n.endswith("_ms"):
|
||||
return "ms"
|
||||
if n.endswith("_bytes"):
|
||||
return "bytes"
|
||||
if n.endswith(("_bps", "bytes_rate")):
|
||||
return "bytes/s"
|
||||
if n.endswith("_secs") or n.startswith("seconds_"):
|
||||
return "seconds"
|
||||
if n.endswith("_c"):
|
||||
return "°C"
|
||||
if n.endswith("_days"):
|
||||
return "days"
|
||||
if n == "request_rate":
|
||||
return "req/s"
|
||||
return ""
|
||||
|
||||
Reference in New Issue
Block a user