const state = { dashboard: null, auth: null, continuous: 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 toggleHidden(id, hidden) { document.getElementById(id).classList.toggle("hidden", hidden); } function renderAuthPanel(authData) { state.auth = authData; renderAuth(authData.auth_status); const apiId = authData.saved_credentials?.api_id ?? ""; document.getElementById("api-id-input").value = apiId || ""; document.getElementById("api-hash-input").placeholder = authData.saved_credentials?.api_hash_present ? "API Hash сохранён" : "API Hash"; const showQr = Boolean(authData.qr_image); toggleHidden("qr-wrap", !showQr); if (showQr) { document.getElementById("qr-image").src = authData.qr_image; } toggleHidden("code-form", authData.phase !== "code_required"); toggleHidden("password-form", authData.phase !== "password_required"); } 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 = '
Пока задач нет.
'; return; } jobs.forEach((job) => { const node = template.content.firstElementChild.cloneNode(true); node.querySelector(".job-title").textContent = job.title; node.querySelector(".job-status").textContent = job.status; node.querySelector(".job-time").textContent = [job.started_at, job.finished_at].filter(Boolean).join(" -> "); node.querySelector(".job-logs").textContent = job.logs || job.error || "Без логов"; root.appendChild(node); }); } function renderContinuous(data) { state.continuous = data; const config = data.config || {}; const status = data.status || {}; document.getElementById("continuous-enabled").checked = Boolean(config.enabled); document.getElementById("continuous-interval").value = config.interval_minutes || 1; document.getElementById("continuous-all").checked = Boolean(config.run_all_tracked); document.getElementById("continuous-channels").value = (config.channels || []).join(", "); document.getElementById("continuous-status").textContent = status.running ? "running" : "stopped"; document.getElementById("continuous-last-iteration").textContent = status.last_iteration_at || "—"; document.getElementById("continuous-last-error").textContent = status.last_error || "—"; document.getElementById("continuous-logs").textContent = (status.logs || []).join("\n") || "Логов пока нет."; } async function refreshDashboard() { const data = await api("/api/dashboard"); const authData = await api("/api/auth"); const continuousData = await api("/api/continuous"); state.dashboard = data; renderAuthPanel(authData); renderSummary(data); renderChannels(data.channels); renderJobs(data.jobs); renderContinuous(continuousData); } async function main() { document.getElementById("scrape-all-btn").addEventListener("click", async (event) => { setBusy(event.currentTarget, true); try { await api("/api/jobs/scrape", { method: "POST", body: JSON.stringify({}) }); await refreshDashboard(); } finally { setBusy(event.currentTarget, false); } }); document.getElementById("export-all-btn").addEventListener("click", async (event) => { setBusy(event.currentTarget, true); try { await api("/api/jobs/export", { method: "POST", body: JSON.stringify({}) }); await refreshDashboard(); } finally { setBusy(event.currentTarget, false); } }); document.getElementById("refresh-dialogs-btn").addEventListener("click", async (event) => { setBusy(event.currentTarget, true); try { await api("/api/jobs/refresh-dialogs", { method: "POST", body: JSON.stringify({}) }); await refreshDashboard(); } finally { setBusy(event.currentTarget, false); } }); document.getElementById("refresh-jobs-btn").addEventListener("click", refreshDashboard); document.getElementById("scrape-media-toggle").addEventListener("change", async (event) => { const checked = event.currentTarget.checked; document.getElementById("scrape-media-label").textContent = checked ? "ON" : "OFF"; await api("/api/settings/media", { method: "POST", body: JSON.stringify({ value: checked }), }); await refreshDashboard(); }); document.getElementById("add-channel-form").addEventListener("submit", async (event) => { event.preventDefault(); const channelId = document.getElementById("add-channel-id").value.trim(); const name = document.getElementById("add-channel-name").value.trim(); if (!channelId) return; await api("/api/channels/add", { method: "POST", body: JSON.stringify({ channel_id: channelId, name }), }); event.currentTarget.reset(); await refreshDashboard(); }); document.getElementById("credentials-form").addEventListener("submit", async (event) => { event.preventDefault(); const apiId = document.getElementById("api-id-input").value.trim(); const apiHash = document.getElementById("api-hash-input").value.trim(); await api("/api/auth/credentials", { method: "POST", body: JSON.stringify({ api_id: apiId, api_hash: apiHash }), }); await refreshDashboard(); }); document.getElementById("start-qr-btn").addEventListener("click", async (event) => { setBusy(event.currentTarget, true); try { await api("/api/auth/qr/start", { method: "POST", body: JSON.stringify({}), }); await refreshDashboard(); } finally { setBusy(event.currentTarget, false); } }); document.getElementById("phone-form").addEventListener("submit", async (event) => { event.preventDefault(); const phone = document.getElementById("phone-input").value.trim(); await api("/api/auth/phone/request", { method: "POST", body: JSON.stringify({ phone }), }); await refreshDashboard(); }); document.getElementById("code-form").addEventListener("submit", async (event) => { event.preventDefault(); const code = document.getElementById("code-input").value.trim(); await api("/api/auth/phone/submit", { method: "POST", body: JSON.stringify({ code }), }); event.currentTarget.reset(); await refreshDashboard(); }); document.getElementById("password-form").addEventListener("submit", async (event) => { event.preventDefault(); const password = document.getElementById("password-input").value.trim(); await api("/api/auth/password", { method: "POST", body: JSON.stringify({ password }), }); event.currentTarget.reset(); await refreshDashboard(); }); document.getElementById("continuous-form").addEventListener("submit", async (event) => { event.preventDefault(); const enabled = document.getElementById("continuous-enabled").checked; const intervalMinutes = document.getElementById("continuous-interval").value; const runAllTracked = document.getElementById("continuous-all").checked; const channelsRaw = document.getElementById("continuous-channels").value.trim(); const channels = channelsRaw ? channelsRaw.split(",").map((item) => item.trim()).filter(Boolean) : []; await api("/api/continuous", { method: "POST", body: JSON.stringify({ enabled, interval_minutes: intervalMinutes, run_all_tracked: runAllTracked, channels, }), }); await refreshDashboard(); }); await refreshDashboard(); window.setInterval(refreshDashboard, 5000); } main().catch((error) => { console.error(error); alert(error.message); });