/* global React */ // ======================================================================= // BlazeConnector Admin v3 — Operations pages // Dashboard · Mensajería · Colas · Logs · Alertas // ======================================================================= const { useState: _us, useEffect: _ue, useMemo: _um, useRef: _ur, useCallback: _uc } = React; /* ========================================================================= DASHBOARD ========================================================================= */ function PageDashboard({ tenant, sse, systemHealth }) { const [tick, setTick] = _us(0); _ue(() => { const id = setInterval(() => setTick(t => t + 1), 4000); return () => clearInterval(id); }, []); const isOutage = systemHealth === 'outage'; const isDegraded = systemHealth === 'degraded'; // KPIs adjusted by health const baseRate = isOutage ? 12.4 : isDegraded ? 3.1 : 0.6; const baseLat = isOutage ? 1840 : isDegraded ? 720 : 318; const baseFailed = isOutage ? 412 : isDegraded ? 84 : 18; const kpis = [ { label: 'Mensajes / min', value: 1242 + (tick % 5) * 4, unit: '', delta: 3.2, series: window.DASHBOARD_SERIES.msgsPerMin, color: 'orange', foot: 'últimos 60m · ↑ vs ayer' }, { label: 'Error rate', value: baseRate + '%', unit: '', delta: isOutage ? 412 : isDegraded ? 84 : -12, deltaTone: isOutage || isDegraded ? 'down' : 'up', series: window.DASHBOARD_SERIES.errorsPerMin, color: isOutage ? 'danger' : isDegraded ? 'warn' : 'green', foot: baseFailed + ' fallos / 60m' }, { label: 'Latencia p95', value: baseLat, unit: 'ms', delta: isOutage ? 240 : isDegraded ? 64 : -8, deltaTone: isOutage || isDegraded ? 'down' : 'up', series: window.DASHBOARD_SERIES.latencyP95, color: isDegraded ? 'warn' : 'blue', foot: 'p99 ' + (baseLat * 2.1).toFixed(0) + 'ms' }, { label: 'Tenants activos', value: 10, unit: '/ 12', delta: 0, series: [10,10,10,10,11,11,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10], color: 'violet', foot: '2 suspendidos · 0 nuevos hoy' }, { label: 'Logs / min', value: 2418, unit: '', delta: 8.4, series: window.DASHBOARD_SERIES.inbound, color: 'teal', foot: 'ingest p95 142ms' }, { label: 'Workers activos', value: 48, unit: '/ 56', delta: 0, series: window.DASHBOARD_SERIES.workerActive, color: 'green', foot: '8 idle · 0 caídos' } ]; const queueDepth = window.QUEUES.reduce((a, q) => a + q.pending + q.active, 0); const recentActivity = _um(() => window.genMessages(8), [tick]); return (

Dashboard

Estado operacional · {tenant.id === '*' ? 'todos los tenants' : tenant.name} · actualizado hace {tick * 4}s

