refactor: move host, docker, alert and monitor queries into service modules
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 35s
CI / unit (push) Successful in 59s
CI / publish (push) Successful in 56s

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>
This commit is contained in:
2026-10-06 07:29:21 -04:00
co-authored by Claude Opus 5.5
parent 970adf41e7
commit 312ea95bcf
10 changed files with 486 additions and 397 deletions
+4 -10
View File
@@ -11,7 +11,8 @@ from email.message import EmailMessage
from sqlalchemy import and_, case, func, select
from steward.models.alerts import AlertRule, AlertState, AlertStateEnum, AlertEvent
from steward.alerts import service as alert_svc
from steward.models.alerts import AlertEvent
from steward.models.metrics import PluginMetric
from steward.models.monitors import Monitor, MonitorResult
@@ -51,14 +52,7 @@ async def build_report_data(app) -> dict:
]
# ── Active alerts ─────────────────────────────────────────────────────
active_result = await db.execute(
select(AlertState, AlertRule)
.join(AlertRule, AlertState.rule_id == AlertRule.id)
.where(AlertState.state.in_([
AlertStateEnum.firing, AlertStateEnum.acknowledged, AlertStateEnum.pending,
]))
.order_by(AlertState.last_transition_at.desc())
)
active_rows = await alert_svc.active_alerts(db)
active_alerts = [
{
"state": state.state.value.upper(),
@@ -68,7 +62,7 @@ async def build_report_data(app) -> dict:
"operator": rule.operator.value,
"threshold": rule.threshold,
}
for state, rule in active_result.all()
for state, rule in active_rows
]
# ── Alert event counts (7d) ───────────────────────────────────────────