From bddc84395cd85417b53408b34635f07d2cadce95c45b9b7817c458764e772409 Mon Sep 17 00:00:00 2001 From: larssand Date: Mon, 6 Jul 2026 09:36:00 +0200 Subject: [PATCH] fix config --- src/fgai/dashboard.py | 19 ++++++++++++++++--- tests/test_dashboard.py | 9 +++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/fgai/dashboard.py b/src/fgai/dashboard.py index 4f16068..ea67d43 100644 --- a/src/fgai/dashboard.py +++ b/src/fgai/dashboard.py @@ -215,6 +215,8 @@ const tableSort = {}; const uiCache = {correlations: [], fieldRows: [], correlationHitboxes: [], selectedCorrelationKey: '', entityLabels: {}}; window.availableStreams = []; window.streamSelection = {}; +window.configuredStreams = []; +window.streamProfiles = []; window.currentCorrelations = []; window.currentConfiguration = {}; function table(rows, columns, id = '') { @@ -815,6 +817,7 @@ async function loadSettings() { const vtKey = form.elements.namedItem('virustotal_api_key'); vtKey.placeholder = config.virustotal_api_key_configured ? 'Key configured; leave blank to keep it' : 'Paste VirusTotal API key'; window.streamProfiles = config.graylog_stream_profiles || []; + window.configuredStreams = config.graylog_streams || []; if (config.graylog_mcp_token_configured && config.graylog_mcp_url) loadStreams(); loadOllamaModels(); } @@ -842,10 +845,16 @@ async function loadStreams() { renderStreamPicker(payload.error || `Could not load streams: ${response.status}`); return; } - const selected = new Set((payload.selected || []).filter(item => item.enabled).map(item => item.id)); + const configuredStreams = payload.selected || window.configuredStreams || []; + let selected = new Set(configuredStreams.filter(item => item.enabled).map(item => item.id)); + let recoveryText = ''; + if (!selected.size && (window.streamProfiles || []).length) { + selected = new Set((window.streamProfiles || []).map(item => item.stream_id).filter(Boolean)); + recoveryText = 'No enabled streams were saved; streams with existing profiles are selected for recovery. Click Save streams to keep this selection.'; + } window.availableStreams = payload.streams || []; window.streamSelection = Object.fromEntries(window.availableStreams.map(stream => [stream.id, {id:stream.id, title:stream.title, enabled:selected.has(stream.id)}])); - renderStreamPicker(payload.error || ''); + renderStreamPicker(recoveryText || payload.error || ''); } document.getElementById('loadStreams').addEventListener('click', loadStreams); async function saveStreamSelection() { @@ -879,6 +888,7 @@ function renderStreamPicker(errorText='') { .filter(stream => !previousEnabledOnly || window.streamSelection[stream.id]?.enabled); const enabledCount = Object.values(window.streamSelection).filter(item => item.enabled).length; target.innerHTML = ` + ${errorText ? `
${esc(errorText)}
` : ''}
@@ -1027,7 +1037,10 @@ document.getElementById('settingsForm').addEventListener('submit', async event = values.profile_advisor_enabled = form.elements.profile_advisor_enabled.checked; values.threat_intel_enabled = form.elements.threat_intel_enabled.checked; values.graylog_tls_verify = form.elements.graylog_tls_verify.checked; - values.graylog_streams = Object.values(window.streamSelection || {}).length ? Object.values(window.streamSelection) : [...document.querySelectorAll('.graylog-stream')].map(item => ({id:item.dataset.id, title:item.dataset.title, enabled:item.checked})); + const streamValues = Object.values(window.streamSelection || {}); + if (streamValues.length) { + values.graylog_streams = streamValues; + } if (window.activeProfileStream) { let detectors={},fieldWeights={},relationshipFields=[]; try { detectors=form.elements.profile_detectors.value.trim() ? JSON.parse(form.elements.profile_detectors.value) : {}; } catch { document.getElementById('settingsResult').textContent='Detector thresholds must be valid JSON.'; return; } try { fieldWeights=form.elements.profile_field_weights.value.trim() ? JSON.parse(form.elements.profile_field_weights.value) : {}; } catch { document.getElementById('settingsResult').textContent='Field weights must be valid JSON.'; return; } try { relationshipFields=form.elements.profile_relationship_fields.value.trim() ? JSON.parse(form.elements.profile_relationship_fields.value) : []; } catch { document.getElementById('settingsResult').textContent='Behavior relationships must be valid JSON.'; return; } if (!Array.isArray(relationshipFields)) { document.getElementById('settingsResult').textContent='Behavior relationships must be a JSON list.'; return; } const entityFields=[...form.querySelectorAll('.profile-entity:checked')].map(item=>item.value); if (!entityFields.length) { document.getElementById('settingsResult').textContent='Select at least one Entity field for the active profile.'; return; } const profileTitle=window.activeProfileTitle || window.activeProfileStream; const profile={stream_id:window.activeProfileStream,name:form.elements.profile_name.value.trim() || `${profileTitle} profile`,entity_field:entityFields[0]||'',entity_fields:entityFields,timestamp_field:form.querySelector('[name="profile_timestamp"]:checked')?.value||'timestamp',categorical_fields:[...form.querySelectorAll('.profile-categorical:checked')].map(item=>item.value),numeric_fields:[...form.querySelectorAll('.profile-numeric:checked')].map(item=>item.value),detectors,field_weights:fieldWeights,relationship_fields:relationshipFields}; values.graylog_stream_profiles=[...(window.streamProfiles||[]).filter(item=>item.stream_id!==profile.stream_id),profile]; } const savedProfile = window.activeProfileStream ? ` Profile saved for ${window.activeProfileTitle || window.activeProfileStream}.` : ' No profile editor active, so profiles were not changed.'; const response = await fetch('/api/config', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(values)}); diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 38ceeee..674815e 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -57,6 +57,15 @@ class DashboardTests(unittest.TestCase): self.assertIn("incidentNotice", HTML) self.assertIn("Incident marked", HTML) + def test_dashboard_does_not_clear_streams_when_picker_is_unloaded(self): + self.assertIn("const streamValues = Object.values(window.streamSelection || {})", HTML) + self.assertIn("if (streamValues.length)", HTML) + self.assertIn("values.graylog_streams = streamValues", HTML) + + def test_dashboard_can_recover_stream_selection_from_profiles(self): + self.assertIn("No enabled streams were saved", HTML) + self.assertIn("streams with existing profiles are selected for recovery", HTML) + if __name__ == "__main__": unittest.main()