const state = { dashboard: null, }; async function api(path, options = {}) { const response = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options, }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || "Request failed"); } return data; } function formatDate(value) { if (!value) return "—"; return value.replace("T", " ").replace("+00:00", " UTC"); } function setBusy(button, busy) { if (!button) return; button.disabled = busy; } function renderAuth(auth) { document.getElementById("auth-status").textContent = auth.status; document.getElementById("auth-details").textContent = auth.details || ""; } function renderSummary(data) { document.getElementById("channel-count").textContent = String(data.state.channel_count); document.getElementById("forwarding-count").textContent = String(data.state.forwarding_rules.length); const toggle = document.getElementById("scrape-media-toggle"); toggle.checked = Boolean(data.state.scrape_media); document.getElementById("scrape-media-label").textContent = toggle.checked ? "ON" : "OFF"; } function renderChannels(channels) { const tbody = document.getElementById("channels-table"); tbody.innerHTML = ""; const template = document.getElementById("channel-row-template"); channels.forEach((channel) => { const node = template.content.firstElementChild.cloneNode(true); node.querySelector(".channel-name").textContent = channel.name; node.querySelector(".channel-id").textContent = channel.channel_id; node.querySelector(".message-count").textContent = String(channel.message_count); node.querySelector(".media-count").textContent = String(channel.media_count); node.querySelector(".last-date").textContent = channel.last_date || "—"; node.querySelector(".scrape-btn").addEventListener("click", async () => { await api("/api/jobs/scrape", { method: "POST", body: JSON.stringify({ channel_id: channel.channel_id }), }); await refreshDashboard(); }); node.querySelector(".export-view-btn").addEventListener("click", () => { window.location.href = `/viewer?channel=${encodeURIComponent(channel.channel_id)}`; }); node.querySelector(".media-btn").addEventListener("click", async () => { await api("/api/jobs/rescrape-media", { method: "POST", body: JSON.stringify({ channel_id: channel.channel_id }), }); await refreshDashboard(); }); node.querySelector(".remove-btn").addEventListener("click", async () => { await api("/api/channels/remove", { method: "POST", body: JSON.stringify({ channel_id: channel.channel_id }), }); await refreshDashboard(); }); tbody.appendChild(node); }); } function renderJobs(jobs) { const root = document.getElementById("jobs-list"); root.innerHTML = ""; const template = document.getElementById("job-template"); if (!jobs.length) { root.innerHTML = '