const STATUS_RUNNING = 'running'; const STATUS_QUEUED = 'queued'; const STATUS_NEEDS_UPDATE = 'needs-update'; const STATUS_COMPLETE = 'complete'; async function populateDivs() { const systemStats = await getSystemStats(); const systemStatsDiv = document.getElementById('system-stats'); systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2); const analysisReportDiv = document.getElementById('analysis-report'); try { const analysisReport = await getAnalysisReport('live'); analysisReportDiv.innerHTML = JSON.stringify(analysisReport, null, 2); } catch (e) { analysisReportDiv.innerHTML = e.toString(); } const qmdlManifest = await getQmdlManifest(); await updateAnalysisStatus(qmdlManifest); await updateAnalysisResults(qmdlManifest); updateQmdlManifestTable(qmdlManifest); } function setStatus(qmdlManifest, name, status) { // ignore qmdlManifest.current_entry, it's always running for (const entry of qmdlManifest.entries) { if (entry.name === name) { entry['status'] = status; return; } } } async function updateAnalysisStatus(qmdlManifest) { const status = JSON.parse(await req('GET', '/api/analysis')); if (status.running) { setStatus(qmdlManifest, status.running, STATUS_RUNNING); } for (const queued in status.queued) { setStatus(qmdlManifest, queued, STATUS_QUEUED); } } function parseNewlineDelimitedJSON(inputStr) { const lines = inputStr.split('\n'); const result = []; let currentLine = ''; while (lines.length > 0) { currentLine += lines.shift(); try { const entry = JSON.parse(currentLine); result.push(entry); currentLine = ''; // if this chunk wasn't valid JSON, there was an escaped newline in the // JSON line, so simply continue to the next one } catch (e) {} } return result; } async function updateEntryAnalysisResult(entry) { entry.analysis = { warnings: [], }; const report = parseNewlineDelimitedJSON(await req('GET', `/api/analysis-report/${entry.name}`)); for (const row of report) { if (row["analysis"]) { const timestamp = new Date(row["timestamp"]); const analysis = row["analysis"]; for (const warning of analysis) { entry.analysis.warnings.push({ timestamp, warning, }) } } } if (entry.analysis.warnings.length === 0) { entry.analysis_result = `0 warnings!`; } else { entry.analysis_result = `!!! ${entry.analysis.warnings.length} warnings !!!`; for (const warning of entry.analysis.warnings) { for (const event of warning.warning.events) { if (event === null) continue; msg = `${warning.timestamp}: ${event.message}` entry.analysis_result += `
${msg}` } } } } async function updateAnalysisResults(qmdlManifest) { if (qmdlManifest.current_entry) { await updateEntryAnalysisResult(qmdlManifest.current_entry); } for (const entry of qmdlManifest.entries) { if (entry.status === STATUS_NEEDS_UPDATE) { await updateEntryAnalysisResult(entry); entry.status = STATUS_COMPLETE; } } } function updateQmdlManifestTable(manifest) { const table = document.getElementById('qmdl-manifest-table'); const numRows = table.rows.length; for (let i=1; i { await req('POST', uri); populateDivs(); }; return link; } function createEntryRow(entry, isCurrent) { const row = document.createElement('tr'); const name = document.createElement('th'); name.scope = 'row'; name.innerText = entry.name; row.appendChild(name); for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes']) { const td = document.createElement('td'); td.innerText = entry[key]; row.appendChild(td); } const pcapTd = document.createElement('td'); pcapTd.appendChild(createLink(`/api/pcap/${entry.name}`, 'pcap')); row.appendChild(pcapTd); const qmdlTd = document.createElement('td'); qmdlTd.appendChild(createLink(`/api/qmdl/${entry.name}.qmdl`, 'qmdl')); row.appendChild(qmdlTd); const analysisResult = document.createElement('td'); analysisResult.innerHTML = entry.analysis_result; if (entry.analysis.warnings.length > 0) { row.classList.add("warning"); } row.appendChild(analysisResult); const actionsButtons = document.createElement('td'); actionsButtons.appendChild(createButton(`/api/delete-recording/${entry.name}`, 'Delete')); row.appendChild(actionsButtons); return row; } async function getAnalysisReport(name) { const rows = await req('GET', `/api/analysis-report/${name}`); return rows.split('\n') .filter(row => row.length > 0) .map(row => JSON.parse(row)); } async function getSystemStats() { return JSON.parse(await req('GET', '/api/system-stats')); } async function getQmdlManifest() { const manifest = JSON.parse(await req('GET', '/api/qmdl-manifest')); if (manifest.current_entry) { parseQmdlEntry(manifest.current_entry); } for (entry of manifest.entries) { parseQmdlEntry(entry); } // sort them in reverse chronological order manifest.entries.reverse(); return manifest; } function parseQmdlEntry(entry) { entry.status = STATUS_NEEDS_UPDATE; entry.analysis_result = 'Waiting...'; entry.start_time = new Date(entry.start_time); if (entry.last_message_time === null) { entry.last_message_time = "N/A"; } else { entry.last_message_time = new Date(entry.last_message_time); } } async function startRecording() { await req('POST', '/api/start-recording'); populateDivs(); } async function stopRecording() { await req('POST', '/api/stop-recording'); populateDivs(); } async function deleteAllRecodings() { if (window.confirm("Are you sure you want to permanently delete all of your recordings?")) { await req('POST', '/api/delete-all-recordings'); populateDivs(); } } async function req(method, url) { const response = await fetch(url, { method: method, }); const body = await response.text(); if (response.status >= 200 && response.status < 300) { return body; } else { throw new Error(body); } }