{(isOutage || isDegraded) && (
{isOutage ? 'Outage parcial en pagos.cybersource' : 'Degradación en cola messaging — backlog elevado'}
{isOutage ? 'CyberSource no responde desde 17:18. Affected: 1 tenant. Fallback activo: pendiente.' : 'Backlog 1,842 mensajes. p95 ingest +84%. Auto-scale de workers en curso.'}
)}
{kpis.map((k) => )}
}>
({ value: c.count, color: c.color }))} centerLabel="107k" centerSub="MENSAJES" size={130} />
{window.CHANNEL_BREAKDOWN.map(c => (
{c.label} {c.count.toLocaleString()} {c.pct}%
))}
a + q.workers, 0) + ' workers'}>
{window.QUEUES.map(q => { const total = q.pending + q.active + q.retry; const max = 2000; const isCritical = q.pending > 500; return (
{q.name} {isCritical && }
{total.toLocaleString()} {q.throughputPerMin}/m
); })}
active pending retry
!a.ack).length + ' sin reconocer'} actions={}>
{window.ALERTS.slice(0, 5).map(a => (
{a.title}
{a.source}{a.tenant ? ' · ' + a.tenant : ''} · hace {a.age}
{a.ack ? ack : }
))}
Ir a mensajería }> {window.fmtTime(r.ts)} }, { label: 'Tenant', render: (r) => {r.tenantName} }, { label: 'Canal', width: 60, render: (r) => }, { label: 'Dirección', width: 80, render: (r) => {r.direction} }, { label: 'Estado', width: 100, render: (r) => }, { label: 'Destinatario', render: (r) => {r.to || r.from}, faint: true }, { label: 'Mensaje', render: (r) => {r.preview} }, { label: 'Latencia', width: 80, numeric: true, render: (r) => {r.latencyMs}ms } ]} rows={recentActivity} />
); } /* ========================================================================= MESSAGING ========================================================================= */ function PageMessaging({ tenant }) { const [messages] = _us(() => window.genMessages(120)); const [selected, setSelected] = _us(null); const [search, setSearch] = _us(''); const [statusF, setStatusF] = _us('all'); const [channelF, setChannelF] = _us('all'); const [directionF, setDirectionF] = _us('all'); const filtered = _um(() => { const ql = search.toLowerCase(); return messages.filter(m => { if (tenant.id !== '*' && m.tenant !== tenant.id) return false; if (statusF !== 'all' && m.status !== statusF) return false; if (channelF !== 'all' && m.channel !== channelF) return false; if (directionF !== 'all' && m.direction !== directionF) return false; if (ql) { const blob = (m.to + ' ' + m.from + ' ' + m.contactName + ' ' + m.preview + ' ' + m.id + ' ' + m.trace_id + ' ' + (m.template || '')).toLowerCase(); if (!blob.includes(ql)) return false; } return true; }); }, [messages, tenant, statusF, channelF, directionF, search]); const stats = _um(() => { const s = { delivered: 0, sent: 0, queued: 0, failed: 0, read: 0, expired: 0 }; filtered.forEach(m => { s[m.status] = (s[m.status] || 0) + 1; }); return s; }, [filtered]); return (

Mensajería

Explorador operacional · {filtered.length.toLocaleString()} mensajes · ventana 60m

{[ { lbl: 'Total', val: filtered.length, tone: 'neutral' }, { lbl: 'Delivered', val: stats.delivered, tone: 'success' }, { lbl: 'Sent', val: stats.sent, tone: 'info' }, { lbl: 'Queued', val: stats.queued, tone: 'warn' }, { lbl: 'Failed', val: stats.failed, tone: 'danger' }, { lbl: 'Read', val: stats.read, tone: 'success' } ].map((s) => (
{s.lbl} {s.val.toLocaleString()}
))}
setStatusF(statusF === 'all' ? 'failed' : statusF === 'failed' ? 'queued' : statusF === 'queued' ? 'delivered' : 'all')} /> setChannelF(channelF === 'all' ? 'whatsapp' : channelF === 'whatsapp' ? 'telegram' : channelF === 'telegram' ? 'chatwoot' : 'all')} /> setDirectionF(directionF === 'all' ? 'outbound' : directionF === 'outbound' ? 'inbound' : 'all')} />
{window.fmtTime(r.ts)} }, { label: 'Tenant', width: 160, render: (r) => {r.tenantName} }, { label: 'Canal', width: 56, render: (r) => }, { label: 'Dir', width: 56, render: (r) => }, { label: 'Estado', width: 110, render: (r) => }, { label: 'Plantilla', width: 180, render: (r) => r.template ? {r.template} : }, { label: 'Destinatario', width: 130, render: (r) => {r.to || r.from} }, { label: 'Contenido', render: (r) => {r.preview} }, { label: 'Lat.', width: 60, numeric: true, render: (r) => 500 ? 'var(--st-warn-fg)' : 'var(--app-fg2)' }}>{r.latencyMs}ms }, { label: 'Retries', width: 60, numeric: true, render: (r) => r.retries > 0 ? {r.retries} : 0 }, { label: 'msg_id', width: 130, render: (r) => {r.id} } ]} rows={filtered} />
{filtered.length.toLocaleString()} mensajes · página 1 / {Math.ceil(filtered.length / 50)}
1
{selected && setSelected(null)} />}
); } function MessageDetailDrawer({ msg, onClose }) { const timeline = [ { ts: msg.ts, label: 'Mensaje creado', meta: 'connector.dispatcher.v3', tone: 'info' }, { ts: new Date(new Date(msg.ts).getTime() + 80).toISOString(), label: 'Encolado · messaging', meta: 'asynq · weight=4', tone: 'info' }, { ts: new Date(new Date(msg.ts).getTime() + 142).toISOString(), label: 'Dispatch a Meta Cloud API', meta: 'wabacloud.proxy · POST /messages', tone: 'info' }, { ts: new Date(new Date(msg.ts).getTime() + msg.latencyMs).toISOString(), label: msg.status === 'failed' ? 'FAILED · rate_limit_exceeded' : msg.status === 'queued' ? 'Esperando aceptación' : 'Aceptado · ' + msg.providerMessageId.slice(0, 16) + '…', meta: 'meta.graph status=' + (msg.status === 'failed' ? '429' : '202') + ' · ' + msg.latencyMs + 'ms', tone: msg.tone }, ...(msg.status === 'delivered' || msg.status === 'read' ? [{ ts: new Date(new Date(msg.ts).getTime() + msg.latencyMs + 1200).toISOString(), label: 'Webhook: ' + msg.status, meta: 'msgr.webhook · message_status', tone: 'success' }] : []), ...(msg.status === 'read' ? [{ ts: new Date(new Date(msg.ts).getTime() + msg.latencyMs + 8400).toISOString(), label: 'Webhook: read', meta: 'msgr.webhook · message_status', tone: 'success' }] : []), ...(msg.retries > 0 ? [{ ts: new Date(new Date(msg.ts).getTime() + 30000).toISOString(), label: 'Retry #' + msg.retries + ' encolado', meta: 'backoff exponential · jitter ±20%', tone: 'warn' }] : []) ]; return ( Reintentar} }>
Resumen
Estado
Tenant
{msg.tenantName}
Canal
{window.CHANNEL_LABEL[msg.channel]}
Dirección
{msg.direction}
Provider
{msg.provider}
Provider msg id
{msg.providerMessageId}
{msg.direction === 'outbound' ? 'Destinatario' : 'Remitente'}
{msg.to || msg.from} ({msg.contactName})
Plantilla
{msg.template || }
Latencia
{msg.latencyMs}ms
Retries
{msg.retries}
trace_id
{msg.trace_id}
Timeline
{timeline.map((t, i) => (
{t.label}
{window.fmtFullTime(t.ts)}·{t.meta}
))}
Payload
Headers
); } /* ========================================================================= QUEUES ========================================================================= */ function PageQueues({ tenant }) { const [tick, setTick] = _us(0); _ue(() => { const id = setInterval(() => setTick(t => t + 1), 3000); return () => clearInterval(id); }, []); const live = window.QUEUES.map((q, i) => ({ ...q, active: Math.max(0, q.active + Math.floor(Math.sin(tick + i) * 8)), throughputPerMin: Math.max(0, q.throughputPerMin + Math.floor((Math.random() - 0.5) * 20)) })); const totals = live.reduce((a, q) => ({ active: a.active + q.active, pending: a.pending + q.pending, retry: a.retry + q.retry, failed: a.failed + q.failed, completed: a.completed + q.completed, throughput: a.throughput + q.throughputPerMin, workers: a.workers + q.workers }), { active: 0, pending: 0, retry: 0, failed: 0, completed: 0, throughput: 0, workers: 0 }); return (

Colas

Asynq · {live.length} colas · {totals.workers} workers · {totals.throughput.toLocaleString()} msgs/m

{live.map(q => { const tone = q.pending > 500 ? 'warn' : q.failed > 5 ? 'danger' : 'success'; return (
{q.name} weight={q.weight} · workers={q.workers}
{[ { lbl: 'Active', val: q.active, color: '#1E7BFF' }, { lbl: 'Pending', val: q.pending, color: '#F2A900' }, { lbl: 'Retry', val: q.retry, color: '#7A3CFF' }, { lbl: 'Failed', val: q.failed, color: '#E03E3E' }, { lbl: 'p95', val: q.p95 + 'ms', color: 'var(--app-fg2)' } ].map((s, i) => (
{s.lbl}
{typeof s.val === 'number' ? s.val.toLocaleString() : s.val}
))}
Math.max(0, v * (q.weight / 12) + (Math.random() - 0.5) * 40))} color={tone === 'warn' ? 'warn' : tone === 'danger' ? 'danger' : 'blue'} />
Throughput: {q.throughputPerMin}/m Completed: {q.completed.toLocaleString()}
); })}
); } /* ========================================================================= LOGS — streaming, virtualized-ish ========================================================================= */ function PageLogs({ tenant, sseEvents }) { const [logs, setLogs] = _us(() => window.genLogs(80)); const [search, setSearch] = _us(''); const [levelF, setLevelF] = _us('all'); const [svcF, setSvcF] = _us('all'); const [expanded, setExpanded] = _us(null); const [paused, setPaused] = _us(false); const scrollRef = _ur(null); _ue(() => { if (paused) return; const id = setInterval(() => { const tpl = window.randomFrom(window.ERROR_TEMPLATES); const tnt = window.randomFrom(window.TENANTS); const newLog = { id: 'log_' + window.randHex(10), ts: new Date().toISOString(), level: tpl.level, svc: tpl.svc, tenant: tnt.id, tenantName: tnt.name, msg: tpl.msg, code: tpl.code, trace_id: window.TRACE_PREFIX(), span_id: window.randHex(8), attrs: { env: 'production', region: 'sdq-1', host: 'connector-worker-' + (Math.floor(Math.random() * 6) + 1), request_id: 'req_' + window.randHex(12), user_agent: 'BlazeConnector/3.4.2 (go1.22)' }, isNew: true }; setLogs(prev => [newLog, ...prev].slice(0, 200)); }, 2200 + Math.random() * 2000); return () => clearInterval(id); }, [paused]); const filtered = _um(() => { const ql = search.toLowerCase(); return logs.filter(l => { if (tenant.id !== '*' && l.tenant !== tenant.id) return false; if (levelF !== 'all' && l.level !== levelF) return false; if (svcF !== 'all' && l.svc !== svcF) return false; if (ql) { const blob = (l.msg + ' ' + l.svc + ' ' + l.tenantName + ' ' + l.trace_id + ' ' + (l.code || '')).toLowerCase(); if (!blob.includes(ql)) return false; } return true; }); }, [logs, search, levelF, svcF, tenant]); const distrib = _um(() => { const out = { error: 0, warn: 0, info: 0, debug: 0 }; logs.forEach(l => { out[l.level] = (out[l.level] || 0) + 1; }); return out; }, [logs]); return (

Logs

Stream estructurado · {filtered.length} / {logs.length} eventos · {paused ? 'pausado' : 'live'}

ERROR {distrib.error} WARN {distrib.warn} INFO {distrib.info} DEBUG {distrib.debug}
setLevelF(levelF === 'all' ? 'error' : levelF === 'error' ? 'warn' : levelF === 'warn' ? 'info' : 'all')} /> setSvcF(svcF === 'all' ? 'msgr.dispatch' : svcF === 'msgr.dispatch' ? 'pagos.callback' : 'all')} />
timestamplevelservicetenantmessage
{filtered.length === 0 ? : filtered.map((l) => (
setExpanded(expanded === l.id ? null : l.id)}> {l.ts.slice(11, 23)} {l.level} {l.svc} {l.tenantName} {l.msg} {l.code && · code={l.code}}
{expanded === l.id && (
)}
))}
); } /* ========================================================================= ALERTAS ========================================================================= */ function PageAlerts({ tenant }) { const [filter, setFilter] = _us('active'); const [search, setSearch] = _us(''); const rows = _um(() => { const ql = search.toLowerCase(); return window.ALERTS.filter(a => { if (tenant.id !== '*' && a.tenant && a.tenant !== tenant.id) return false; if (filter === 'active' && a.ack) return false; if (filter === 'critical' && a.severity !== 'critical') return false; if (filter === 'ack' && !a.ack) return false; if (ql && !(a.title + ' ' + a.source).toLowerCase().includes(ql)) return false; return true; }); }, [filter, tenant, search]); return (

Alertas

{window.ALERTS.filter(a => !a.ack).length} sin reconocer · {window.ALERTS.length} totales · políticas activas: 24

a.severity === 'critical').length} color="danger" foot={window.ALERTS.filter(a => a.severity === 'critical' && !a.ack).length + ' sin ack'} /> a.severity === 'warning').length} color="warn" foot={window.ALERTS.filter(a => a.severity === 'warning' && !a.ack).length + ' sin ack'} /> a.severity === 'info').length} color="blue" />
{['active','critical','ack','all'].map(f => ( ))} }, { label: 'Título', render: (a) =>
{a.title}
{a.source}
}, { label: 'Tenant', width: 160, render: (a) => a.tenant ? t.id === a.tenant) || {}).name || a.tenant }} size={16} /> {(window.TENANTS.find(t => t.id === a.tenant) || {}).name || a.tenant} : global }, { label: 'Disparo', width: 140, render: (a) => {window.fmtTime(a.fired)} · {a.age} }, { label: 'Estado', width: 100, render: (a) => a.ack ? : }, { label: '', width: 200, render: (a) =>
{a.runbook && } {!a.ack && }
} ]} rows={rows} />
); } Object.assign(window, { PageDashboard, PageMessaging, PageQueues, PageLogs, PageAlerts });