| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252 |
- 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);
- }
- },
- };
- };
|