Files
foodlinkk-command-center/cockpit/static/js/live-pulse.js
T
Aissa 4dd1b8265f Add live CEO briefing, Wereldexport CRM, halal engine, and realtime cockpit.
Ship export intel with contact filters and CRM push, Herman live digest with Telegram briefing, halal recommendation engine, revenue cockpit, and dashboard polling/WebSocket fixes.
2026-07-19 18:25:21 +00:00

77 lines
2.6 KiB
JavaScript

window.CockpitLive = (function () {
var ws = null;
var reconnectTimer = null;
function startPolling(fn, intervalMs) {
intervalMs = intervalMs || 30000;
fn();
var id = setInterval(fn, intervalMs);
return function () { clearInterval(id); };
}
function eventFingerprint(data) {
var ev = (data && data.events) || [];
if (!ev.length) return '';
return ev.slice(0, 5).map(function (e) {
return String(e.created_at || '') + '#' + String(e.agent_name || '') + '#' + String(e.title || e.event_type || '');
}).join('|');
}
function connectFeed(onMessage) {
if (typeof WebSocket === 'undefined') return function () {};
var proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
var lastFp = '';
var booted = false;
function connect() {
try {
ws = new WebSocket(proto + '//' + location.host + '/ws/feed');
ws.onmessage = function (ev) {
try {
var data = JSON.parse(ev.data);
var fp = eventFingerprint(data);
if (!booted) {
booted = true;
lastFp = fp;
if (onMessage) onMessage(data, { reason: 'connect' });
return;
}
if (fp && fp !== lastFp) {
lastFp = fp;
if (onMessage) onMessage(data, { reason: 'events' });
}
} catch (e) {}
};
ws.onclose = function () {
booted = false;
lastFp = '';
reconnectTimer = setTimeout(connect, 5000);
};
} catch (e) {
reconnectTimer = setTimeout(connect, 5000);
}
}
connect();
return function () {
if (reconnectTimer) clearTimeout(reconnectTimer);
if (ws) { try { ws.close(); } catch (e) {} ws = null; }
};
}
function updateLiveBadge(el, at) {
if (!el) return;
el.textContent = 'Live · ' + (at || new Date().toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' }));
}
function renderAgentFeed(container, events) {
if (!container || !events) return;
container.innerHTML = events.slice(0, 12).map(function (ev) {
var t = (ev.created_at || '').substring(11, 16);
return '<div class="hub-feed-item"><span>' + t + '</span>' +
'<span class="badge badge-agent-' + String(ev.agent_name || '').toLowerCase() + '">' + (ev.agent_name || '') + '</span> ' +
(ev.title || ev.event_type || '') + '</div>';
}).join('') || '<p class="empty-state">Nog geen events.</p>';
}
return { startPolling: startPolling, connectFeed: connectFeed, updateLiveBadge: updateLiveBadge, renderAgentFeed: renderAgentFeed };
})();