const $ = (sel) => document.querySelector(sel); const loginScreen = $("#login-screen"); const dashboardScreen = $("#dashboard-screen"); const loginForm = $("#login-form"); const loginError = $("#login-error"); const logoutBtn = $("#logout-btn"); const novavpsUrlInput = $("#novavps-url"); const saveConfigBtn = $("#save-config-btn"); const configMsg = $("#config-msg"); const runBtn = $("#run-btn"); const runMsg = $("#run-msg"); const runningIndicator = $("#running-indicator"); const currentStep = $("#current-step"); const lastRunEl = $("#last-run"); const POLL_INTERVAL = 10000; let pollTimer = null; function showScreen(screen) { loginScreen.classList.add("hidden"); dashboardScreen.classList.add("hidden"); screen.classList.remove("hidden"); } function formatTime(iso) { if (!iso) return ""; const d = new Date(iso); return d.toLocaleString(); } function updatePanelStatus(prefix, data) { const badge = $(`#${prefix}-status`); const detail = $(`#${prefix}-detail`); const time = $(`#${prefix}-time`); badge.textContent = data.status; badge.className = "status-badge " + data.status; let detailText = ""; if (prefix === "novavps" && data.count != null) { detailText = `${data.count} connections`; } else if (data.outboundsCount != null) { detailText = `${data.outboundsCount} outbounds`; if (data.synced) detailText += " (synced)"; } if (data.error) detailText += (detailText ? " | " : "") + data.error; detail.textContent = detailText; time.textContent = formatTime(data.timestamp); } async function fetchStatus() { try { const res = await fetch("/api/status"); if (res.status === 401) { showScreen(loginScreen); stopPolling(); return; } const data = await res.json(); const isRunning = data.status === "running"; runBtn.disabled = isRunning; runningIndicator.classList.toggle("hidden", !isRunning); const stepLabels = { initializing: "Initializing...", novavps_fetching: "Fetching Novavps data...", xray1_parsing: "Parsing Xray 1...", xray1_syncing: "Syncing Xray 1...", xray2_parsing: "Parsing Xray 2...", xray2_syncing: "Syncing Xray 2...", done: "Completed", novavps_url_missing: "Error: NOVAVPS URL not set", no_xray_panels: "Error: No Xray panels configured", fatal: "Fatal error", }; currentStep.textContent = stepLabels[data.currentStep] || data.currentStep; updatePanelStatus("novavps", data.novavps); updatePanelStatus("xray1", data.xray1); updatePanelStatus("xray2", data.xray2); lastRunEl.textContent = data.lastRun ? formatTime(data.lastRun) : "Never"; } catch (err) { console.error("Status fetch error:", err); } } async function fetchConfig() { try { const res = await fetch("/api/config"); if (res.status === 401) { showScreen(loginScreen); stopPolling(); return; } const data = await res.json(); novavpsUrlInput.value = data.novavpsUrl || ""; } catch (err) { console.error("Config fetch error:", err); } } function startPolling() { fetchStatus(); fetchConfig(); if (pollTimer) clearInterval(pollTimer); pollTimer = setInterval(fetchStatus, POLL_INTERVAL); } function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } function showMsg(el, text, type) { el.textContent = text; el.className = "msg " + type; el.classList.remove("hidden"); setTimeout(() => el.classList.add("hidden"), 5000); } loginForm.addEventListener("submit", async (e) => { e.preventDefault(); loginError.classList.add("hidden"); const username = $("#username").value; const password = $("#password").value; try { const res = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); const data = await res.json(); if (!res.ok) { loginError.textContent = data.error || "Login failed"; loginError.classList.remove("hidden"); return; } showScreen(dashboardScreen); startPolling(); } catch (err) { loginError.textContent = "Network error"; loginError.classList.remove("hidden"); } }); logoutBtn.addEventListener("click", async () => { try { await fetch("/api/logout", { method: "POST" }); } catch {} stopPolling(); showScreen(loginScreen); }); saveConfigBtn.addEventListener("click", async () => { try { const res = await fetch("/api/config", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ novavpsUrl: novavpsUrlInput.value }), }); const data = await res.json(); if (!res.ok) { showMsg(configMsg, data.error || "Save failed", "error"); return; } showMsg(configMsg, "Saved", "success"); } catch (err) { showMsg(configMsg, "Network error", "error"); } }); runBtn.addEventListener("click", async () => { runMsg.classList.add("hidden"); try { const res = await fetch("/api/run", { method: "POST" }); const data = await res.json(); if (!res.ok) { showMsg(runMsg, data.error || "Failed to start", "error"); return; } showMsg(runMsg, "Parser started", "success"); fetchStatus(); } catch (err) { showMsg(runMsg, "Network error", "error"); } }); // Check if already logged in fetch("/api/status") .then((res) => { if (res.ok) { showScreen(dashboardScreen); startPolling(); } else { showScreen(loginScreen); } }) .catch(() => showScreen(loginScreen));