${esc(title)}
+${desc}
+ +diff --git a/projects/ops-desk/frontend/assets/app.js b/projects/ops-desk/frontend/assets/app.js
index 313aec7..08617d1 100644
--- a/projects/ops-desk/frontend/assets/app.js
+++ b/projects/ops-desk/frontend/assets/app.js
@@ -3825,17 +3825,190 @@ function infraKvHtml(items) {
).join('')}`;
}
+const PROC_CARD_ICONS = {
+ soc: '📡',
+ openpanel: '🎛️',
+ purge: '🔐',
+ vm112: '🌐',
+ wazuh: '🛡️',
+ integrations: '🔗',
+};
+
+function procCardHtml(opts) {
+ const {
+ id,
+ icon,
+ accent = 'teal',
+ title,
+ spec,
+ desc,
+ statusLabel,
+ statusCls = 'review',
+ actions = [],
+ } = opts;
+ const acts = actions.map((a) =>
+ ``
+ ).join('');
+ return `
+ ${desc}${esc(title)}
+
Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.
` + ); + document.getElementById('btn-test-webhook-modal')?.addEventListener('click', () => runWebhookIntegrationTest('infra')); + document.getElementById('btn-refresh-health-modal')?.addEventListener('click', () => { + closeInfraProcessModal(); + renderInfra(); + }); + return; + } + if (procId === 'openpanel') { + openInfraProcessModal( + 'OpenPanel API — Re-engenharia Ligbox', + 'Spec 028 · VM123 bridge :18087', + `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.
'} +Suite openpanel-multidomain-api-confirm
A carregar…
${esc(JSON.stringify(integrations, null, 2))}Verificando…
'; try { - const [vm112, wazuh, integrations, health, vm123Health] = await Promise.all([ + 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'), 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` : '—'; @@ -3845,124 +4018,110 @@ async function renderInfra() { const opOk = Boolean(op.ok); const bridgeOk = Boolean(op.bridge); const statusCls = health.status === 'ok' ? 'ok' : health.status === 'critical' ? 'escalated' : 'assisting'; - const heroHealthDot = health.status === 'ok' ? '' : health.status === 'critical' ? 'infra-hero-dot--bad' : 'infra-hero-dot--warn'; - const alerts = (health.alerts || []).map((a) => - `Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.
-VM123 · bridge FOSS :18087 · multidomínio · conta temporária com cleanup automático.
Status VM123 indisponível — verifique API /vm123/health.
Suite openpanel-multidomain-api-confirm · CLI scripts/test-openpanel-multidomain-api.sh
A carregar…
${esc(JSON.stringify(integrations, null, 2))}
- Processos de infraestrutura · cards uniformes · detalhes e formulários em modal (Spec 033).
+Erro: ${esc(e.message)}
`; } } -async function renderPurgeAuthInfraPanel() { - const panel = document.getElementById('purge-auth-infra-panel'); +async function renderPurgeAuthPanel(panel) { if (!panel) return; try { const meta = await api('/v1/infra/purge-auth-domains'); @@ -4060,6 +4219,10 @@ async function renderPurgeAuthInfraPanel() { } } +async function renderPurgeAuthInfraPanel() { + await renderPurgeAuthPanel(document.getElementById('purge-auth-infra-panel')); +} + async function refresh(options = {}) { const { poll = false } = options; await loadHealth(); @@ -4143,6 +4306,7 @@ document.getElementById('btn-refresh')?.addEventListener('click', () => { applyRoleNav(); DeskModules.applyVisibility(); bindOverviewModal(); + bindInfraProcessModal(); bindTeamDrawerClose(); bindSocTestModal(); setView('dashboard'); diff --git a/projects/ops-desk/frontend/assets/styles.css b/projects/ops-desk/frontend/assets/styles.css index 70f9bf3..733c50f 100644 --- a/projects/ops-desk/frontend/assets/styles.css +++ b/projects/ops-desk/frontend/assets/styles.css @@ -4111,6 +4111,102 @@ button.health-card { background: #0f172a; } +/* Process cards — grid uniforme (Spec 033 § proc-card) */ +.proc-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 16px; + align-items: stretch; +} +.proc-card { + position: relative; + display: flex; + flex-direction: column; + min-height: 168px; + padding: 16px; + border-radius: 12px; + border: 1px solid #e2e8f0; + background: #fff; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); +} +.proc-card--teal { border-top: 3px solid #14b8a6; } +.proc-card--orange { border-top: 3px solid #f97316; } +.proc-card--rose { border-top: 3px solid #f43f5e; } +.proc-card--slate { border-top: 3px solid #64748b; } +.proc-card--violet { border-top: 3px solid #8b5cf6; } +.proc-card--aqua { border-top: 3px solid #06b6d4; } +.proc-card-head { + display: flex; + align-items: flex-start; + gap: 8px; + min-height: 32px; +} +.proc-card-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + font-size: 1.05rem; + line-height: 1; + flex-shrink: 0; +} +.proc-card--teal .proc-card-icon { background: #ccfbf1; } +.proc-card--orange .proc-card-icon { background: #ffedd5; } +.proc-card--rose .proc-card-icon { background: #ffe4e6; } +.proc-card--slate .proc-card-icon { background: #f1f5f9; } +.proc-card--violet .proc-card-icon { background: #ede9fe; } +.proc-card--aqua .proc-card-icon { background: #cffafe; } +.proc-card-spec { + font-size: 0.62rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #64748b; + margin-left: auto; + text-align: right; + line-height: 1.2; + max-width: 42%; +} +.proc-card-badge { + position: absolute; + top: 12px; + right: 12px; + font-size: 0.62rem; +} +.proc-card-title { + margin: 8px 0 0; + font-size: 0.88rem; + font-weight: 600; + color: #0f172a; + line-height: 1.35; + padding-right: 3.5rem; +} +.proc-card-desc { + margin: 6px 0 0; + font-size: 0.75rem; + color: #64748b; + line-height: 1.45; + flex: 1; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.proc-card-foot { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #f1f5f9; +} +.proc-card-foot .btn { flex: 1 1 auto; min-width: 0; } +@media (max-width: 480px) { + .proc-grid { grid-template-columns: 1fr; } +} + /* Spec 021 — Acesso utilizador (separado do VM112 Onboard) */ .ws-access-zone { margin-bottom: 1.25rem; diff --git a/projects/ops-desk/frontend/index.html b/projects/ops-desk/frontend/index.html index ccb3e0b..67ec74d 100644 --- a/projects/ops-desk/frontend/index.html +++ b/projects/ops-desk/frontend/index.html @@ -413,14 +413,27 @@