cc-dashboard/src/static/js/sse.js
Vadym Samoilenko 7b30880d44 Initial commit — CC Dashboard v1.0
Multi-tenant Claude Code monitoring dashboard.
FastAPI + PostgreSQL + Docker + SSE real-time updates.
Montserrat font, black/#FFC407 color scheme.
Apache reverse proxy config at /cc-dashboard/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 12:54:47 +00:00

61 lines
1.9 KiB
JavaScript

/**
* SSE client with auto-reconnect.
* Heartbeat ": heartbeat" comments keep connection alive through 30s LB timeout.
*/
const SSE = (() => {
const BASE = window.CC_BASE || '';
let _es = null;
let _handlers = {};
let _dotEl = null;
let _reconnectTimer = null;
function setDot(el) { _dotEl = el; }
function _setState(state) {
if (!_dotEl) return;
_dotEl.className = 'sse-dot ' + state;
_dotEl.title = state === 'connected' ? 'Live updates active' : state === 'error' ? 'Disconnected — retrying' : 'Connecting…';
}
function on(type, handler) { _handlers[type] = handler; }
function connect() {
if (_es) return;
_setState('');
// EventSource doesn't support custom headers — send token as query param
const token = Api.getAccessToken();
// We use a small wrapper: GET /api/events with Bearer via query won't work with
// standard EventSource. Instead we fetch a one-time SSE ticket or reuse JWT from
// localStorage. For simplicity, pass token via URL param and validate on server.
_es = new EventSource(`${BASE}/api/events?token=${encodeURIComponent(token)}`);
_es.onopen = () => { _setState('connected'); };
_es.onmessage = (e) => {
try {
const data = JSON.parse(e.data);
const handler = _handlers[data.type];
if (handler) handler(data);
const allHandler = _handlers['*'];
if (allHandler) allHandler(data);
} catch { /* ignore parse errors */ }
};
_es.onerror = () => {
_setState('error');
_es.close();
_es = null;
if (_reconnectTimer) clearTimeout(_reconnectTimer);
_reconnectTimer = setTimeout(connect, 4000);
};
}
function disconnect() {
if (_reconnectTimer) clearTimeout(_reconnectTimer);
if (_es) { _es.close(); _es = null; }
_setState('');
}
return { connect, disconnect, on, setDot };
})();