MCP-3 of milestone 461 "MCP server: Steward as an LLM-inspectable, LLM-operable hub". The /mcp tools run outside Quart's request pipeline, so the queries they share with the pages now live in plain functions that take an open session: - steward/hosts/service.py: uptime, status, monitors, agent metrics, agent overview (stale threshold passed in, not read from current_app), CPU sparks. - plugins/docker/service.py: bucket, container_history, host_map, list_containers, container_events, query_logs. - steward/alerts/service.py: active_alerts (also used by core/reports), list_rules, list_maintenance_windows. - steward/monitors/service.py: monitor_rows (was routes._rows_data). Routes are repointed and render the same data. One behavior fix rides along: the container-log search now escapes % and _ so they match literally instead of acting as LIKE wildcards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
53 lines
2.1 KiB
Python
53 lines
2.1 KiB
Python
"""Monitor queries shared by the monitors pages and the MCP tools.
|
|
|
|
Each function takes an open session, with no Quart request context, so the
|
|
routes and the /mcp tools (which run outside Quart's pipeline) call the same
|
|
code.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import select
|
|
|
|
from steward.core.status import _heartbeat_title, _last_n_results, _uptime_by_monitor
|
|
from steward.core.time_range import parse_range
|
|
from steward.models.monitors import Monitor
|
|
|
|
|
|
async def monitor_rows(db, range_key: str):
|
|
"""Per-monitor state: latest result, heartbeat strip, uptime and TLS days.
|
|
|
|
Returns (rows, up, down, pending, range_key). `range_key` picks the uptime
|
|
window surfaced (24h/7d/30d; anything else falls back to 24h) and comes
|
|
back normalised.
|
|
"""
|
|
_since, range_key = parse_range(range_key)
|
|
monitors = list((await db.execute(select(Monitor).order_by(Monitor.name))).scalars())
|
|
ids = [m.id for m in monitors]
|
|
recent = await _last_n_results(db, ids)
|
|
uptime = await _uptime_by_monitor(db, ids)
|
|
|
|
# Map the selected range to the uptime bucket we surface in the list.
|
|
bucket = {"24h": "24h", "7d": "7d", "30d": "30d"}.get(range_key, "24h")
|
|
now = datetime.now(timezone.utc)
|
|
data = []
|
|
for m in monitors:
|
|
rows = recent.get(m.id, [])
|
|
latest = rows[-1] if rows else None
|
|
tls_days = None
|
|
if latest and latest.tls_expires_at:
|
|
tls_days = (latest.tls_expires_at - now).total_seconds() / 86400.0
|
|
data.append({
|
|
"monitor": m,
|
|
"latest": latest,
|
|
"heartbeat": [{"state": "up" if r.is_up else "down",
|
|
"title": _heartbeat_title(m.type, r)} for r in rows],
|
|
"uptime_pct": uptime.get(m.id, {}).get(bucket),
|
|
"tls_days": tls_days,
|
|
})
|
|
up = sum(1 for d in data if d["latest"] and d["latest"].is_up)
|
|
down = sum(1 for d in data if d["latest"] and not d["latest"].is_up)
|
|
pending = sum(1 for d in data if not d["latest"])
|
|
return data, up, down, pending, range_key
|