import { api, toast, fmtRelative } from "/api/monitoring-tg/static/js/api.js"; import { isAdmin } from "/api/monitoring-tg/static/js/access.js"; import { getVertical, getSection, sectionBase, VERTICAL_META } from "/api/monitoring-tg/static/js/vertical.js"; const V = getVertical(); const section = getSection(); const sBase = sectionBase(); const meta = VERTICAL_META[V]; function escape(s) { if (s == null) return ""; return String(s).replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); } async function loadStats() { const [stats, llm, queue] = await Promise.all([ api.globalStats(), api.llmStatus().catch(() => ({ enabled: false, ready: false, model: "—" })), api.llmQueue().catch(() => ({ pending: null })), ]); const grid = document.getElementById("stats"); const llmBadge = llm.enabled ? (llm.ready ? `ready` : `загружается`) : `off`; const queueValue = queue.pending == null ? "—" : queue.pending.toLocaleString(); grid.innerHTML = `
Каналы
${stats.channels_active} / ${stats.channels_total}
Сообщений всего
${stats.messages_total.toLocaleString()}
Сообщений за 24ч
${stats.messages_last_24h.toLocaleString()}
🎯 Лидов всего
${(stats.leads_total ?? 0).toLocaleString()}
🎯 Лидов за 24ч
${(stats.leads_last_24h ?? 0).toLocaleString()}
⏳ В очереди ИИ
${queueValue}
Период опроса
${stats.poll_interval_seconds}s
Последний опрос
${fmtRelative(stats.last_poll_at)}
Локальный ИИ
${llmBadge}
${escape(llm.model || "")}
`; } async function loadChannels() { const channels = await api.listChannels(); const tbody = document.getElementById("channels-tbody"); if (!channels.length) { tbody.innerHTML = `Каналов в этом подразделе пока нет — добавьте их на странице Каналы`; return; } const stats = await Promise.all(channels.map(c => api.channelStats(c.id).catch(() => null))); tbody.innerHTML = channels.map((c, i) => { const s = stats[i] || {}; return `
${escape(c.title || c.identifier)}
${escape(c.identifier)}
${(s.message_count ?? 0).toLocaleString()} ${fmtRelative(s.last_message_at)} ${fmtRelative(c.last_polled_at)} ${c.is_active ? 'on' : 'off'} `; }).join(""); } document.getElementById("poll-all").addEventListener("click", async (e) => { e.target.disabled = true; try { const res = await api.pollAll(); const scope = section ? `${meta.short} / ${section}` : meta.short; toast(`В очереди ${res.queued ?? 0} каналов (${scope}) — опрос идёт в фоне`, "success"); await loadAll(); } catch (err) { toast(err.message, "error"); } finally { e.target.disabled = false; } }); async function loadAll() { try { document.getElementById("poll-all").hidden = !(await isAdmin()); await Promise.all([loadStats(), loadChannels()]); } catch (err) { toast(err.message, "error"); } } loadAll(); setInterval(loadAll, 15000);