diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py index a3d2bb8..b16003b 100644 --- a/src/fgai/dashboard.py +++ b/src/fgai/dashboard.py @@ -251,6 +251,8 @@ function llmCapability(llm, configuration) { const status = String(llm.status || 'starting'); if (status === 'ok') return {state: 'on', detail: 'ok'}; if (status === 'cached') return {state: 'on', detail: 'cached'}; + if (status === 'cached_assessment_error') return {state: 'warn', detail: 'cached, assessment unavailable'}; + if (status === 'assessment_unavailable') return {state: 'warn', detail: 'assessment unavailable'}; if (status === 'error' && String(llm.text || '').trim()) return {state: 'warn', detail: 'cached, assessment error'}; if (status === 'error') return {state: 'warn', detail: 'assessment error'}; return {state: 'warn', detail: status}; @@ -619,6 +621,7 @@ async function refresh() { `Baseline DB size: ${esc(bytes((data.baseline || {}).size_bytes || 0))}`, `Discovery fields recorded: ${esc((data.baseline || {}).discovery_fields_recorded || 0)}`, `Discovery cache events: ${esc((data.baseline || {}).discovery_cache_events || 0)}`, + ['assessment_unavailable','cached_assessment_error'].includes(llm.status) ? `Ollama assessment: unavailable (${esc(llm.error || 'unknown error')})` : '', llm.status === 'error' ? `Ollama assessment error: ${esc(llm.error || 'unknown error')}` : '', `MCP status: ${esc(mcp.status || 'unknown')}`, mcp.status === 'refreshing' && pollStartedAt ? `MCP poll running: ${esc(pollRunningSeconds)}s` : '', @@ -641,8 +644,7 @@ async function refresh() { configuration.log_source === 'graylog_mcp' && mcp.status !== 'refreshing' && enabledStreams.length > 0 && displayedRawEvents === 0 ? `No raw events fetched from enabled streams. Check poll window, Graylog query, stream permissions, and Diagnostics -> Stream Coverage.` : '', enabledWithNoRawEvents.length && mcp.status !== 'refreshing' ? `${esc(enabledWithNoRawEvents.length)} enabled stream(s) returned zero raw events in ${esc(zeroRawWindow)}.` : '', mcp.coverage_warning ? `${esc(mcp.coverage_warning)}` : '', - advisor.status === 'heuristic_fallback' ? `Profile advisor: heuristic fallback (${esc(advisor.error || 'advisor unavailable')})` : '', - advisor.status === 'error' ? `Profile advisor error: ${esc(advisor.error || 'unknown error')}` : '' + ['heuristic_fallback','error'].includes(advisor.status) ? `Profile advisor: heuristic fallback (${esc(advisor.error || 'advisor unavailable')})` : '' ].filter(Boolean).join('
'); document.getElementById('health').innerHTML = [ metric('Enabled streams', enabledStreams.length), metric('Streams missing profile', streamsMissingProfile), metric('MCP aggregate events', displayedAggregateEvents), metric('MCP raw sample', displayedRawEvents), metric('Partial streams', mcp.partial_streams || 0), metric('Sample capped', mcp.sample_limited_streams || 0), metric('Truncated streams', mcp.truncated_streams || 0), metric('Correlated entities', correlations.length) diff --git a/src/fgai/monitor.py b/src/fgai/monitor.py index 420a9fe..318512b 100644 --- a/src/fgai/monitor.py +++ b/src/fgai/monitor.py @@ -598,7 +598,7 @@ def add_llm_assessment(status: dict[str, object], *, previous: str | None = None except Exception as exc: status["llm_assessment"] = { "enabled": True, - "status": "error", + "status": "cached_assessment_error" if previous else "assessment_unavailable", "generated_at": int(time.time()), "error": str(exc), "text": previous or "", diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 2070879..ab58869 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -73,8 +73,8 @@ class DashboardTests(unittest.TestCase): def test_dashboard_labels_ollama_assessment_errors(self): self.assertIn("function llmCapability", HTML) - self.assertIn("assessment error", HTML) - self.assertIn("Ollama assessment error", HTML) + self.assertIn("assessment unavailable", HTML) + self.assertIn("Ollama assessment: unavailable", HTML) if __name__ == "__main__": diff --git a/tests/test_monitor.py b/tests/test_monitor.py index 7543726..1ba091b 100644 --- a/tests/test_monitor.py +++ b/tests/test_monitor.py @@ -178,15 +178,24 @@ class MonitorTests(unittest.TestCase): self.assertEqual(status["capabilities"]["graylog_mcp"]["status"], "no_streams_enabled") self.assertEqual(status["summary"]["total"], 0) - def test_add_llm_assessment_records_error_without_ollama(self): + def test_add_llm_assessment_uses_cached_error_status_with_previous_text(self): status = {"summary": {}, "anomalies": []} with patch("fgai.monitor.ollama_dashboard_assessment", side_effect=TimeoutError("timeout")): add_llm_assessment(status, previous="old text") - self.assertEqual(status["llm_assessment"]["status"], "error") + self.assertEqual(status["llm_assessment"]["status"], "cached_assessment_error") self.assertEqual(status["llm_assessment"]["text"], "old text") + def test_add_llm_assessment_records_unavailable_without_previous_text(self): + status = {"summary": {}, "anomalies": []} + + with patch("fgai.monitor.ollama_dashboard_assessment", side_effect=TimeoutError("timeout")): + add_llm_assessment(status) + + self.assertEqual(status["llm_assessment"]["status"], "assessment_unavailable") + self.assertEqual(status["llm_assessment"]["text"], "") + def test_add_llm_assessment_records_text(self): status = {"summary": {}, "anomalies": []}