const btnIF = document.getElementById("btnIF"); const btnAE = document.getElementById("btnAE"); const startBtn = document.getElementById("startBtn"); let chosenAlgorithm = null; let chosenAlgorithmName = ""; let startTime = null; let lastResults = null; let progressInterval = null; let backendErrorShown = false; const JOB_STORAGE_KEY = "default_active_job"; function selectAlgorithm(selected, other) { selected.classList.add("scale-105", "border-2", "border-black"); selected.classList.remove("opacity-50"); other.classList.remove("scale-105", "border-2", "border-black"); other.classList.add("opacity-50"); } function enableStartButton() { startBtn.classList.remove("bg-gray-700", "text-gray-300", "cursor-not-allowed"); startBtn.classList.add("bg-orange-500", "hover:bg-orange-400", "text-white", "cursor-pointer"); } btnIF.addEventListener("click", () => { selectAlgorithm(btnIF, btnAE); chosenAlgorithm = "isolation_forest"; chosenAlgorithmName = "Isolation Forest"; enableStartButton(); }); btnAE.addEventListener("click", () => { selectAlgorithm(btnAE, btnIF); chosenAlgorithm = "autoencoder"; chosenAlgorithmName = "Autoencoder"; enableStartButton(); }); function getBackendJobName() { return chosenAlgorithm === "isolation_forest" ? "iforest" : "autoencoder"; } function getResultURL() { return chosenAlgorithm === "isolation_forest" ? "get-isolation-forest-result" : "get-autoencoder-result"; } function saveActiveJob() { sessionStorage.setItem(JOB_STORAGE_KEY, JSON.stringify({ algorithm: chosenAlgorithm, algorithmName: chosenAlgorithmName, backendJob: getBackendJobName(), startedAt: Date.now() })); } function loadActiveJob() { const raw = sessionStorage.getItem(JOB_STORAGE_KEY); if (!raw) return null; try { return JSON.parse(raw); } catch { sessionStorage.removeItem(JOB_STORAGE_KEY); return null; } } function clearActiveJob() { sessionStorage.removeItem(JOB_STORAGE_KEY); } function applySavedAlgorithm(saved) { chosenAlgorithm = saved.algorithm; chosenAlgorithmName = saved.algorithmName; startTime = saved.startedAt; if (chosenAlgorithm === "isolation_forest") { selectAlgorithm(btnIF, btnAE); } else if (chosenAlgorithm === "autoencoder") { selectAlgorithm(btnAE, btnIF); } enableStartButton(); } function updateProgressUI(progress) { document.getElementById("progressContainer").classList.remove("hidden"); document.getElementById("progressBar").style.width = progress + "%"; document.getElementById("progressLabel").textContent = progress + "%"; } function showLoading(customText = null) { document.getElementById("loadingOverlay").classList.remove("hidden"); document.getElementById("progressBar").style.width = "0%"; document.getElementById("progressLabel").textContent = "0%"; document.getElementById("progressContainer").classList.add("hidden"); const loadingText = document.querySelector("#loadingOverlay p"); if (loadingText) { loadingText.innerHTML = customText ?? `Starting ${chosenAlgorithmName}...
This may take a while.`; } } function hideLoading() { document.getElementById("loadingOverlay").classList.add("hidden"); } function showErrorPopup(message) { const popup = document.createElement("div"); popup.innerHTML = `

Error

${message}

`; document.body.appendChild(popup); document.getElementById("closeError").onclick = () => popup.remove(); } function fetchFinalResults(retryCount = 0) { fetch(getResultURL()) .then(res => res.json()) .then(json => { if (!json.ready) { if (retryCount < 10) { setTimeout(() => fetchFinalResults(retryCount + 1), 1000); return; } hideLoading(); clearActiveJob(); showErrorPopup("Final results are not ready."); return; } hideLoading(); clearActiveJob(); lastResults = json.data; if (json.data?.error) { showErrorPopup(`Algorithm failed.
${json.data.message}`); return; } displayResults(json.data); }) .catch(() => { hideLoading(); clearActiveJob(); showErrorPopup("Error fetching final results."); }); } function startProgressPolling() { if (progressInterval) { clearInterval(progressInterval); } document.getElementById("progressContainer").classList.remove("hidden"); progressInterval = setInterval(async () => { try { const res = await fetch("get-status"); if (!res.ok) { throw new Error("Backend status fetch failed"); } const data = await res.json(); updateProgressUI(data.progress || 0); if (!data.running) { clearInterval(progressInterval); progressInterval = null; fetchFinalResults(); } } catch { clearInterval(progressInterval); progressInterval = null; hideLoading(); clearActiveJob(); if (!backendErrorShown) { backendErrorShown = true; showErrorPopup("Connection to backend was lost.
Backend may have stopped."); } } }, 500); } async function restoreRunningJob() { const saved = loadActiveJob(); if (!saved) return; try { const res = await fetch("get-status"); const status = await res.json(); applySavedAlgorithm(saved); if (status.current_job !== saved.backendJob) { try { const resultRes = await fetch(getResultURL()); const resultJson = await resultRes.json(); if (resultJson.ready) { hideLoading(); clearActiveJob(); lastResults = resultJson.data; if (resultJson.data?.error) { showErrorPopup(`Algorithm failed.
${resultJson.data.message}`); return; } displayResults(resultJson.data); return; } } catch { } clearActiveJob(); return; } showLoading(`Resuming ${chosenAlgorithmName}...
This may take a while.`); updateProgressUI(status.progress || 0); if (status.running) { startProgressPolling(); } else { fetchFinalResults(); } } catch { hideLoading(); clearActiveJob(); if (!backendErrorShown) { backendErrorShown = true; showErrorPopup("Connection to backend was lost.
Please start the backend and try again."); } } } startBtn.addEventListener("click", () => { if (!chosenAlgorithm) return; showLoading(`Loading prepared ${chosenAlgorithmName} results...
This may take a moment.`); backendErrorShown = false; saveActiveJob(); let url = chosenAlgorithm === "isolation_forest" ? "start-iforest" : "start-autoencoder"; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 10000); fetch(url, { method: "POST", signal: controller.signal }) .then(async res => { clearTimeout(timeout); if (res.status === 409) { hideLoading(); clearActiveJob(); showErrorPopup("Another algorithm is already running.
Please wait until it finishes."); return; } if (!res.ok) { throw new Error("Server error"); } await res.json(); const saved = loadActiveJob(); startTime = saved?.startedAt ?? Date.now(); startProgressPolling(); }) .catch(err => { hideLoading(); clearActiveJob(); console.error(err); showErrorPopup("Backend server is not running.
Start app.py and try again."); }); }); window.addEventListener("load", restoreRunningJob); function displayResults(data) { const container = document.getElementById("resultContainer"); container.classList.remove("hidden"); const runtime = data.runtime !== undefined && data.runtime !== null ? Number(data.runtime).toFixed(2) : ((Date.now() - startTime) / 1000).toFixed(2); let topAnomaliesHTML = ""; if (data.top_anomalies && data.top_anomalies.length > 0) { const headers = Object.keys(data.top_anomalies[0]); topAnomaliesHTML = `

Top 5 detected anomalies

${headers.map(h => ``).join("")} ${data.top_anomalies.map(row => ` ${headers.map(key => { const val = row[key]; return ``; }).join("")} `).join("")}
${h}
${typeof val === "number" ? val.toFixed(4) : val}
`; } container.innerHTML = `

Results

Class Precision Recall F1-Score
Normal traffic ${data.precision_normal.toFixed(4)} ${data.recall_normal.toFixed(4)} ${data.f1_normal.toFixed(4)}
Anomaly ${data.precision_attack.toFixed(4)} ${data.recall_attack.toFixed(4)} ${data.f1_attack.toFixed(4)}
Overall accuracy - - ${data.accuracy.toFixed(4)}

Class distribution

Performance

Normal traffic: ${Math.round(data.normal_count)}
Anomalies: ${Math.round(data.attack_count)}
Runtime: ${runtime} s
RAM before: ${data.ram_before} MB
RAM peak: ${data.ram_peak} MB
RAM after: ${data.ram_after} MB
RAM increase: +${data.ram_increase} MB
${topAnomaliesHTML}
`; loadPieChart(data); document.getElementById("downloadPDF").addEventListener("click", () => { downloadPDF(data, runtime); }); document.getElementById("downloadJSON").addEventListener("click", () => { downloadJSON(data, runtime); }); } function downloadPDF(results, runtime) { fetch("download-pdf", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ algorithm: chosenAlgorithmName, results: results, runtime: runtime }) }) .then(response => response.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "report.pdf"; link.click(); }); } function downloadJSON(results, runtime) { fetch("download-json", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ algorithm: chosenAlgorithmName, results: results, runtime: runtime }) }) .then(res => res.blob()) .then(blob => { const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = "report.json"; link.click(); }); } function loadPieChart(data) { const ctx = document.getElementById("pieChart"); new Chart(ctx, { type: "pie", data: { labels: ["Normal Traffic", "Attacks"], datasets: [{ data: [data.normal_count || 0, data.attack_count || 0], backgroundColor: ["#14b8a6", "#dc2626"] }] }, options: { plugins: { legend: { labels: { color: "#ffffff" } } } } }); }