feat(syslog): lines beside the readings: around an anomaly, in get_anomaly, and marked on host and SNMP charts (#6002)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m29s
CI / integration (push) Successful in 1m37s
CI / publish (push) Successful in 1m9s

get_anomaly's circumstances gain the host's or SNMP device's syslog
warnings and worse (mcp_events / mcp_resource_events). An anomaly row opens
"Syslog around it", read only when opened. A shared Chart.js plugin
(chart-marks.js) draws a dashed line at each syslog error on the host
metrics charts and an SNMP device's charts, with a line saying how many.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 06:36:31 -04:00
co-authored by Claude Opus 5.5
parent f37a578136
commit 521d84f318
12 changed files with 335 additions and 23 deletions
+39
View File
@@ -364,3 +364,42 @@ def test_patterns_page_adds_edits_and_removes_and_a_rename_moves_counts(app):
await _clear_patterns(app)
asyncio.run(go())
def test_lines_beside_the_readings_around_an_anomaly_and_as_chart_marks(app):
now = datetime.now(timezone.utc)
mcp_tools = _mod("mcp_tools")
async def go():
host_id, host_name, _, src_name = await _seed(app, now)
client = await _client(app, "viewer")
since, until = (now - timedelta(minutes=35)).isoformat(), (now - timedelta(minutes=25)).isoformat()
around = await (await client.get("/plugins/syslog/around", query_string={
"source": "host_agent", "resource": f"{host_name}:/", "since": since, "until": until})).get_data(as_text=True)
# Warnings and worse within half an hour either side; info lines left out.
assert "nfsd: server not responding" in around and "100% of quota used" in around
assert "eth0 link up" not in around and "an old error" not in around
elsewhere = await (await client.get("/plugins/syslog/around", query_string={
"source": "traefik", "resource": "web@docker", "since": since})).get_data(as_text=True)
assert "No added syslog source" in elsewhere
assert (await client.get("/plugins/syslog/around?source=host_agent")).status_code == 400
day = await (await client.get(f"/plugins/syslog/marks?host_id={host_id}&range=24h")).get_json()
assert [m["text"] for m in day["marks"]] == [f"{src_name} err kernel: nfsd: server not responding"]
assert day["more"] is False
week = await (await client.get(f"/plugins/syslog/marks?host_id={host_id}&hours=168")).get_json()
assert len(week["marks"]) == 2
assert (await (await client.get("/plugins/syslog/marks?address=10.1.1.1")).get_json())["marks"] == []
# get_anomaly's circumstances: the host's lines, and nothing for a
# resource syslog can't place.
async with app.db_sessionmaker() as s:
events = await mcp_tools.mcp_events(s, host_id, host_name, now - timedelta(hours=1), now)
assert [(e["kind"], e["subject"]) for e in events] == [("syslog", src_name)] * 2
assert events[0]["what"].startswith("err kernel: nfsd")
assert await mcp_tools.mcp_resource_events(s, "traefik", "web@docker", now - timedelta(hours=1), now) == []
async with app.db_sessionmaker() as s:
async with s.begin():
await s.execute(delete_sources())
asyncio.run(go())