Multidomínio · conta temporária com cleanup automático.
- ${vm123Health
- ? infraKvHtml([
- ['OpenPanel', opOk ? 'OK' : esc(op.error || 'offline')],
- ['Bridge API', bridgeOk ? 'OK' : 'offline'],
- ['Bridge URL', esc(op.bridge_url || '—')],
- ['VM123', vm123Health.ok ? 'OK' : esc(vm123Health.error || 'check')],
- ])
- : 'Status VM123 indisponível.
'}
+ ${infraKvHtml([
+ ['OpenPanel', opOk ? 'OK' : esc(op.error || op.detail || 'offline')],
+ ['Bridge API', bridgeOk ? 'OK' : 'offline'],
+ ['Bridge URL', esc(op.bridge_url || op.url || '—')],
+ ])}
OpenPanel UI
@@ -3954,7 +4016,7 @@ function openInfraProcessDetail(procId) {
document.getElementById('btn-test-openpanel-modal')?.addEventListener('click', () => runOpenPanelApiTest());
return;
}
- if (procId === 'purge') {
+ if (procId === 'purge' || procId === 'vm122-purge-auth') {
openInfraProcessModal(
'Códigos purge — autorização extra',
'Spec 032 · domínios protegidos',
@@ -3963,159 +4025,76 @@ function openInfraProcessDetail(procId) {
renderPurgeAuthPanel(document.getElementById('purge-auth-modal-panel'));
return;
}
- if (procId === 'vm112') {
- openInfraProcessModal(
- 'VM112 — Onboard Portal',
- 'HTTP health · serviço portal',
- infraKvHtml([
- ['HTTP', String(vm112.http_status ?? '—')],
- ['Service', esc(vm112.vm112?.service || vm112.error || '—')],
- ['API integração', vmOk ? 'OK' : 'offline'],
- ])
- );
- return;
- }
- if (procId === 'wazuh') {
- openInfraProcessModal(
- 'VM104 — Wazuh SOC',
- 'Spec 002 · API + webhook',
- infraKvHtml([
- ['API HTTP', String(wazuh.http_status ?? '—')],
- ['Integração', 'webhook level ≥ 10 → VM122'],
- ['Status', wazuhOk ? 'online' : 'check'],
- ])
- );
- return;
- }
if (procId === 'integrations') {
openInfraProcessModal(
'Integrações activas',
'Snapshot JSON · Desk API',
- `
${esc(JSON.stringify(integrations, null, 2))}`
+ `
${esc(JSON.stringify(integrations || {}, null, 2))}`
);
}
}
async function renderInfra() {
const el = document.getElementById('infra-content');
- el.innerHTML = '
Verificando…
';
+ el.innerHTML = '
Verificando stack…
';
try {
- const [vm112, wazuh, integrations, health, vm123Health, purgeMeta] = await Promise.all([
- api('/v1/infra/vm112/status'),
- api('/v1/infra/wazuh/status'),
- api('/v1/integrations'),
- api('/v1/integrations/health'),
+ const [stack, integrations, health, vm123Health, purgeMeta] = await Promise.all([
+ api('/v1/infra/stack/status'),
+ api('/v1/integrations').catch(() => null),
+ api('/v1/integrations/health').catch(() => ({})),
api('/v1/vm123/health').catch(() => null),
api('/v1/infra/purge-auth-domains').catch(() => ({ domains: [], can_generate: false })),
]);
- state.infraSnapshot = { vm112, wazuh, integrations, health, vm123Health, purgeMeta };
- const onboard = health.vm112_onboard || {};
- const last = onboard.last_webhook;
- const gap = onboard.gap_minutes != null ? `${Math.round(onboard.gap_minutes)} min` : '—';
- const vmOk = onboard.vm112_api?.reachable;
- const wazuhOk = wazuh.http_status === 200;
- const op = vm123Health?.openpanel || {};
- const opOk = Boolean(op.ok);
- const bridgeOk = Boolean(op.bridge);
- const statusCls = health.status === 'ok' ? 'ok' : health.status === 'critical' ? 'escalated' : 'assisting';
- const purgeDomains = purgeMeta.domains || [];
- const purgeLabel = purgeDomains.length ? `${purgeDomains.length} domínio(s)` : 'sem extra-auth';
+ state.infraSnapshot = { stack, integrations, health, vm123Health, purgeMeta };
+ const summary = stack.summary || {};
+ const okCls = summary.ok === summary.total ? 'ok' : summary.ok > 0 ? 'assisting' : 'escalated';
+ let sections = (stack.vms || []).map((vm) => {
+ const cards = (vm.services || []).map((svc) =>
+ procCardHtml({
+ id: svc.id,
+ icon: svc.icon || '⚙️',
+ accent: svc.accent || 'teal',
+ title: svc.title,
+ spec: svc.spec && svc.spec !== '—' ? `Spec ${svc.spec}` : String(svc.kind || 'stack').toUpperCase(),
+ desc: esc(svc.detail || svc.url || ''),
+ statusLabel: svc.status || (svc.ok ? 'online' : 'down'),
+ statusCls: stackServiceStatusCls(svc),
+ actions: stackServiceActions(svc.id),
+ })
+ ).join('');
+ return `
+
+ ${esc(vm.vm_label)} ${esc(vm.ip)}
+ ${cards}
+ `;
+ }).join('');
+
+ const integrationsCard = integrations
+ ? procCardHtml({
+ id: 'integrations-json',
+ icon: '🔗',
+ accent: 'violet',
+ title: 'Integrações Desk',
+ spec: 'JSON',
+ desc: 'Registry onboard + Wazuh · snapshot API',
+ statusLabel: 'activas',
+ statusCls: 'open',
+ actions: [{ label: 'Ver JSON', action: 'detail', primary: true }],
+ })
+ : '';
+
el.innerHTML = `
-
Processos de infraestrutura · cards uniformes · detalhes e formulários em modal (Spec 033).
-
- ${procCardHtml({
- id: 'soc',
- icon: PROC_CARD_ICONS.soc,
- accent: 'teal',
- title: 'SOC VM112',
- spec: 'Webhook',
- desc: `Gap ${gap} · ${last?.event ? esc(last.event) : 'sem eventos recentes'}`,
- statusLabel: health.status || '—',
- statusCls,
- actions: [
- { id: 'btn-proc-soc-detail', label: 'Detalhes', primary: true },
- { id: 'btn-test-webhook', label: 'Testar', primary: false },
- ],
- })}
- ${procCardHtml({
- id: 'openpanel',
- icon: PROC_CARD_ICONS.openpanel,
- accent: 'orange',
- title: 'OpenPanel API',
- spec: 'Spec 028',
- desc: `Bridge ${bridgeOk ? 'OK' : 'check'} · ${esc(op.bridge_url || '10.10.10.123:18087')}`,
- statusLabel: opOk ? 'online' : 'check',
- statusCls: opOk ? 'ok' : 'review',
- actions: [
- { id: 'btn-proc-openpanel-detail', label: 'Detalhes', primary: true },
- { id: 'btn-test-openpanel-api', label: 'Testar', primary: false },
- ],
- })}
- ${procCardHtml({
- id: 'purge',
- icon: PROC_CARD_ICONS.purge,
- accent: 'rose',
- title: 'Códigos purge',
- spec: 'Spec 032',
- desc: purgeDomains.length
- ? `Protegidos: ${purgeDomains.map((d) => esc(d)).join(', ')}`
- : 'Geração de códigos para domínios com autorização extra',
- statusLabel: purgeLabel,
- statusCls: purgeDomains.length ? 'assisting' : 'open',
- actions: [
- { id: 'btn-proc-purge-manage', label: 'Gerir códigos', primary: true },
- ],
- })}
- ${procCardHtml({
- id: 'vm112',
- icon: PROC_CARD_ICONS.vm112,
- accent: 'aqua',
- title: 'VM112 Onboard',
- spec: 'Portal',
- desc: esc(vm112.vm112?.service || vm112.error || 'Portal de onboarding'),
- statusLabel: vmOk ? 'online' : 'check',
- statusCls: vmOk ? 'ok' : 'review',
- actions: [
- { id: 'btn-proc-vm112-detail', label: 'Ver status', primary: true },
- ],
- })}
- ${procCardHtml({
- id: 'wazuh',
- icon: PROC_CARD_ICONS.wazuh,
- accent: 'slate',
- title: 'Wazuh SOC',
- spec: 'Spec 002',
- desc: `API HTTP ${wazuh.http_status ?? '—'} · alertas nível ≥ 10`,
- statusLabel: wazuhOk ? 'online' : 'check',
- statusCls: wazuhOk ? 'ok' : 'review',
- actions: [
- { id: 'btn-proc-wazuh-detail', label: 'Ver status', primary: true },
- ],
- })}
- ${procCardHtml({
- id: 'integrations',
- icon: PROC_CARD_ICONS.integrations,
- accent: 'violet',
- title: 'Integrações',
- spec: 'JSON',
- desc: 'Snapshot das integrações configuradas no Desk',
- statusLabel: 'activas',
- statusCls: 'open',
- actions: [
- { id: 'btn-proc-integrations-json', label: 'Ver JSON', primary: true },
- ],
- })}
+
+ ${summary.ok ?? 0}/${summary.total ?? 0} online
+ Stack Ligbox · VMs 112 · 114 · 122 · 123 · 130 · Infra as Code · ${fmtDate(stack.generated_at)}
+
+ ${sections}
+ ${integrationsCard ? `
Integrações · Desk API
${integrationsCard}
` : ''}
`;
- document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra'));
- document.getElementById('btn-test-openpanel-api')?.addEventListener('click', () => runOpenPanelApiTest());
- document.getElementById('btn-proc-soc-detail')?.addEventListener('click', () => openInfraProcessDetail('soc'));
- document.getElementById('btn-proc-openpanel-detail')?.addEventListener('click', () => openInfraProcessDetail('openpanel'));
- document.getElementById('btn-proc-purge-manage')?.addEventListener('click', () => openInfraProcessDetail('purge'));
- document.getElementById('btn-proc-vm112-detail')?.addEventListener('click', () => openInfraProcessDetail('vm112'));
- document.getElementById('btn-proc-wazuh-detail')?.addEventListener('click', () => openInfraProcessDetail('wazuh'));
- document.getElementById('btn-proc-integrations-json')?.addEventListener('click', () => openInfraProcessDetail('integrations'));
+ document.getElementById('btn-infra-refresh-stack')?.addEventListener('click', () => renderInfra());
+ bindStackCardActions(el);
} catch (e) {
el.innerHTML = `
Erro: ${esc(e.message)}
`;
}
diff --git a/projects/ops-desk/frontend/assets/styles.css b/projects/ops-desk/frontend/assets/styles.css
index 733c50f..2e4afcf 100644
--- a/projects/ops-desk/frontend/assets/styles.css
+++ b/projects/ops-desk/frontend/assets/styles.css
@@ -3901,6 +3901,26 @@ button.health-card {
flex-direction: column;
gap: 0.85rem;
}
+.infra-stack-summary {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.65rem;
+ padding: 0.65rem 0.85rem;
+ border-radius: 12px;
+ border: 1px solid #e2e8f0;
+ background: linear-gradient(135deg, #f0fdfa 0%, #fff 70%);
+}
+.infra-vm-section {
+ margin-bottom: 0.5rem;
+}
+.infra-vm-head {
+ margin: 0 0 0.65rem;
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #0f172a;
+ letter-spacing: 0.02em;
+}
.infra-hero {
display: flex;
flex-wrap: wrap;
diff --git a/projects/ops-desk/frontend/index.html b/projects/ops-desk/frontend/index.html
index 67ec74d..abf207f 100644
--- a/projects/ops-desk/frontend/index.html
+++ b/projects/ops-desk/frontend/index.html
@@ -194,7 +194,7 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+