Improve Infra page cards with wizard-style ws-panel aqua/teal layout.
Redesign SOC, purge auth, and integration panels; fix servicos-tile-icon CSS typo.
This commit is contained in:
parent
2168d432f7
commit
41c0c2d428
2 changed files with 338 additions and 83 deletions
|
|
@ -3819,6 +3819,12 @@ async function renderInfra2() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function infraKvHtml(items) {
|
||||||
|
return `<dl class="infra-kv">${items.map(([label, value]) =>
|
||||||
|
`<div><dt>${esc(label)}</dt><dd>${value}</dd></div>`
|
||||||
|
).join('')}</dl>`;
|
||||||
|
}
|
||||||
|
|
||||||
async function renderInfra() {
|
async function renderInfra() {
|
||||||
const el = document.getElementById('infra-content');
|
const el = document.getElementById('infra-content');
|
||||||
el.innerHTML = '<p class="loading">Verificando…</p>';
|
el.innerHTML = '<p class="loading">Verificando…</p>';
|
||||||
|
|
@ -3832,58 +3838,97 @@ async function renderInfra() {
|
||||||
const onboard = health.vm112_onboard || {};
|
const onboard = health.vm112_onboard || {};
|
||||||
const last = onboard.last_webhook;
|
const last = onboard.last_webhook;
|
||||||
const gap = onboard.gap_minutes != null ? `${Math.round(onboard.gap_minutes)} min` : '—';
|
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 statusCls = health.status === 'ok' ? 'ok' : health.status === 'critical' ? 'escalated' : 'assisting';
|
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) =>
|
const alerts = (health.alerts || []).map((a) =>
|
||||||
`<li class="badge ${a.level === 'critical' ? 'escalated' : 'assisting'}">${esc(a.message)}</li>`
|
`<li class="badge ${a.level === 'critical' ? 'escalated' : 'assisting'}">${esc(a.message)}</li>`
|
||||||
).join('') || '<li class="muted">Nenhum alerta</li>';
|
).join('') || '<li class="muted">Nenhum alerta activo</li>';
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div class="card soc-panel">
|
<div class="infra-page">
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:0.75rem;flex-wrap:wrap">
|
<div class="infra-hero">
|
||||||
<h3>SOC — Integração VM112</h3>
|
<div class="infra-hero-chip">
|
||||||
<span class="badge ${statusCls}">${esc(health.status || '—')}</span>
|
<span class="infra-hero-dot ${heroHealthDot}" aria-hidden="true"></span>
|
||||||
|
<div class="infra-hero-body">
|
||||||
|
<strong>SOC integração</strong>
|
||||||
|
<span>Webhook VM112 · gap ${gap}</span>
|
||||||
|
</div>
|
||||||
|
<span class="badge ${statusCls}">${esc(health.status || '—')}</span>
|
||||||
|
</div>
|
||||||
|
<div class="infra-hero-chip">
|
||||||
|
<span class="infra-hero-dot ${vmOk ? '' : 'infra-hero-dot--warn'}" aria-hidden="true"></span>
|
||||||
|
<div class="infra-hero-body">
|
||||||
|
<strong>VM112 Portal</strong>
|
||||||
|
<span>${esc(vm112.vm112?.service || vm112.error || '—')}</span>
|
||||||
|
</div>
|
||||||
|
<span class="badge ${vmOk ? 'ok' : 'review'}">${vmOk ? 'online' : 'check'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="infra-hero-chip">
|
||||||
|
<span class="infra-hero-dot ${wazuhOk ? '' : 'infra-hero-dot--warn'}" aria-hidden="true"></span>
|
||||||
|
<div class="infra-hero-body">
|
||||||
|
<strong>VM104 Wazuh</strong>
|
||||||
|
<span>API HTTP ${wazuh.http_status ?? '—'}</span>
|
||||||
|
</div>
|
||||||
|
<span class="badge ${wazuhOk ? 'ok' : 'review'}">${wazuhOk ? 'online' : 'check'}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl class="kv">
|
<div class="infra-grid">
|
||||||
<dt>Último webhook</dt><dd>${last ? esc(last.event) : '—'}</dd>
|
<article class="ws-panel infra-panel infra-panel--wide">
|
||||||
<dt>Domínio</dt><dd>${last?.domain ? esc(last.domain) : '—'}</dd>
|
<div class="ws-panel-head ws-panel-head--teal">SOC — Integração VM112</div>
|
||||||
<dt>Há quanto tempo</dt><dd>${gap}</dd>
|
<div class="ws-panel-body">
|
||||||
<dt>VM112 API</dt><dd>${onboard.vm112_api?.reachable ? 'OK' : esc(onboard.vm112_api?.error || 'offline')}</dd>
|
${infraKvHtml([
|
||||||
</dl>
|
['Último evento', last ? esc(last.event) : '—'],
|
||||||
<ul class="soc-alerts" style="list-style:none;padding:0;margin:0.5rem 0;display:flex;flex-direction:column;gap:0.35rem">${alerts}</ul>
|
['Domínio', last?.domain ? esc(last.domain) : '—'],
|
||||||
<div class="actions">
|
['Há quanto tempo', gap],
|
||||||
<button type="button" class="btn secondary" id="btn-test-webhook">Testar webhook</button>
|
['VM112 API', vmOk ? 'OK' : esc(onboard.vm112_api?.error || 'offline')],
|
||||||
<button type="button" class="btn secondary" id="btn-refresh-health">Atualizar</button>
|
])}
|
||||||
|
<ul class="infra-alert-list">${alerts}</ul>
|
||||||
|
<div class="infra-actions">
|
||||||
|
<button type="button" class="btn secondary btn-sm" id="btn-test-webhook">Testar webhook</button>
|
||||||
|
<button type="button" class="btn secondary btn-sm" id="btn-refresh-health">Atualizar</button>
|
||||||
|
</div>
|
||||||
|
<p class="infra-hint">Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="ws-panel infra-panel">
|
||||||
|
<div class="ws-panel-head ws-panel-head--rose">Códigos purge · Spec 032</div>
|
||||||
|
<div class="ws-panel-body" id="purge-auth-infra-panel"><p class="loading">A carregar…</p></div>
|
||||||
|
</article>
|
||||||
|
<article class="ws-panel infra-panel">
|
||||||
|
<div class="ws-panel-head ws-panel-head--orange">OpenPanel API</div>
|
||||||
|
<div class="ws-panel-body">
|
||||||
|
<p class="infra-hint">Spec 028 · VM123 bridge :18087 · multidomínio · conta temporária com cleanup.</p>
|
||||||
|
<div class="infra-actions">
|
||||||
|
<button type="button" class="btn secondary btn-sm" id="btn-test-openpanel-api">Testar multidomínio</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="ws-panel infra-panel">
|
||||||
|
<div class="ws-panel-head ws-panel-head--teal">VM112 — Onboard</div>
|
||||||
|
<div class="ws-panel-body">
|
||||||
|
${infraKvHtml([
|
||||||
|
['HTTP', String(vm112.http_status ?? '—')],
|
||||||
|
['Service', esc(vm112.vm112?.service || vm112.error || '—')],
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="ws-panel infra-panel">
|
||||||
|
<div class="ws-panel-head ws-panel-head--slate">VM104 — Wazuh SOC</div>
|
||||||
|
<div class="ws-panel-body">
|
||||||
|
${infraKvHtml([
|
||||||
|
['API HTTP', String(wazuh.http_status ?? '—')],
|
||||||
|
['Integração', 'webhook level ≥ 10 → VM122'],
|
||||||
|
])}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="ws-panel infra-panel infra-panel--wide">
|
||||||
|
<div class="ws-panel-head ws-panel-head--violet">Integrações activas</div>
|
||||||
|
<div class="ws-panel-body infra-json-panel" style="padding:0">
|
||||||
|
<pre class="raw">${esc(JSON.stringify(integrations, null, 2))}</pre>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<p class="health-card-hint">Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.</p>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>Códigos autorização purge</h3>
|
|
||||||
<p class="health-card-hint">Domínios protegidos (ex.: <code>myvexx.com</code>) exigem código único gerado aqui pelo <strong>root</strong> — válido para conferência / purge em Serviços.</p>
|
|
||||||
<div id="purge-auth-infra-panel"><p class="loading">A carregar…</p></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>OpenPanel API — Re-engenharia Ligbox</h3>
|
|
||||||
<p class="health-card-hint">Spec 028 · VM123 bridge :18087 · multidomínio · conta temporária com cleanup automático.</p>
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" class="btn secondary" id="btn-test-openpanel-api">Testar multidomínio</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>VM112 — Portal Onboard</h3>
|
|
||||||
<dl class="kv">
|
|
||||||
<dt>HTTP</dt><dd>${vm112.http_status ?? '—'}</dd>
|
|
||||||
<dt>Service</dt><dd>${esc(vm112.vm112?.service || vm112.error || '—')}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>VM104 — Wazuh SOC</h3>
|
|
||||||
<dl class="kv">
|
|
||||||
<dt>API</dt><dd>${wazuh.http_status ?? '—'}</dd>
|
|
||||||
<dt>Integração</dt><dd>webhook level ≥ 10 → VM122</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<h3>Integrações ativas</h3>
|
|
||||||
<pre class="raw">${esc(JSON.stringify(integrations, null, 2))}</pre>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
document.getElementById('btn-refresh-health')?.addEventListener('click', () => renderInfra());
|
document.getElementById('btn-refresh-health')?.addEventListener('click', () => renderInfra());
|
||||||
document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra'));
|
document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra'));
|
||||||
|
|
@ -3899,43 +3944,57 @@ async function renderPurgeAuthInfraPanel() {
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
try {
|
try {
|
||||||
const meta = await api('/v1/infra/purge-auth-domains');
|
const meta = await api('/v1/infra/purge-auth-domains');
|
||||||
const domains = (meta.domains || []).map((d) => `<code>${esc(d)}</code>`).join(', ') || '—';
|
const domainChips = (meta.domains || []).map((d) =>
|
||||||
|
`<span class="infra-domain-chip">${esc(d)}</span>`
|
||||||
|
).join('') || '<span class="ticket-meta">Nenhum</span>';
|
||||||
const canGen = meta.can_generate && typeof canManageUsers === 'function' && canManageUsers();
|
const canGen = meta.can_generate && typeof canManageUsers === 'function' && canManageUsers();
|
||||||
let codesHtml = '';
|
let codesHtml = '';
|
||||||
if (canGen) {
|
if (canGen) {
|
||||||
const data = await api('/v1/infra/purge-auth-codes?limit=20');
|
const data = await api('/v1/infra/purge-auth-codes?limit=20');
|
||||||
const rows = (data.codes || []).map((c) => `
|
const rows = (data.codes || []).map((c) => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${esc(c.domain)}</td>
|
<td><code>${esc(c.domain)}</code></td>
|
||||||
<td>${esc(c.note || '—')}</td>
|
<td>${esc(c.note || '—')}</td>
|
||||||
<td>${fmtDate(c.expires_at)}</td>
|
<td>${fmtDate(c.expires_at)}</td>
|
||||||
<td>${esc(c.created_by || '—')}</td>
|
<td>${esc(c.created_by || '—')}</td>
|
||||||
</tr>`).join('');
|
</tr>`).join('');
|
||||||
codesHtml = `
|
codesHtml = `
|
||||||
|
<p class="infra-hint">Gere código com senha Root — use na conferência antes do purge em Serviços.</p>
|
||||||
|
<div class="infra-domain-chips">${domainChips}</div>
|
||||||
<form id="purge-auth-generate-form" class="purge-auth-form">
|
<form id="purge-auth-generate-form" class="purge-auth-form">
|
||||||
<label>Domínio</label>
|
<div>
|
||||||
<input type="text" id="purge-auth-domain" class="cf-select" placeholder="myvexx.com" required />
|
<label for="purge-auth-domain">Domínio</label>
|
||||||
<label>Nota (conferência / ticket)</label>
|
<input type="text" id="purge-auth-domain" placeholder="myvexx.com" required />
|
||||||
<input type="text" id="purge-auth-note" class="cf-select" placeholder="Autorizado em call com Roger" />
|
</div>
|
||||||
<label>Validade (horas)</label>
|
<div>
|
||||||
<input type="number" id="purge-auth-ttl" class="cf-select" value="24" min="1" max="168" />
|
<label for="purge-auth-ttl">Validade (horas)</label>
|
||||||
<label>Senha Root</label>
|
<input type="number" id="purge-auth-ttl" value="24" min="1" max="168" />
|
||||||
<input type="password" id="purge-auth-root-pwd" class="cf-select" autocomplete="current-password" required />
|
</div>
|
||||||
<button type="submit" class="btn secondary">Gerar código</button>
|
<div style="grid-column:1/-1">
|
||||||
|
<label for="purge-auth-note">Nota (conferência / ticket)</label>
|
||||||
|
<input type="text" id="purge-auth-note" placeholder="Autorizado em call com Roger" />
|
||||||
|
</div>
|
||||||
|
<div style="grid-column:1/-1">
|
||||||
|
<label for="purge-auth-root-pwd">Senha Root</label>
|
||||||
|
<input type="password" id="purge-auth-root-pwd" autocomplete="current-password" required />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn secondary btn-sm">Gerar código</button>
|
||||||
</form>
|
</form>
|
||||||
<p id="purge-auth-gen-msg" class="ticket-meta"></p>
|
<p id="purge-auth-gen-msg" class="ticket-meta"></p>
|
||||||
<div id="purge-auth-generated" class="purge-auth-generated hidden"></div>
|
<div id="purge-auth-generated" class="purge-auth-generated hidden"></div>
|
||||||
<h4 style="margin-top:1rem">Códigos activos</h4>
|
<h4 style="margin:0.85rem 0 0.35rem;font-size:0.78rem;text-transform:uppercase;color:#64748b">Códigos activos</h4>
|
||||||
<table class="purge-history-table">
|
<div class="infra-table-wrap">
|
||||||
<thead><tr><th>Domínio</th><th>Nota</th><th>Expira</th><th>Por</th></tr></thead>
|
<table class="purge-history-table">
|
||||||
<tbody>${rows || '<tr><td colspan="4">Nenhum código activo</td></tr>'}</tbody>
|
<thead><tr><th>Domínio</th><th>Nota</th><th>Expira</th><th>Por</th></tr></thead>
|
||||||
</table>`;
|
<tbody>${rows || '<tr><td colspan="4">Nenhum código activo</td></tr>'}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
codesHtml = '<p class="ticket-meta">Apenas <strong>super_admin (root)</strong> gera códigos. Peça o código ao root antes do purge em Serviços.</p>';
|
codesHtml = `
|
||||||
|
<div class="infra-domain-chips">${domainChips}</div>
|
||||||
|
<p class="infra-hint">Apenas <strong>super_admin (root)</strong> gera códigos. Peça o código ao root antes do purge em Serviços.</p>`;
|
||||||
}
|
}
|
||||||
panel.innerHTML = `
|
panel.innerHTML = codesHtml;
|
||||||
<p><strong>Domínios protegidos:</strong> ${domains}</p>
|
|
||||||
${codesHtml}`;
|
|
||||||
const form = panel.querySelector('#purge-auth-generate-form');
|
const form = panel.querySelector('#purge-auth-generate-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', async (ev) => {
|
form.addEventListener('submit', async (ev) => {
|
||||||
|
|
|
||||||
|
|
@ -3771,6 +3771,7 @@ button.health-card {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px dashed #cbd5e1;
|
border: 1px dashed #cbd5e1;
|
||||||
}
|
}
|
||||||
|
.servicos-tile-icon {
|
||||||
font-size: 1.35rem;
|
font-size: 1.35rem;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
@ -3888,29 +3889,224 @@ button.health-card {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
.purge-auth-generated {
|
|
||||||
margin: 0.75rem 0;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: rgba(46, 125, 50, 0.12);
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.purge-auth-generated.hidden { display: none; }
|
|
||||||
.purge-auth-code-display {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
}
|
|
||||||
.purge-auth-form {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.5rem;
|
|
||||||
max-width: 28rem;
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
.purge-history-removed {
|
.purge-history-removed {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--muted, #6b7280);
|
color: var(--muted, #6b7280);
|
||||||
max-width: 14rem;
|
max-width: 14rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Infra — layout tipo wizard (ws-panel + aqua/teal) */
|
||||||
|
.infra-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
.infra-hero {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.65rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.infra-hero-chip {
|
||||||
|
flex: 1 1 200px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: linear-gradient(135deg, #f0fdfa 0%, #fff 55%);
|
||||||
|
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
.infra-hero-chip--alert {
|
||||||
|
background: linear-gradient(135deg, #fff7ed 0%, #fff 55%);
|
||||||
|
border-color: #fed7aa;
|
||||||
|
}
|
||||||
|
.infra-hero-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #14b8a6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.25);
|
||||||
|
}
|
||||||
|
.infra-hero-dot--warn { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.25); }
|
||||||
|
.infra-hero-dot--bad { background: #ef4444; box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.25); }
|
||||||
|
.infra-hero-body strong {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: #0f172a;
|
||||||
|
}
|
||||||
|
.infra-hero-body span {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #64748b;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.infra-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
.infra-panel--wide { grid-column: 1 / -1; }
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.infra-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
.infra-panel .ws-panel-body { padding: 0.85rem 1rem; }
|
||||||
|
.infra-kv {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
gap: 0.65rem 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.infra-kv div {
|
||||||
|
padding: 0.55rem 0.65rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.infra-kv dt {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.infra-kv dd {
|
||||||
|
margin: 0.2rem 0 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0f172a;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.infra-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
.infra-hint {
|
||||||
|
margin: 0.65rem 0 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
.infra-alert-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0.65rem 0 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
.infra-alert-list li {
|
||||||
|
padding: 0.45rem 0.65rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
.ws-panel-head--aqua {
|
||||||
|
background: linear-gradient(135deg, #0891b2 0%, #22d3ee 45%, #2dd4bf 100%);
|
||||||
|
}
|
||||||
|
.ws-panel-head--slate {
|
||||||
|
background: linear-gradient(90deg, #334155, #64748b);
|
||||||
|
}
|
||||||
|
.ws-panel-head--violet {
|
||||||
|
background: linear-gradient(90deg, #6d28d9, #8b5cf6);
|
||||||
|
}
|
||||||
|
.ws-panel-head--rose {
|
||||||
|
background: linear-gradient(90deg, #be123c, #f43f5e);
|
||||||
|
}
|
||||||
|
.infra-domain-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.infra-domain-chip {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
padding: 0.25rem 0.55rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ecfeff;
|
||||||
|
border: 1px solid #99f6e4;
|
||||||
|
color: #0f766e;
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
}
|
||||||
|
.purge-auth-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.65rem 0.85rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.85rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, #f0fdfa 0%, #f8fafc 100%);
|
||||||
|
border: 1px solid #ccfbf1;
|
||||||
|
}
|
||||||
|
.purge-auth-form label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
.purge-auth-form input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 8px;
|
||||||
|
font: inherit;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.purge-auth-form input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #14b8a6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.2);
|
||||||
|
}
|
||||||
|
.purge-auth-form button {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
.purge-auth-generated {
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
padding: 1rem 1.1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #ecfdf5 0%, #f0fdfa 100%);
|
||||||
|
border: 1px solid #6ee7b7;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.purge-auth-generated.hidden { display: none; }
|
||||||
|
.purge-auth-code-display {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
color: #0f766e;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.infra-table-wrap {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.infra-table-wrap table {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.infra-table-wrap th {
|
||||||
|
background: #f1f5f9;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
}
|
||||||
|
.infra-table-wrap td {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
.infra-json-panel pre.raw {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 240px;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
/* Spec 021 — Acesso utilizador (separado do VM112 Onboard) */
|
/* Spec 021 — Acesso utilizador (separado do VM112 Onboard) */
|
||||||
.ws-access-zone {
|
.ws-access-zone {
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 1.25rem;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue