const toast = (typeof window !== "undefined" && window.createToast) || (typeof window !== "undefined" && window.showToast) || null; const tableBody = document.getElementById("proxyTableBody"); const addProxyBtn = document.getElementById("addProxyBtn"); const addProxyModal = document.getElementById("addProxyModal"); const modalClientSelect = document.getElementById("modalClientSelect"); const modalPortInput = document.getElementById("modalPortInput"); const modalCancelBtn = document.getElementById("modalCancelBtn"); const modalAddBtn = document.getElementById("modalAddBtn"); let pollingInterval = null; // Check if a clientId was provided via URL params (from context menu) const urlParams = new URLSearchParams(window.location.search); const preselectedClientId = urlParams.get("clientId") || ""; // ── Data fetching ───────────────────────────────────────────────────────────── async function fetchProxies() { try { const res = await fetch("/api/proxy/list"); if (!res.ok) return []; const data = await res.json(); return data.proxies || []; } catch { return []; } } async function fetchClients() { try { const res = await fetch("/api/clients?page=1&pageSize=500"); if (!res.ok) return []; const data = await res.json(); return (data.items || []).filter((c) => c.online); } catch { return []; } } // ── Rendering ───────────────────────────────────────────────────────────────── function formatTime(ts) { if (!ts) return "-"; const d = new Date(ts); return d.toLocaleString(); } function renderTable(proxies) { if (!proxies.length) { tableBody.innerHTML = `

No active proxies

Click "Add Proxy" to start routing traffic through a client.

`; return; } tableBody.innerHTML = proxies .map( (p) => `
${escapeHtml(p.clientId)}
${p.port} Active ${p.connections} socks5://<server>:${p.port} ${formatTime(p.createdAt)} `, ) .join(""); // wire up stop buttons tableBody.querySelectorAll(".stop-btn").forEach((btn) => { btn.addEventListener("click", () => { const port = parseInt(btn.dataset.port); stopProxy(port); }); }); } function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str; return div.innerHTML.replace(/"/g, """).replace(/'/g, "'"); } // ── Actions ─────────────────────────────────────────────────────────────────── async function startProxy(clientId, port) { try { const res = await fetch("/api/proxy/start", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, port }), }); const data = await res.json(); if (data.ok) { if (toast) toast(`Proxy started on port ${port}`, "success"); } else { if (toast) toast(data.message || "Failed to start proxy", "error"); } refresh(); } catch (err) { if (toast) toast("Network error", "error"); } } async function stopProxy(port) { try { const res = await fetch("/api/proxy/stop", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ port }), }); const data = await res.json(); if (data.ok) { if (toast) toast(`Proxy on port ${port} stopped`, "success"); } else { if (toast) toast(data.message || "Failed to stop proxy", "error"); } refresh(); } catch (err) { if (toast) toast("Network error", "error"); } } async function refresh() { const proxies = await fetchProxies(); renderTable(proxies); } // ── Modal ───────────────────────────────────────────────────────────────────── function showModal() { addProxyModal.classList.remove("hidden"); loadClientsForModal(); } function hideModal() { addProxyModal.classList.add("hidden"); } async function loadClientsForModal() { modalClientSelect.innerHTML = ''; const clients = await fetchClients(); if (!clients.length) { modalClientSelect.innerHTML = ''; return; } modalClientSelect.innerHTML = clients .map((c) => { const label = [c.nickname, c.host, c.user, c.id] .filter(Boolean) .join(" | "); const selected = c.id === preselectedClientId ? " selected" : ""; return ``; }) .join(""); } addProxyBtn.addEventListener("click", showModal); modalCancelBtn.addEventListener("click", hideModal); addProxyModal.addEventListener("click", (e) => { if (e.target === addProxyModal) hideModal(); }); modalAddBtn.addEventListener("click", () => { const clientId = modalClientSelect.value; const port = parseInt(modalPortInput.value); if (!clientId) { if (toast) toast("Select a client", "error"); return; } if (isNaN(port) || port < 1 || port > 65535) { if (toast) toast("Invalid port (1-65535)", "error"); return; } hideModal(); startProxy(clientId, port); }); // ── Init ────────────────────────────────────────────────────────────────────── refresh(); pollingInterval = setInterval(refresh, 5000); // Auto-open modal if a clientId was provided via URL if (preselectedClientId) { showModal(); }