Files
parse_link_vpn/public/app.js
2026-05-18 19:29:35 +05:00

207 lines
5.5 KiB
JavaScript

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));