fix config

This commit is contained in:
larssand
2026-07-06 09:36:00 +02:00
parent 1238fd0e4d
commit bddc84395c
2 changed files with 25 additions and 3 deletions

View File

@@ -215,6 +215,8 @@ const tableSort = {};
const uiCache = {correlations: [], fieldRows: [], correlationHitboxes: [], selectedCorrelationKey: '', entityLabels: {}}; const uiCache = {correlations: [], fieldRows: [], correlationHitboxes: [], selectedCorrelationKey: '', entityLabels: {}};
window.availableStreams = []; window.availableStreams = [];
window.streamSelection = {}; window.streamSelection = {};
window.configuredStreams = [];
window.streamProfiles = [];
window.currentCorrelations = []; window.currentCorrelations = [];
window.currentConfiguration = {}; window.currentConfiguration = {};
function table(rows, columns, id = '') { function table(rows, columns, id = '') {
@@ -815,6 +817,7 @@ async function loadSettings() {
const vtKey = form.elements.namedItem('virustotal_api_key'); 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'; 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.streamProfiles = config.graylog_stream_profiles || [];
window.configuredStreams = config.graylog_streams || [];
if (config.graylog_mcp_token_configured && config.graylog_mcp_url) loadStreams(); if (config.graylog_mcp_token_configured && config.graylog_mcp_url) loadStreams();
loadOllamaModels(); loadOllamaModels();
} }
@@ -842,10 +845,16 @@ async function loadStreams() {
renderStreamPicker(payload.error || `Could not load streams: ${response.status}`); renderStreamPicker(payload.error || `Could not load streams: ${response.status}`);
return; 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.availableStreams = payload.streams || [];
window.streamSelection = Object.fromEntries(window.availableStreams.map(stream => [stream.id, {id:stream.id, title:stream.title, enabled:selected.has(stream.id)}])); 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); document.getElementById('loadStreams').addEventListener('click', loadStreams);
async function saveStreamSelection() { async function saveStreamSelection() {
@@ -879,6 +888,7 @@ function renderStreamPicker(errorText='') {
.filter(stream => !previousEnabledOnly || window.streamSelection[stream.id]?.enabled); .filter(stream => !previousEnabledOnly || window.streamSelection[stream.id]?.enabled);
const enabledCount = Object.values(window.streamSelection).filter(item => item.enabled).length; const enabledCount = Object.values(window.streamSelection).filter(item => item.enabled).length;
target.innerHTML = ` target.innerHTML = `
${errorText ? `<div class="notice warn">${esc(errorText)}</div>` : ''}
<div class="stream-tools"> <div class="stream-tools">
<input id="streamFilter" type="search" placeholder="Search streams by name or id" value="${esc(previousFilter)}"> <input id="streamFilter" type="search" placeholder="Search streams by name or id" value="${esc(previousFilter)}">
<label><input id="streamEnabledOnly" type="checkbox" ${previousEnabledOnly ? 'checked' : ''}> enabled only</label> <label><input id="streamEnabledOnly" type="checkbox" ${previousEnabledOnly ? 'checked' : ''}> enabled only</label>
@@ -1027,7 +1037,10 @@ document.getElementById('settingsForm').addEventListener('submit', async event =
values.profile_advisor_enabled = form.elements.profile_advisor_enabled.checked; values.profile_advisor_enabled = form.elements.profile_advisor_enabled.checked;
values.threat_intel_enabled = form.elements.threat_intel_enabled.checked; values.threat_intel_enabled = form.elements.threat_intel_enabled.checked;
values.graylog_tls_verify = form.elements.graylog_tls_verify.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]; } 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 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)}); const response = await fetch('/api/config', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(values)});

View File

@@ -57,6 +57,15 @@ class DashboardTests(unittest.TestCase):
self.assertIn("incidentNotice", HTML) self.assertIn("incidentNotice", HTML)
self.assertIn("Incident marked", 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__": if __name__ == "__main__":
unittest.main() unittest.main()