Add live visitor monitoring

This commit is contained in:
rajchales-monito
2026-07-24 11:28:03 +02:00
parent 6c7c2b2b43
commit c72e6f3ed1
5 changed files with 280 additions and 2 deletions
+67 -1
View File
@@ -2,6 +2,7 @@ const state = {
site: null,
snippet: "",
conversations: [],
visitors: [],
activeId: null,
active: null,
events: null,
@@ -202,7 +203,7 @@ async function showApp() {
$("#loginView").classList.add("hidden");
$("#appView").classList.remove("hidden");
await loadBootstrap();
await loadConversations();
await Promise.all([loadConversations(), loadVisitors()]);
connectEvents();
}
@@ -224,6 +225,12 @@ async function loadConversations() {
renderConversations();
}
async function loadVisitors() {
const data = await api("/api/admin/visitors");
state.visitors = data.visitors;
renderVisitors();
}
async function openConversation(id) {
stopAttention();
state.activeId = id;
@@ -247,6 +254,11 @@ function connectEvents() {
renderSite();
return;
}
if (data.type === "visitors:update") {
state.visitors = data.visitors;
renderVisitors();
return;
}
await loadConversations();
if (data.type === "conversation:delete" && data.conversationId === state.activeId) {
clearActiveConversation();
@@ -305,6 +317,60 @@ function renderConversations() {
`).join("") : `<p class="empty-list">Zadny chat pro vybrany filtr.</p>`;
}
function renderVisitors() {
$("#visitorCount").textContent = `${state.visitors.length} online`;
$("#visitorList").innerHTML = state.visitors.length ? state.visitors.map((visitor) => `
<article class="visitor-card">
<div class="visitor-card-head">
<span class="visitor-avatar">${escapeHtml(visitor.initials || "??")}</span>
<div>
<strong>${escapeHtml(visitor.label || "Navstevnik")}</strong>
<small>${escapeHtml(browserLabel(visitor.device))}</small>
</div>
<span class="online-dot" title="Online"></span>
</div>
<dl>
<dt>Zeme</dt>
<dd>${visitor.countryFlag || ""} ${escapeHtml(visitor.countryCode || "?")}</dd>
<dt>Stranka</dt>
<dd><a href="${escapeHtml(visitor.currentUrl || "#")}" target="_blank" rel="noreferrer">${escapeHtml(pageTitle(visitor))}</a></dd>
<dt>Prisiel z</dt>
<dd>${visitor.referrer ? `<a href="${escapeHtml(visitor.referrer)}" target="_blank" rel="noreferrer">${escapeHtml(referrerLabel(visitor.referrer))}</a>` : "primy vstup"}</dd>
<dt>Cas</dt>
<dd>${formatDuration(visitor.sessionSeconds)}</dd>
<dt>Navstevy</dt>
<dd>${Number(visitor.visitCount || visitor.pageCount || 1)}</dd>
<dt>Chaty</dt>
<dd>${Number(visitor.conversationCount || 0)}</dd>
</dl>
</article>
`).join("") : `<p class="empty-list">Zatim nikdo online.</p>`;
}
function pageTitle(visitor) {
const history = Array.isArray(visitor.browsingHistory) ? visitor.browsingHistory : [];
const current = history.find((item) => item.url === visitor.currentUrl) || history[history.length - 1];
return current?.title || shortUrl(visitor.currentUrl) || "neznamá stranka";
}
function referrerLabel(value) {
try {
return new URL(value).hostname.replace(/^www\./, "");
} catch {
return shortUrl(value) || value;
}
}
function browserLabel(device = {}) {
const ua = String(device.userAgent || "");
const browser = ua.includes("Edg/") ? "Edge"
: ua.includes("Chrome/") ? "Chrome"
: ua.includes("Firefox/") ? "Firefox"
: ua.includes("Safari/") ? "Safari"
: "Browser";
return [device.platform || "zarizeni", browser].filter(Boolean).join(" - ");
}
function renderActive() {
const { conversation, messages } = state.active;
$("#conversationHeader").innerHTML = `