const btnIF = document.getElementById("btnIF"); const btnAE = document.getElementById("btnAE"); const startBtn = document.getElementById("startBtn"); let chosenAlgorithm = null; let chosenAlgorithmName = null; let startTime = null; let lastResults = null; let progressInterval = null; let backendErrorShown = false; const JOB_STORAGE_KEY = "custom_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-600", "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 = "iforest_custom"; chosenAlgorithmName = "Isolation Forest (Custom)"; enableStartButton(); }); btnAE.addEventListener("click", () => { selectAlgorithm(btnAE, btnIF); chosenAlgorithm = "autoencoder_custom"; chosenAlgorithmName = "Autoencoder (Custom)"; enableStartButton(); }); function getBackendJobName() { return chosenAlgorithm; } function getResultURL() { if (chosenAlgorithm === "iforest_custom") { return "/get-isolation-forest-result-custom"; } return "/get-autoencoder-result-custom"; } 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 === "iforest_custom") { selectAlgorithm(btnIF, btnAE); } else if (chosenAlgorithm === "autoencoder_custom") { selectAlgorithm(btnAE, btnIF); } enableStartButton(); } function restoreCustomRunLayout() { const step1 = document.getElementById("step-1"); const step2 = document.getElementById("step-2"); const step3 = document.getElementById("step-3"); const step4 = document.getElementById("step-4"); const step5 = document.getElementById("step-5"); if (step1) step1.classList.add("hidden"); if (step2) step2.classList.add("hidden"); if (step3) step3.classList.add("hidden"); if (step4) step4.classList.add("hidden"); if (step5) step5.classList.remove("hidden"); } 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.getElementById("loadingText"); 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(); } const originalFetch = window.fetch.bind(window); window.fetch = async function (...args) { const res = await originalFetch(...args); let url = ""; const req = args[0]; if (typeof req === "string") { url = req; } else if (req && typeof req.url === "string") { url = req.url; } if (res.status === 409) { let message = "Another operation is already running."; try { const data = await res.clone().json(); if (data?.error) { message = data.error; } else if (data?.message) { message = data.message; } } catch (_) {} if (url.includes("/upload-dataset")) { showErrorPopup(message || "Cannot upload a new dataset while an algorithm is running."); } else if (url.includes("/save-config")) { showErrorPopup(message || "Cannot save config while an algorithm is running."); } } return res; }; 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(); restoreCustomRunLayout(); 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 (err) { clearInterval(progressInterval); progressInterval = null; hideLoading(); clearActiveJob(); if (!backendErrorShown) { backendErrorShown = true; showErrorPopup("Connection to backend was lost.
Backend may have stopped."); } } }, 1500); } async function restoreRunningJob() { const saved = loadActiveJob(); if (!saved) return; try { const res = await fetch("/get-status"); const status = await res.json(); if (status.current_job !== saved.backendJob) { clearActiveJob(); return; } applySavedAlgorithm(saved); restoreCustomRunLayout(); showLoading(`Resuming ${chosenAlgorithmName}...
This may take a while.`); updateProgressUI(status.progress || 0); if (status.running) { startProgressPolling(); } else { fetchFinalResults(); } } catch (err) { 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(); backendErrorShown = false; saveActiveJob(); let url; if (chosenAlgorithm === "iforest_custom") { url = "/start-iforest-custom"; } else if (chosenAlgorithm === "autoencoder_custom") { url = "/start-autoencoder-custom"; } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 60000); 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"); } 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 = ((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 || 0)}
Anomalies: ${Math.round(data.attack_count || 0)}
Runtime: ${runtime} s
RAM before: ${data.ram_before || 0} MB
RAM peak: ${data.ram_peak || 0} MB
RAM after: ${data.ram_after || 0} MB
RAM increase: +${data.ram_increase || 0} 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" } } } } }); }