Add Crucible rich terminal renderers and ignore local coverage dirs.

Listen ports table UI and styles; gitignore for spread-kits and cov artifact files.
This commit is contained in:
AetherForge
2026-05-31 01:14:10 -07:00
parent ea6f54ad03
commit babe644c94
3 changed files with 295 additions and 6 deletions

View File

@@ -715,3 +715,157 @@
letter-spacing: 0.05em;
}
.crucible-btn-muted:hover { color: var(--text-primary); border-color: rgba(255,255,255,0.25); }
/* ── Rich terminal renderers ──────────────────────────────────────────────── */
.rich-line {
align-items: flex-start !important;
}
.ctl-rich {
flex: 1;
overflow-x: auto;
}
.rich-block {
margin: 0.2rem 0 0.4rem 0;
padding: 0.5rem 0.75rem;
border-left: 2px solid rgba(0, 245, 255, 0.3);
background: rgba(0, 0, 0, 0.35);
border-radius: 0 4px 4px 0;
font-family: var(--font-tech);
font-size: 0.76rem;
max-width: 720px;
}
.rich-ports { border-left-color: rgba(0, 245, 255, 0.5); }
.rich-patch { border-left-color: rgba(255, 200, 0, 0.5); }
.rich-posture { border-left-color: rgba(150, 120, 255, 0.5); }
.rich-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.45rem;
}
.rich-label {
color: var(--neon-cyan);
font-weight: 700;
letter-spacing: 0.1em;
font-size: 0.72rem;
}
.rich-count {
color: var(--text-muted);
font-size: 0.68rem;
}
.rich-score {
font-weight: 700;
letter-spacing: 0.05em;
padding: 0.1rem 0.45rem;
border-radius: 3px;
font-size: 0.76rem;
}
.rich-score-ok { color: #00ff9d; background: rgba(0,255,100,0.1); }
.rich-score-warn { color: #ffc800; background: rgba(255,200,0,0.1); }
.rich-score-bad { color: #ff4444; background: rgba(255,50,0,0.12); }
.rich-tag {
font-size: 0.68rem;
padding: 0.1rem 0.4rem;
border-radius: 3px;
font-weight: 700;
letter-spacing: 0.08em;
}
.rich-tag-warn { color: #ff4444; background: rgba(255, 50, 0, 0.15); border: 1px solid rgba(255,50,0,0.3); }
.rich-empty {
color: var(--text-muted);
font-style: italic;
font-size: 0.72rem;
padding: 0.25rem 0;
}
/* Port/Patch/Posture tables */
.rich-table {
border-collapse: collapse;
width: 100%;
font-size: 0.74rem;
}
.rich-table th {
color: var(--text-muted);
text-align: left;
padding: 0.1rem 0.6rem 0.15rem 0;
letter-spacing: 0.08em;
font-size: 0.66rem;
border-bottom: 1px solid rgba(255,255,255,0.07);
white-space: nowrap;
}
.rich-table td {
padding: 0.18rem 0.6rem 0.18rem 0;
white-space: nowrap;
color: var(--text-primary);
}
.rich-table tbody tr:hover { background: rgba(255,255,255,0.03); }
.rich-row-ssh td { color: #00ff9d; }
.rich-row-sys td:first-child { color: var(--neon-cyan); }
.rich-port { font-weight: 700; min-width: 48px; color: var(--neon-cyan) !important; }
.rich-addr { color: var(--text-muted) !important; min-width: 100px; }
.rich-proto { color: rgba(180,160,255,0.85) !important; min-width: 50px; }
.rich-proc { color: var(--text-primary); min-width: 100px; }
.rich-pid { color: var(--text-muted) !important; font-size: 0.68rem; }
/* Key-value rows */
.rich-kv-row {
display: flex;
gap: 0.5rem;
padding: 0.12rem 0;
align-items: baseline;
}
.rich-key {
color: var(--text-muted);
min-width: 140px;
font-size: 0.71rem;
flex-shrink: 0;
}
.rich-val { color: var(--text-primary); font-size: 0.74rem; }
.rich-val-ok { color: #00ff9d; }
.rich-val-warn { color: #ffc800; }
.rich-val-bad { color: #ff4444; }
.rich-posture-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0 1.5rem;
}
.rich-sub-label {
margin: 0.5rem 0 0.2rem;
color: var(--neon-cyan);
font-size: 0.66rem;
letter-spacing: 0.1em;
opacity: 0.7;
}
.rich-svc-status {
font-weight: 700;
font-size: 0.7rem;
letter-spacing: 0.05em;
}
.blink-slow {
animation: blink-slow 1.5s step-end infinite;
}
@keyframes blink-slow {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}

View File

@@ -579,6 +579,137 @@ export default function CruciblePage() {
};
};
// ── Rich terminal renderers ────────────────────────────────────────────
const RichListenPortsTable = ({ d }: { d: RichListenPorts }) => (
<div className="rich-block rich-ports">
<div className="rich-header">
<span className="rich-label">LISTEN PORTS</span>
<span className="rich-count">{d.count} listener{d.count !== 1 ? 's' : ''}</span>
</div>
{d.ports.length === 0 ? (
<div className="rich-empty">No listeners found</div>
) : (
<table className="rich-table">
<thead>
<tr>
<th>PORT</th>
<th>ADDR</th>
<th>PROTO</th>
<th>PROCESS</th>
<th>PID</th>
</tr>
</thead>
<tbody>
{d.ports.map((p, i) => (
<tr key={i} className={p.port === 22 ? 'rich-row-ssh' : p.port < 1024 ? 'rich-row-sys' : ''}>
<td className="rich-port">{p.port}</td>
<td className="rich-addr">{p.addr || '*'}</td>
<td className="rich-proto">{p.proto.toUpperCase()}</td>
<td className="rich-proc">{p.process || '—'}</td>
<td className="rich-pid">{p.pid ?? '—'}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
const RichPatchStatusBlock = ({ d }: { d: RichPatchStatus }) => (
<div className="rich-block rich-patch">
<div className="rich-header">
<span className="rich-label">PATCH STATUS</span>
{d.reboot_pending && <span className="rich-tag rich-tag-warn blink-slow">REBOOT REQUIRED</span>}
</div>
<div className="rich-kv-row">
<span className="rich-key">Pending Updates</span>
<span className={`rich-val ${(d.pending_updates ?? 0) > 0 ? 'rich-val-warn' : 'rich-val-ok'}`}>
{d.pending_updates ?? 0}
</span>
</div>
<div className="rich-kv-row">
<span className="rich-key">Last Patch</span>
<span className={`rich-val ${(d.last_patch_days ?? 0) > 60 ? 'rich-val-bad' : (d.last_patch_days ?? 0) > 30 ? 'rich-val-warn' : 'rich-val-ok'}`}>
{d.last_patch ?? '—'}{d.last_patch_days != null ? ` (${d.last_patch_days}d ago)` : ''}
</span>
</div>
<div className="rich-kv-row">
<span className="rich-key">Reboot Pending</span>
<span className={`rich-val ${d.reboot_pending ? 'rich-val-warn' : 'rich-val-ok'}`}>
{d.reboot_pending ? 'YES' : 'NO'}
</span>
</div>
</div>
);
const fwIcon = (on?: boolean) => on ? <span className="rich-val-ok">ON</span> : <span className="rich-val-bad">OFF</span>;
const RichPostureSummaryBlock = ({ d }: { d: RichPostureSummary }) => (
<div className="rich-block rich-posture">
<div className="rich-header">
<span className="rich-label">POSTURE REPORT</span>
{d.posture_score != null && (
<span className={`rich-score ${d.posture_score >= 70 ? 'rich-score-ok' : d.posture_score >= 40 ? 'rich-score-warn' : 'rich-score-bad'}`}>
{d.posture_score} / 100
</span>
)}
</div>
<div className="rich-posture-grid">
<div className="rich-kv-row"><span className="rich-key">Defender</span>
<span className={`rich-val ${d.defender_enabled ? 'rich-val-ok' : 'rich-val-bad'}`}>{d.defender_enabled ? 'ON' : 'OFF'}</span>
</div>
<div className="rich-kv-row"><span className="rich-key">Real-Time Prot</span>
<span className={`rich-val ${d.defender_rtp ? 'rich-val-ok' : 'rich-val-warn'}`}>{d.defender_rtp ? 'ON' : 'OFF'}</span>
</div>
<div className="rich-kv-row"><span className="rich-key">Firewall Domain/Priv/Pub</span>
<span className="rich-val">{fwIcon(d.firewall_domain)} / {fwIcon(d.firewall_private)} / {fwIcon(d.firewall_public)}</span>
</div>
<div className="rich-kv-row"><span className="rich-key">AV Products</span>
<span className="rich-val">{d.av_products?.join(', ') || '—'}</span>
</div>
<div className="rich-kv-row"><span className="rich-key">SSH Listening</span>
<span className={`rich-val ${d.ssh_listening ? 'rich-val-ok' : 'rich-val-bad'}`}>{d.ssh_listening ? 'YES' : 'NO'}</span>
</div>
<div className="rich-kv-row"><span className="rich-key">Agent Elevated</span>
<span className={`rich-val ${d.agent_elevated ? 'rich-val-warn' : 'rich-val-ok'}`}>{d.agent_elevated ? 'YES (ADMIN)' : 'no'}</span>
</div>
{d.last_patch_days != null && (
<div className="rich-kv-row"><span className="rich-key">Last Patch</span>
<span className={`rich-val ${d.last_patch_days > 60 ? 'rich-val-bad' : d.last_patch_days > 30 ? 'rich-val-warn' : 'rich-val-ok'}`}>
{d.last_patch_days}d ago{d.pending_updates ? ` · ${d.pending_updates} pending` : ''}
{d.reboot_pending ? ' · REBOOT!' : ''}
</span>
</div>
)}
</div>
{d.services && d.services.length > 0 && (
<>
<div className="rich-sub-label">SERVICES</div>
<table className="rich-table">
<thead><tr><th>SERVICE</th><th>STATUS</th><th>START</th></tr></thead>
<tbody>
{d.services.map((s, i) => (
<tr key={i}>
<td className="rich-proc">{s.display_name || s.name}</td>
<td className={`rich-svc-status ${s.status === 'running' ? 'rich-val-ok' : 'rich-val-bad'}`}>{s.status.toUpperCase()}</td>
<td className="rich-addr">{s.start_type}</td>
</tr>
))}
</tbody>
</table>
</>
)}
</div>
);
const renderRichData = (d: RichTermData) => {
if (d.type === 'listen_ports') return <RichListenPortsTable d={d} />;
if (d.type === 'patch_status') return <RichPatchStatusBlock d={d} />;
if (d.type === 'posture') return <RichPostureSummaryBlock d={d} />;
return null;
};
// ── Render ─────────────────────────────────────────────────────────────
return (
@@ -929,7 +1060,7 @@ export default function CruciblePage() {
return (
<div
key={line.id}
className={`crucible-term-line ${line.isCmd ? 'cmd-line' : 'out-line'} ${line.success === false ? 'err-line' : ''}`}
className={`crucible-term-line ${line.isCmd ? 'cmd-line' : 'out-line'} ${line.success === false ? 'err-line' : ''} ${line.richData ? 'rich-line' : ''}`}
>
<span className="ctl-agent" style={{ color }}>
{line.agentName.slice(0, 12).padEnd(12)}
@@ -937,7 +1068,11 @@ export default function CruciblePage() {
<span className="ctl-arrow" style={{ color }}>
{line.isCmd ? '▶' : '◀'}
</span>
<span className="ctl-text">{line.text}</span>
{line.richData ? (
<span className="ctl-text ctl-rich">{renderRichData(line.richData)}</span>
) : (
<span className="ctl-text">{line.text}</span>
)}
<span className="ctl-ts">{line.ts.toLocaleTimeString()}</span>
</div>
);