feat(docker): container log viewer + Settings controls [M79 step 5]
The user-facing half (rule 27). Per-container log viewer + admin controls, so container logs are something the operator can actually touch. Viewer (plugins/docker): - /container/<host>/<name>/logs full page + /logs/lines HTMX fragment polled every 5s (near-live follow); stream filter (all/stdout/stderr) + case-insensitive text search; newest-first so the latest lines survive the poll's scroll reset; empty/loading states; a "View logs" link from the container detail page. Jinja auto-escapes log content (no markup injection). Settings (Thresholds & Retention tab): - global on/off toggle, comma-separated exclude list, retention days + max MB per container — DB-backed, no restart. The toggle + exclude are enforced authoritatively at ingest (_persist_logs drops disabled/excluded lines), since the push model has no channel to tell an agent to stop. Tests: route-defined smoke; template-parse covers the new templates; integration test for the ingest-time toggle + exclude enforcement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CAGR73DUowdVFVvYzLXC5C
This commit is contained in:
@@ -133,6 +133,7 @@ _RETENTION_FIELDS = [
|
||||
("docker_metrics_raw_days", "docker.retention.metrics_raw_days"),
|
||||
("docker_metrics_rollup_days", "docker.retention.metrics_rollup_days"),
|
||||
("docker_events_days", "docker.retention.events_days"),
|
||||
("docker_logs_retention_days", "docker.logs.retention_days"),
|
||||
("metrics_raw_days", "metrics.retention.raw_days"),
|
||||
("metrics_rollup_days", "metrics.retention.rollup_days"),
|
||||
]
|
||||
@@ -170,6 +171,20 @@ async def save_thresholds():
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
await set_setting(db, key, val)
|
||||
# Container-log controls (m79). Checkbox: present ⇒ on (this is a
|
||||
# full-page form, so absence is a genuine "off"). Exclude: comma-split
|
||||
# names. Size: entered in MB, stored as bytes.
|
||||
await set_setting(db, "docker.logs.enabled", "docker_logs_enabled" in form)
|
||||
exclude = [n.strip() for n in form.get("docker_logs_exclude", "").split(",")
|
||||
if n.strip()]
|
||||
await set_setting(db, "docker.logs.exclude", exclude)
|
||||
max_mb = form.get("docker_logs_max_mb", "")
|
||||
if max_mb != "":
|
||||
try:
|
||||
await set_setting(db, "docker.logs.max_bytes_per_container",
|
||||
max(1, int(max_mb)) * 1_000_000)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
await _reload_app_config()
|
||||
await log_audit(current_app, session.get("user_id"), session.get("username", ""),
|
||||
"settings.saved", detail={"section": "thresholds"})
|
||||
|
||||
Reference in New Issue
Block a user