feat(system): log-health scanner + Add/Edit-Printer setup pre-flight

Adds a passive log-health check that complements the active Connection
  Diagnostic. Scans Bambuddy's recent app log against a curated allowlist
  catalog of known failure signatures (rejected access code, FTPS :990
  timeout, FTPS TLS failure, flapping MQTT, unreachable camera, SQLite
  "database is locked" contention), dedupes and classifies each finding
  as layer8/environment/bug, and deep-links to the troubleshooting wiki.
  Sample log lines are sanitized before they leave the process. Exposed
  via GET /system/health and surfaced on two surfaces sharing one
  SystemHealthPanel component: a System Health section on the System
  page, and inline in the bug reporter when the form opens.

  The Add-Printer and Edit-Printer dialogs gained a setup-time pre-flight:
  saving runs the connection diagnostic and, on a failed check, warns with
  a "save anyway" escape hatch instead of silently saving a printer that
  will immediately show offline.

  Log read/parse/sanitize primitives extracted from routes/support.py into
  a shared services/log_reader.py (behaviour-preserving); affected support
  tests repointed accordingly.

  Tests: test_log_health.py (11), test_system_api.py (2 new),
  SystemHealthPanel + BugReportBubble + AddPrinterPreflight +
  EditPrinterPreflight (8 frontend). All strings translated across the 9
  locales. Backend ruff clean, full unit suite green, frontend build +
  eslint clean, i18n parity green.
This commit is contained in:
maziggy
2026-05-22 16:31:16 +02:00
parent ab8e07618f
commit e222a0ef0e
31 changed files with 2325 additions and 690 deletions
@@ -308,3 +308,48 @@ class TestSystemHelperFunctions:
result = format_uptime(30) # 30 seconds
assert result == "< 1m"
class TestSystemHealthAPI:
"""Integration tests for GET /api/v1/system/health (log-health scan)."""
@pytest.mark.asyncio
@pytest.mark.integration
async def test_health_clean_log(self, async_client: AsyncClient, tmp_path, monkeypatch):
"""A log with no known issues returns an empty, healthy result."""
from backend.app.core.config import settings
(tmp_path / "bambuddy.log").write_text(
"2026-05-22 10:00:00,000 INFO [backend.app.main] Application startup complete\n",
encoding="utf-8",
)
monkeypatch.setattr(settings, "log_dir", tmp_path)
response = await async_client.get("/api/v1/system/health")
assert response.status_code == 200
result = response.json()
assert result["log_available"] is True
assert result["findings"] == []
assert result["summary"]["total"] == 0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_health_detects_known_issue(self, async_client: AsyncClient, tmp_path, monkeypatch):
"""A known signature in the log surfaces as a finding."""
from backend.app.core.config import settings
(tmp_path / "bambuddy.log").write_text(
"2026-05-22 10:00:00,000 WARNING [backend.app.services.bambu_ftp] "
"FTP connection permission error to 10.0.0.9: 530\n",
encoding="utf-8",
)
monkeypatch.setattr(settings, "log_dir", tmp_path)
response = await async_client.get("/api/v1/system/health")
assert response.status_code == 200
result = response.json()
ids = [f["signature_id"] for f in result["findings"]]
assert "ftp-auth-rejected" in ids
assert result["summary"]["layer8"] >= 1