function defaultLogLossStreams() { return [ { name: "log_monitor", query: "full_log:log_monitor OR rule.id:100411", min_count: 1 }, { name: "fortigate", query: "full_log:fortigate OR full_log:FGT80F OR full_log:FGT60F OR full_log:FGT40F OR full_log:FGT501E", min_count: 1, }, { name: "windows_agent", query: "full_log:windows_agent OR full_log:windows", min_count: 1 }, ]; } function parseJsonOrThrow(text, label) { const raw = (text || "").trim(); if (!raw) { return {}; } try { return JSON.parse(raw); } catch (_err) { throw new Error(`Invalid JSON in ${label}`); } } const SIM_SCRIPT_DESCRIPTIONS = { fortigate: "FortiGate firewall telemetry simulator (40F/60F/80F/501E) for network and security events.", endpoint: "Windows/macOS/Linux endpoint-agent simulator for process, auth, and endpoint behavior logs.", cisco: "Cisco ASA/IOS network log simulator for parser/rule coverage and telemetry validation.", proposal_required: "Appendix A required-use-case simulator (A1-A4) for proposal/UAT coverage.", proposal_appendix_b: "Appendix B optional-use-case simulator (B1-B3), including log-loss test flow.", proposal_appendix_c: "Appendix C future-enhancement simulator (C1-C3 currently implemented).", wazuh_test: "Generic Wazuh baseline test events for connectivity and ingest sanity checks.", }; const SIM_SCRIPT_TARGET_OPTIONS = { fortigate: ["all", "501E", "80F", "60F", "40F"], endpoint: ["all", "windows", "mac", "linux"], cisco: ["all", "asa_acl_deny", "asa_vpn_auth_fail", "ios_login_fail", "ios_config_change"], proposal_required: ["all", "a1", "a2", "a3", "a4"], proposal_appendix_b: ["all", "b1", "b2", "b3"], proposal_appendix_c: ["all", "c1", "c2", "c3"], wazuh_test: ["all", "ioc_dns", "ioc_ips", "vpn_outside_th", "windows_auth_fail"], }; window.socUi = function socUi() { return { tabs: [ { key: "overview", label: "Overview" }, { key: "systems", label: "Systems" }, { key: "database", label: "Database" }, { key: "monitoring", label: "Monitoring" }, { key: "ioc", label: "IOC" }, { key: "geoip", label: "GeoIP" }, { key: "iris", label: "IRIS" }, { key: "shuffle", label: "Shuffle" }, { key: "wazuh", label: "Wazuh" }, { key: "mvp", label: "MVP" }, { key: "explorer", label: "API Explorer" }, ], activeTab: "overview", apiBase: window.location.origin, internalApiKey: "dev-internal-key", errorMessage: "", overview: { health: null, autoSync: null }, systemsMonitor: { data: null, loading: false, autoRefresh: true, paused: false, intervalSeconds: 20, minutes: 60, limit: 20, lastRefreshAt: null, timerId: null, }, dbTables: { data: null, loading: false, lastRefreshAt: null, }, dbBrowser: { selectedTable: "", rows: null, loading: false, limit: 50, offset: 0, }, simLogs: { runs: null, startResult: null, selectedRunId: "", output: null, outputLimit: 200, autoRefresh: true, intervalSeconds: 3, timerId: null, form: { script: "fortigate", targets: ["all"], scenario: "all", count: 1, delay_seconds: 0.3, forever: false, }, }, simWazuh: { latest: null, limit: 100, autoRefresh: true, showQuery: false, }, systemsCardMeta: [ { key: "wazuh", label: "Wazuh" }, { key: "shuffle", label: "Shuffle" }, { key: "iris", label: "IRIS" }, { key: "pagerduty", label: "PagerDuty" }, ], logLoss: { result: null }, cDetections: { state: null, evaluate: null, history: null }, ioc: { enrich: null, evaluate: null, history: null, upload: null, analysis: null, fileEval: null }, geoip: { ip: "8.8.8.8", result: null }, iris: { create: null, list: null }, shuffle: { status: null, catalog: null, execute: null }, wazuh: { status: null, list: null, sync: null }, mvp: { status: null, ingest: null, evaluate: null, policyText: JSON.stringify({ risk_thresholds: { high: 80, medium: 50 } }, null, 2), ingestText: JSON.stringify( { source: "wazuh", event_type: "vpn_geo_anomaly", event_id: "evt-demo-1", timestamp: new Date().toISOString(), severity: "high", title: "VPN login anomaly", description: "User login from unusual country", asset: { user: "alice", hostname: "win-dc01" }, network: { src_ip: "8.8.8.8", country: "US" }, tags: ["vpn", "geo-anomaly"], risk_context: { admin_account: false }, raw: { full_log: "soc_mvp_test=true" }, payload: {}, }, null, 2, ), iocEvalText: JSON.stringify({ ioc_type: "ip", ioc_value: "8.8.8.8", source_event: { event_id: "evt-demo-1" } }, null, 2), vpnEvalText: JSON.stringify( { user: "alice", src_ip: "8.8.8.8", country_code: "US", success: true, event_time: new Date().toISOString(), is_admin: false, off_hours: true, first_seen_country: true, event_id: "vpn-demo-1", }, null, 2, ), }, logLossForm: { minutes: 5, createTicket: false, streams: defaultLogLossStreams() }, cEvalForm: { minutes: 30, query: "soc_mvp_test=true", selectorsText: "c1,c2,c3", dry_run: true, limit: 200 }, iocForm: { ioc_type: "ip", ioc_value: "8.8.8.8", providersText: "virustotal,abuseipdb", malicious_threshold: 1, suspicious_threshold: 3, }, iocFileForm: { file: null, analysis_id: "", poll_timeout_seconds: 30, poll_interval_seconds: 2 }, irisForm: { title: "SOC Demo Ticket", description: "Created from SOC Integrator UI", case_customer: 1, case_soc_id: "soc-prod", }, irisList: { limit: 50, offset: 0 }, shuffleExec: { workflow_id: "", payloadText: JSON.stringify({ hello: "world" }, null, 2) }, wazuhList: { limit: 50, offset: 0, q: "" }, wazuhSync: { minutes: 120, limit: 50, q: "soc_mvp_test=true OR event_type:*" }, explorer: { spec: null, endpoints: [], selectedKey: "", selected: null, pathParamsText: "{}", queryText: "{}", bodyText: "{}", result: null, }, tabClass(key) { return this.activeTab === key ? "bg-sky-600 text-white border-sky-600" : "bg-white text-slate-700 hover:bg-slate-50"; }, init() { this.loadHealth(); this.loadAutoSync(); this.loadCState(); this.loadSystemsMonitor(); this.loadDbTables(); this.loadSimRuns(); this.startSimLogsAutoRefresh(); this.startSystemsMonitorAutoRefresh(); this.loadOpenApiSpec(); }, simScriptUsesScenario() { return this.simLogs.form.script === "endpoint"; }, simScriptDescription() { const key = String(this.simLogs.form.script || "").trim(); return SIM_SCRIPT_DESCRIPTIONS[key] || "No description available for this script."; }, simTargetOptions() { const key = String(this.simLogs.form.script || "").trim(); return SIM_SCRIPT_TARGET_OPTIONS[key] || ["all"]; }, simTargetSelectionChanged() { const options = this.simTargetOptions(); const selected = Array.isArray(this.simLogs.form.targets) ? [...this.simLogs.form.targets] : []; let valid = selected.filter((item) => options.includes(item)); if (valid.includes("all") && valid.length > 1) { valid = ["all"]; } if (!valid.length) { valid = ["all"]; } this.simLogs.form.targets = valid; }, onSimScriptChange() { this.simTargetSelectionChanged(); }, selectedTargetsForRun() { const options = this.simTargetOptions(); let selected = Array.isArray(this.simLogs.form.targets) ? [...this.simLogs.form.targets] : []; selected = selected.filter((item) => options.includes(item)); if (!selected.length || selected.includes("all")) { return ["all"]; } return selected; }, systemsStatusClass(status) { if (status === "ok") { return "status-ok"; } if (status === "degraded") { return "status-warn"; } return "status-down"; }, systemsCards() { const root = this.unwrapApiData(this.systemsMonitor.data) || {}; return root.cards || {}; }, systemsCard(key) { const cards = this.systemsCards(); return cards[key] || {}; }, systemsRecentRows(key) { const recent = this.systemsCard(key).recent; if (!Array.isArray(recent)) { return []; } return recent.map((row, index) => this.normalizeTableRow(row, index)); }, systemsRecentColumns(key) { return this.tableColumns(this.systemsRecentRows(key)); }, systemsPipelineRows() { const root = this.unwrapApiData(this.systemsMonitor.data) || {}; const pipeline = root.pipeline || {}; return Object.entries(pipeline).map(([key, value]) => ({ key, value: this.cellText(value) })); }, systemsSetAutoRefresh(enabled) { this.systemsMonitor.autoRefresh = Boolean(enabled); this.startSystemsMonitorAutoRefresh(); }, systemsTogglePaused() { this.systemsMonitor.paused = !this.systemsMonitor.paused; }, systemsSetInterval(seconds) { const parsed = Number(seconds || 20); this.systemsMonitor.intervalSeconds = parsed > 0 ? parsed : 20; this.startSystemsMonitorAutoRefresh(); }, stopSystemsMonitorAutoRefresh() { if (this.systemsMonitor.timerId) { clearInterval(this.systemsMonitor.timerId); this.systemsMonitor.timerId = null; } }, startSystemsMonitorAutoRefresh() { this.stopSystemsMonitorAutoRefresh(); if (!this.systemsMonitor.autoRefresh) { return; } this.systemsMonitor.timerId = setInterval(() => { if (!this.systemsMonitor.paused) { this.loadSystemsMonitor(); } }, Math.max(5, Number(this.systemsMonitor.intervalSeconds || 20)) * 1000); }, async loadSystemsMonitor() { try { if (!this.internalApiKey) { return; } this.systemsMonitor.loading = true; const params = new URLSearchParams({ minutes: String(Math.max(1, Number(this.systemsMonitor.minutes || 60))), limit: String(Math.max(1, Number(this.systemsMonitor.limit || 20))), }); this.systemsMonitor.data = await this.apiCall(`/monitor/systems?${params.toString()}`, { internal: true, }); this.systemsMonitor.lastRefreshAt = new Date().toISOString(); } catch (err) { this.setErr("Systems monitor failed", err); } finally { this.systemsMonitor.loading = false; } }, async loadDbTables() { try { if (!this.internalApiKey) { return; } this.dbTables.loading = true; this.dbTables.data = await this.apiCall("/monitor/db/tables", { internal: true, }); this.dbTables.lastRefreshAt = new Date().toISOString(); if (!this.dbBrowser.selectedTable) { const tables = this.dbTableRows(); if (tables.length > 0) { this.dbBrowser.selectedTable = String(tables[0].table || ""); await this.loadDbRows(); } } } catch (err) { this.setErr("Database tables failed", err); } finally { this.dbTables.loading = false; } }, dbTableRows() { const root = this.unwrapApiData(this.dbTables.data) || {}; const tables = Array.isArray(root.tables) ? root.tables : []; return tables.map((row, index) => this.normalizeTableRow(row, index)); }, async loadDbRows() { try { const table = String(this.dbBrowser.selectedTable || "").trim(); if (!table) { this.dbBrowser.rows = null; return; } this.dbBrowser.loading = true; const limit = Math.max(1, Math.min(500, Number(this.dbBrowser.limit || 50))); const offset = Math.max(0, Number(this.dbBrowser.offset || 0)); this.dbBrowser.rows = await this.apiCall( `/monitor/db/tables/${encodeURIComponent(table)}/rows?limit=${limit}&offset=${offset}`, { internal: true }, ); } catch (err) { this.setErr("Database rows failed", err); } finally { this.dbBrowser.loading = false; } }, dbSelectedRows() { const root = this.unwrapApiData(this.dbBrowser.rows) || {}; const rows = Array.isArray(root.rows) ? root.rows : []; return rows.map((row, index) => this.normalizeTableRow(row, index)); }, dbSelectedColumns() { return this.tableColumns(this.dbSelectedRows()); }, async loadSimRuns() { try { if (!this.internalApiKey) { return; } this.simLogs.runs = await this.apiCall("/sim/logs/runs", { internal: true }); const rows = this.extractRows(this.simLogs.runs); if (!this.simLogs.selectedRunId && rows.length) { this.simLogs.selectedRunId = rows[0].run_id || ""; if (this.simLogs.selectedRunId) { await this.loadSimWazuhLatest(this.simLogs.selectedRunId); } } } catch (err) { this.setErr("Load sim runs failed", err); } }, async startSimRun() { try { const targets = this.selectedTargetsForRun(); const results = []; for (const target of targets) { const payload = { script: this.simLogs.form.script, target, scenario: this.simLogs.form.scenario || "all", count: Number(this.simLogs.form.count || 1), delay_seconds: Number(this.simLogs.form.delay_seconds || 0.3), forever: Boolean(this.simLogs.form.forever), }; const result = await this.apiCall("/sim/logs/start", { method: "POST", internal: true, json: payload, }); results.push({ target, result }); } this.simLogs.startResult = { data: { started_targets: targets, runs: results.map((item) => ({ target: item.target, run: this.unwrapApiData(item.result)?.run || null, })), }, }; await this.loadSimRuns(); const started = results .map((item) => this.unwrapApiData(item.result)?.run) .find((run) => run && run.run_id); if (started && started.run_id) { this.simLogs.selectedRunId = started.run_id; await this.loadSimOutput(started.run_id); await this.loadSimWazuhLatest(started.run_id); } } catch (err) { this.setErr("Start sim run failed", err); } }, async stopSimRun(runId) { try { await this.apiCall(`/sim/logs/stop/${encodeURIComponent(runId)}`, { method: "POST", internal: true, }); await this.loadSimRuns(); if (this.simLogs.selectedRunId === runId) { await this.loadSimOutput(runId); await this.loadSimWazuhLatest(runId); } } catch (err) { this.setErr("Stop sim run failed", err); } }, async stopRunningSimRuns() { try { await this.apiCall("/sim/logs/stop-running", { method: "POST", internal: true, }); await this.loadSimRuns(); if (this.simLogs.selectedRunId) { await this.loadSimOutput(this.simLogs.selectedRunId); await this.loadSimWazuhLatest(this.simLogs.selectedRunId); } } catch (err) { this.setErr("Stop running sim runs failed", err); } }, simRunRows() { return this.extractRows(this.simLogs.runs); }, selectSimRun(runId) { this.simLogs.selectedRunId = runId || ""; this.loadSimOutput(this.simLogs.selectedRunId); this.loadSimWazuhLatest(this.simLogs.selectedRunId); }, simSelectedRun() { const rows = this.simRunRows(); return rows.find((row) => row.run_id === this.simLogs.selectedRunId) || null; }, stopSimLogsAutoRefresh() { if (this.simLogs.timerId) { clearInterval(this.simLogs.timerId); this.simLogs.timerId = null; } }, startSimLogsAutoRefresh() { this.stopSimLogsAutoRefresh(); if (!this.simLogs.autoRefresh) { return; } this.simLogs.timerId = setInterval(async () => { if (this.activeTab !== "systems") { return; } try { await this.loadSimRuns(); if (this.simLogs.selectedRunId) { await this.loadSimOutput(this.simLogs.selectedRunId); await this.loadSimWazuhLatest(this.simLogs.selectedRunId); } } catch (_err) { // per-request error is already handled by each loader } }, 5000); }, async loadSimOutput(runId = "") { const selectedRunId = runId || this.simLogs.selectedRunId; if (!selectedRunId) { this.simLogs.output = null; return; } try { const limit = Math.max(10, Number(this.simLogs.outputLimit || 200)); this.simLogs.output = await this.apiCall( `/sim/logs/output/${encodeURIComponent(selectedRunId)}?limit=${limit}`, { internal: true }, ); } catch (err) { this.setErr("Load sim output failed", err); } }, async loadSimWazuhLatest(runId = "") { const selectedRunId = runId || this.simLogs.selectedRunId; if (!selectedRunId) { this.simWazuh.latest = null; return; } try { const limit = 100; this.simWazuh.latest = await this.apiCall( `/sim/logs/wazuh-latest/${encodeURIComponent(selectedRunId)}?limit=${limit}&minutes=1440&include_raw=true`, { internal: true }, ); } catch (err) { this.setErr("Load sim Wazuh latest failed", err); } }, simWazuhEventsRows() { const root = this.unwrapApiData(this.simWazuh.latest) || {}; const events = Array.isArray(root.events) ? root.events : []; return events.map((row, index) => this.normalizeTableRow(row, index)); }, simWazuhRulesRows() { const root = this.unwrapApiData(this.simWazuh.latest) || {}; const rules = Array.isArray(root.rules) ? root.rules : []; return rules.map((row, index) => this.normalizeTableRow(row, index)); }, simWazuhEventTableRows() { return this.simWazuhEventsRows().map((row) => ({ time: row["@timestamp"] || row.timestamp || "", rule_id: row.rule_id || row.rule?.id || "", rule_description: row.rule_description || row.rule?.description || "", full_log: row.full_log || "", })); }, parseFullLog(fullLog) { const text = String(fullLog || "").trim(); if (!text) { return {}; } const parsed = {}; const regex = /([A-Za-z0-9_.-]+)=("([^"]*)"|[^\s]+)/g; let match = null; while ((match = regex.exec(text)) !== null) { const key = match[1]; let value = match[3] !== undefined ? match[3] : match[2]; if (value === "true") { value = true; } else if (value === "false") { value = false; } else if (/^-?\d+$/.test(value)) { value = Number(value); } else if (/^-?\d+\.\d+$/.test(value)) { value = Number(value); } parsed[key] = value; } if (Object.keys(parsed).length === 0) { return { message: text }; } return parsed; }, fullLogAsJsonText(fullLog) { return JSON.stringify(this.parseFullLog(fullLog), null, 2); }, pretty(value) { if (value === null || value === undefined) { return "No data yet"; } try { return JSON.stringify(value, null, 2); } catch (_err) { return String(value); } }, unwrapApiData(payload) { if (payload && typeof payload === "object" && !Array.isArray(payload) && Object.prototype.hasOwnProperty.call(payload, "data")) { return payload.data; } return payload; }, findFirstArray(node, depth = 0) { if (depth > 5 || node === null || node === undefined) { return null; } if (Array.isArray(node)) { return node; } if (typeof node !== "object") { return null; } const preferredKeys = ["items", "matches", "results", "alerts", "agents", "workflows", "apps", "rows", "data"]; for (const key of preferredKeys) { if (Array.isArray(node[key])) { return node[key]; } } for (const value of Object.values(node)) { const found = this.findFirstArray(value, depth + 1); if (found) { return found; } } return null; }, normalizeTableRow(row, index) { if (row && typeof row === "object" && !Array.isArray(row)) { return row; } return { row: index + 1, value: row }; }, extractRows(payload) { const root = this.unwrapApiData(payload); const rows = this.findFirstArray(root) || []; return rows.map((row, index) => this.normalizeTableRow(row, index)); }, tableColumns(rows) { const colSet = new Set(); const sample = rows.slice(0, 30); for (const row of sample) { if (!row || typeof row !== "object" || Array.isArray(row)) { continue; } for (const key of Object.keys(row)) { if (colSet.size >= 10) { break; } colSet.add(key); } if (colSet.size >= 10) { break; } } return Array.from(colSet); }, cellText(value) { if (value === null || value === undefined) { return ""; } if (typeof value === "string") { return value; } if (typeof value === "number" || typeof value === "boolean") { return String(value); } return JSON.stringify(value); }, keyValueRows(payload) { const root = this.unwrapApiData(payload); if (!root || typeof root !== "object" || Array.isArray(root)) { return []; } return Object.entries(root).map(([key, value]) => ({ key, value: this.cellText(value), })); }, providersList() { return (this.iocForm.providersText || "") .split(",") .map((s) => s.trim().toLowerCase()) .filter((s) => s.length > 0); }, internalHeaders() { return this.internalApiKey ? { "x-internal-api-key": this.internalApiKey } : {}; }, async apiCall(path, options = {}) { this.errorMessage = ""; const headers = { Accept: "application/json", ...(options.json ? { "Content-Type": "application/json" } : {}), ...(options.internal ? this.internalHeaders() : {}), ...(options.headers || {}), }; const response = await fetch(`${this.apiBase}${path}`, { method: options.method || "GET", headers, body: options.json ? JSON.stringify(options.json) : options.body, }); let data = null; try { data = await response.json(); } catch (_err) { data = { detail: "Non-JSON response", status: response.status }; } if (!response.ok) { const detail = data && data.detail ? data.detail : `HTTP ${response.status}`; throw new Error(detail); } return data; }, setErr(prefix, err) { this.errorMessage = `${prefix}: ${err.message}`; }, async loadHealth() { try { this.overview.health = await this.apiCall("/health"); } catch (err) { this.setErr("Health failed", err); } }, async loadAutoSync() { try { this.overview.autoSync = await this.apiCall("/wazuh/auto-sync/status"); } catch (err) { this.setErr("Auto-sync failed", err); } }, applyLogLossPreset(preset) { if (preset === "b2") { this.logLossForm.minutes = 10; this.logLossForm.streams = [{ name: "b2_log_monitor", query: "full_log:log_monitor OR rule.id:100411", min_count: 1 }]; } else { this.logLossForm.minutes = 5; this.logLossForm.streams = defaultLogLossStreams(); } }, addLogLossStream() { this.logLossForm.streams.push({ name: "", query: "", min_count: 1 }); }, removeLogLossStream(index) { this.logLossForm.streams.splice(index, 1); }, async runLogLossCheck() { try { this.logLoss.result = await this.apiCall( `/monitor/log-loss/check?create_ticket=${this.logLossForm.createTicket ? "true" : "false"}`, { method: "POST", internal: true, json: { minutes: Number(this.logLossForm.minutes || 5), streams: this.logLossForm.streams.map((s) => ({ name: s.name, query: s.query, min_count: Number(s.min_count || 0), })), }, }, ); } catch (err) { this.setErr("Log-loss check failed", err); } }, async loadCState() { try { this.cDetections.state = await this.apiCall("/monitor/c-detections/state", { internal: true }); } catch (err) { this.setErr("C state failed", err); } }, async runCEvaluate() { try { const selectors = this.cEvalForm.selectorsText .split(",") .map((x) => x.trim()) .filter((x) => x.length > 0); this.cDetections.evaluate = await this.apiCall("/monitor/c-detections/evaluate", { method: "POST", internal: true, json: { minutes: Number(this.cEvalForm.minutes || 30), query: this.cEvalForm.query, selectors, dry_run: Boolean(this.cEvalForm.dry_run), limit: Number(this.cEvalForm.limit || 200), }, }); } catch (err) { this.setErr("C evaluate failed", err); } }, async loadCHistory() { try { this.cDetections.history = await this.apiCall("/monitor/c-detections/history?limit=50&offset=0", { internal: true }); } catch (err) { this.setErr("C history failed", err); } }, async runIocEnrich() { try { this.ioc.enrich = await this.apiCall("/ioc/enrich", { method: "POST", json: { ioc_type: this.iocForm.ioc_type, ioc_value: this.iocForm.ioc_value, providers: this.providersList(), }, }); } catch (err) { this.setErr("IOC enrich failed", err); } }, async runIocEvaluate() { try { this.ioc.evaluate = await this.apiCall("/ioc/evaluate", { method: "POST", json: { ioc_type: this.iocForm.ioc_type, ioc_value: this.iocForm.ioc_value, providers: this.providersList(), malicious_threshold: Number(this.iocForm.malicious_threshold || 1), suspicious_threshold: Number(this.iocForm.suspicious_threshold || 3), }, }); } catch (err) { this.setErr("IOC evaluate failed", err); } }, async loadIocHistory() { try { this.ioc.history = await this.apiCall("/ioc/history?limit=50&offset=0"); } catch (err) { this.setErr("IOC history failed", err); } }, async lookupGeoIp() { try { const ip = String(this.geoip.ip || "").trim(); if (!ip) { throw new Error("IP is required"); } this.geoip.result = await this.apiCall(`/geoip/${encodeURIComponent(ip)}`); } catch (err) { this.setErr("GeoIP lookup failed", err); } }, onFileSelected(event) { const files = event && event.target ? event.target.files : null; this.iocFileForm.file = files && files.length ? files[0] : null; }, async uploadIocFile() { try { if (!this.iocFileForm.file) { throw new Error("Select a file first"); } const form = new FormData(); form.append("file", this.iocFileForm.file); this.ioc.upload = await this.apiCall("/ioc/upload-file", { method: "POST", body: form }); } catch (err) { this.setErr("IOC upload failed", err); } }, async evaluateIocFile() { try { if (!this.iocFileForm.file) { throw new Error("Select a file first"); } const params = new URLSearchParams({ malicious_threshold: String(this.iocForm.malicious_threshold || 1), suspicious_threshold: String(this.iocForm.suspicious_threshold || 3), poll_timeout_seconds: String(this.iocFileForm.poll_timeout_seconds || 30), poll_interval_seconds: String(this.iocFileForm.poll_interval_seconds || 2), }); const form = new FormData(); form.append("file", this.iocFileForm.file); this.ioc.fileEval = await this.apiCall(`/ioc/evaluate-file?${params.toString()}`, { method: "POST", body: form }); } catch (err) { this.setErr("IOC file evaluate failed", err); } }, async getIocAnalysis() { try { if (!this.iocFileForm.analysis_id.trim()) { throw new Error("analysis_id is required"); } this.ioc.analysis = await this.apiCall(`/ioc/analysis/${encodeURIComponent(this.iocFileForm.analysis_id.trim())}`); } catch (err) { this.setErr("IOC analysis failed", err); } }, async createIrisTicket() { try { this.iris.create = await this.apiCall("/iris/tickets", { method: "POST", json: { title: this.irisForm.title, description: this.irisForm.description, case_customer: Number(this.irisForm.case_customer || 1), case_soc_id: this.irisForm.case_soc_id, payload: {}, }, }); } catch (err) { this.setErr("IRIS create failed", err); } }, async loadIrisTickets() { try { this.iris.list = await this.apiCall(`/iris/tickets?limit=${Number(this.irisList.limit || 50)}&offset=${Number(this.irisList.offset || 0)}`); } catch (err) { this.setErr("IRIS list failed", err); } }, async loadShuffleHealth() { try { this.shuffle.status = await this.apiCall("/shuffle/health"); } catch (err) { this.setErr("Shuffle health failed", err); } }, async loadShuffleAuth() { try { this.shuffle.status = await this.apiCall("/shuffle/auth-test"); } catch (err) { this.setErr("Shuffle auth failed", err); } }, async loadShuffleApps() { try { this.shuffle.catalog = await this.apiCall("/shuffle/apps"); } catch (err) { this.setErr("Shuffle apps failed", err); } }, async loadShuffleWorkflows() { try { this.shuffle.catalog = await this.apiCall("/shuffle/workflows"); } catch (err) { this.setErr("Shuffle workflows failed", err); } }, async executeShuffleWorkflow() { try { if (!this.shuffleExec.workflow_id.trim()) { throw new Error("workflow_id is required"); } const payload = parseJsonOrThrow(this.shuffleExec.payloadText, "shuffle payload"); this.shuffle.execute = await this.apiCall(`/shuffle/workflows/${encodeURIComponent(this.shuffleExec.workflow_id.trim())}/execute`, { method: "POST", json: payload, }); } catch (err) { this.setErr("Shuffle execute failed", err); } }, async wazuhCall(mode) { try { if (mode === "auth") { this.wazuh.status = await this.apiCall("/wazuh/auth-test"); } else if (mode === "manager") { this.wazuh.status = await this.apiCall("/wazuh/manager-info"); } else if (mode === "version") { this.wazuh.status = await this.apiCall("/sync/wazuh-version"); } else if (mode === "autosync") { this.wazuh.status = await this.apiCall("/wazuh/auto-sync/status"); } } catch (err) { this.setErr("Wazuh status failed", err); } }, async loadWazuhAgents() { try { this.wazuh.list = await this.apiCall(`/wazuh/agents?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}`); } catch (err) { this.setErr("Wazuh agents failed", err); } }, async loadWazuhAlerts() { try { const q = this.wazuhList.q ? `&q=${encodeURIComponent(this.wazuhList.q)}` : ""; this.wazuh.list = await this.apiCall(`/wazuh/alerts?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}${q}`); } catch (err) { this.setErr("Wazuh alerts failed", err); } }, async loadWazuhManagerLogs() { try { const q = this.wazuhList.q ? `&q=${encodeURIComponent(this.wazuhList.q)}` : ""; this.wazuh.list = await this.apiCall(`/wazuh/manager-logs?limit=${Number(this.wazuhList.limit || 50)}&offset=${Number(this.wazuhList.offset || 0)}${q}`); } catch (err) { this.setErr("Wazuh manager logs failed", err); } }, async syncWazuhToMvp() { try { const params = new URLSearchParams({ limit: String(Number(this.wazuhSync.limit || 50)), minutes: String(Number(this.wazuhSync.minutes || 120)), q: String(this.wazuhSync.q || "soc_mvp_test=true OR event_type:*"), }); this.wazuh.sync = await this.apiCall(`/wazuh/sync-to-mvp?${params.toString()}`, { method: "POST", internal: true, }); } catch (err) { this.setErr("Wazuh sync failed", err); } }, async loadMvpDependencies() { try { this.mvp.status = await this.apiCall("/mvp/health/dependencies"); } catch (err) { this.setErr("MVP dependencies failed", err); } }, async loadMvpPolicy() { try { const result = await this.apiCall("/mvp/config/policies"); this.mvp.status = result; const policy = result && result.data ? result.data.policy : null; if (policy) { this.mvp.policyText = JSON.stringify(policy, null, 2); } } catch (err) { this.setErr("MVP policy get failed", err); } }, async updateMvpPolicy() { try { const payload = parseJsonOrThrow(this.mvp.policyText, "policy JSON"); this.mvp.status = await this.apiCall("/mvp/config/policies", { method: "PUT", internal: true, json: payload, }); } catch (err) { this.setErr("MVP policy update failed", err); } }, async mvpIngestIncident() { try { const payload = parseJsonOrThrow(this.mvp.ingestText, "mvp ingest JSON"); this.mvp.ingest = await this.apiCall("/mvp/incidents/ingest", { method: "POST", internal: true, json: payload, }); } catch (err) { this.setErr("MVP ingest failed", err); } }, async mvpEvaluateIoc() { try { const payload = parseJsonOrThrow(this.mvp.iocEvalText, "mvp IOC JSON"); this.mvp.evaluate = await this.apiCall("/mvp/ioc/evaluate", { method: "POST", internal: true, json: payload, }); } catch (err) { this.setErr("MVP IOC evaluate failed", err); } }, async mvpEvaluateVpn() { try { const payload = parseJsonOrThrow(this.mvp.vpnEvalText, "mvp VPN JSON"); this.mvp.evaluate = await this.apiCall("/mvp/vpn/evaluate", { method: "POST", internal: true, json: payload, }); } catch (err) { this.setErr("MVP VPN evaluate failed", err); } }, async loadOpenApiSpec() { try { const spec = await this.apiCall("/openapi.json"); this.explorer.spec = spec; const endpoints = []; const paths = spec && spec.paths ? spec.paths : {}; Object.keys(paths).forEach((path) => { const item = paths[path] || {}; ["get", "post", "put", "delete", "patch"].forEach((method) => { if (item[method]) { endpoints.push({ key: `${method.toUpperCase()} ${path}`, method, path, operation: item[method], pathItem: item, }); } }); }); this.explorer.endpoints = endpoints.sort((a, b) => a.key.localeCompare(b.key)); if (this.explorer.endpoints.length > 0 && !this.explorer.selectedKey) { this.explorer.selectedKey = this.explorer.endpoints[0].key; this.selectExplorerEndpoint(); } } catch (err) { this.setErr("OpenAPI load failed", err); } }, selectExplorerEndpoint() { const selected = this.explorer.endpoints.find((x) => x.key === this.explorer.selectedKey) || null; this.explorer.selected = selected; if (!selected) { return; } const params = [] .concat(selected.pathItem.parameters || []) .concat(selected.operation.parameters || []); const pathParams = {}; const queryParams = {}; params.forEach((p) => { if (!p || !p.name) { return; } if (p.in === "path") { pathParams[p.name] = ""; } if (p.in === "query") { queryParams[p.name] = ""; } }); this.explorer.pathParamsText = JSON.stringify(pathParams, null, 2); this.explorer.queryText = JSON.stringify(queryParams, null, 2); const hasJsonBody = selected.operation && selected.operation.requestBody && selected.operation.requestBody.content && selected.operation.requestBody.content["application/json"]; this.explorer.bodyText = hasJsonBody ? JSON.stringify({}, null, 2) : "{}"; }, async runExplorerRequest() { try { const selected = this.explorer.selected; if (!selected) { throw new Error("Select an endpoint first"); } const pathParams = parseJsonOrThrow(this.explorer.pathParamsText, "path params"); const queryParams = parseJsonOrThrow(this.explorer.queryText, "query params"); const body = parseJsonOrThrow(this.explorer.bodyText, "body"); let path = selected.path; Object.keys(pathParams).forEach((k) => { path = path.replace(`{${k}}`, encodeURIComponent(String(pathParams[k]))); }); const qs = new URLSearchParams(); Object.keys(queryParams).forEach((k) => { const v = queryParams[k]; if (v !== "" && v !== null && v !== undefined) { qs.append(k, String(v)); } }); const fullPath = qs.toString() ? `${path}?${qs.toString()}` : path; const needsInternal = fullPath.startsWith("/monitor/") || fullPath.startsWith("/mvp/") || fullPath.startsWith("/wazuh/sync-to-mvp"); const withBody = ["post", "put", "patch"].includes(selected.method); this.explorer.result = await this.apiCall(fullPath, { method: selected.method.toUpperCase(), internal: needsInternal, ...(withBody ? { json: body } : {}), }); } catch (err) { this.setErr("Explorer request failed", err); } }, }; };