Add monitoring TG service
This commit is contained in:
87
src/parser_bot/web/static/js/dashboard.js
Normal file
87
src/parser_bot/web/static/js/dashboard.js
Normal file
@@ -0,0 +1,87 @@
|
||||
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 ? `<span class="badge ok">ready</span>` : `<span class="badge warn">загружается</span>`)
|
||||
: `<span class="badge off">off</span>`;
|
||||
const queueValue = queue.pending == null ? "—" : queue.pending.toLocaleString();
|
||||
grid.innerHTML = `
|
||||
<div class="card stat"><div class="label">Каналы</div><div class="value">${stats.channels_active} / ${stats.channels_total}</div></div>
|
||||
<div class="card stat"><div class="label">Сообщений всего</div><div class="value">${stats.messages_total.toLocaleString()}</div></div>
|
||||
<div class="card stat"><div class="label">Сообщений за 24ч</div><div class="value">${stats.messages_last_24h.toLocaleString()}</div></div>
|
||||
<div class="card stat"><div class="label">🎯 Лидов всего</div><div class="value">${(stats.leads_total ?? 0).toLocaleString()}</div></div>
|
||||
<div class="card stat"><div class="label">🎯 Лидов за 24ч</div><div class="value"><a href="${sBase}/messages.html?leads_only=true">${(stats.leads_last_24h ?? 0).toLocaleString()}</a></div></div>
|
||||
<div class="card stat"><div class="label">⏳ В очереди ИИ</div><div class="value">${queueValue}</div></div>
|
||||
<div class="card stat"><div class="label">Период опроса</div><div class="value">${stats.poll_interval_seconds}s</div></div>
|
||||
<div class="card stat"><div class="label">Последний опрос</div><div class="value">${fmtRelative(stats.last_poll_at)}</div></div>
|
||||
<div class="card stat"><div class="label">Локальный ИИ</div><div class="value" style="font-size:14px">${llmBadge}<div class="muted mono" style="font-size:11px;margin-top:4px">${escape(llm.model || "")}</div></div></div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function loadChannels() {
|
||||
const channels = await api.listChannels();
|
||||
const tbody = document.getElementById("channels-tbody");
|
||||
if (!channels.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="empty">Каналов в этом подразделе пока нет — добавьте их на странице <a href="${sBase}/channels.html">Каналы</a></td></tr>`;
|
||||
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 `
|
||||
<tr>
|
||||
<td>
|
||||
<div><a href="${sBase}/messages.html?channel_id=${c.id}">${escape(c.title || c.identifier)}</a></div>
|
||||
<div class="muted mono" style="font-size:12px">${escape(c.identifier)}</div>
|
||||
</td>
|
||||
<td>${(s.message_count ?? 0).toLocaleString()}</td>
|
||||
<td>${fmtRelative(s.last_message_at)}</td>
|
||||
<td>${fmtRelative(c.last_polled_at)}</td>
|
||||
<td>${c.is_active ? '<span class="badge ok">on</span>' : '<span class="badge off">off</span>'}</td>
|
||||
</tr>`;
|
||||
}).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);
|
||||
Reference in New Issue
Block a user