Watch
1
0
Fork
You've already forked RedFlag
0

desktop: put this computer health first

The Agent now owns one local view of machine health and top processes. RedFlag opens at dashboard scale, names itself plainly, and renders that evidence before updates.\n\nCI now compiles the Tauri bridge before release day.
This commit is contained in:
Fimeg 2026-08-31 21:20:00 -04:00
commit ffa7afbb58
6 changed files with 302 additions and 20 deletions

View file

@ -124,6 +124,38 @@ interface ScanSnapshot {
updates: UpdateItem[]
}
interface SystemInfo {
hostname: string
os_type: string
os_version: string
os_architecture: string
ip_address: string
cpu_info: { model_name: string; cores: number; threads: number }
memory_info: { total: number; available: number; used: number; used_percent: number }
disk_info: Array<{
mountpoint: string
total: number
available: number
used: number
used_percent: number
filesystem: string
is_root: boolean
}>
running_processes: number
uptime: string
reboot_required: boolean
reboot_reason: string
device_type: string
device_model: string
os_distro: string
}
interface SystemSnapshot {
system: SystemInfo
top_processes: Array<{ name: string; pid: number; cpu: number; mem: number }>
collected_at: string
}
interface TriggerScanResponse {
accepted: boolean
error?: string
@ -157,6 +189,7 @@ type HealthState = 'healthy' | 'warning' | 'error'
const LocalAgentApp: React.FC = () => {
const [theme, setTheme] = useState<Theme>('dark')
const [snapshot, setSnapshot] = useState<LocalSnapshot | null>(null)
const [system, setSystem] = useState<SystemSnapshot | null>(null)
const [updates, setUpdates] = useState<UpdateItem[]>([])
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(true)
@ -168,10 +201,14 @@ const LocalAgentApp: React.FC = () => {
const load = useCallback(async () => {
try {
setError(null)
const next = await invoke<LocalSnapshot>('local_status')
const [next, health, scan] = await Promise.all([
invoke<LocalSnapshot>('local_status'),
invoke<SystemSnapshot>('system_health'),
invoke<ScanSnapshot>('list_updates'),
])
setSnapshot(next)
setSystem(health)
setLastRefresh(new Date())
const scan = await invoke<ScanSnapshot>('list_updates')
setUpdates(scan.updates ?? [])
} catch (err) {
setError(err instanceof Error ? err.message : String(err))
@ -182,7 +219,7 @@ const LocalAgentApp: React.FC = () => {
useEffect(() => {
load()
const id = window.setInterval(load, 5000)
const id = window.setInterval(load, 15000)
return () => window.clearInterval(id)
}, [load])
@ -213,8 +250,11 @@ const LocalAgentApp: React.FC = () => {
if (error || !snapshot) return 'error'
if (snapshot.identity.registered && !snapshot.status.registered) return 'warning'
if (snapshot.status.agent_status && snapshot.status.agent_status !== 'online' && snapshot.identity.registered) return 'warning'
if (system?.system.reboot_required) return 'warning'
if ((system?.system.memory_info.used_percent ?? 0) >= 90) return 'warning'
if (system?.system.disk_info.some(disk => disk.used_percent >= 90)) return 'warning'
return (snapshot.status.summary.by_severity?.critical ?? 0) > 0 ? 'warning' : 'healthy'
}, [error, snapshot])
}, [error, snapshot, system])
const scanners = useMemo(() => {
if (!snapshot?.status.scanners) return []
@ -223,18 +263,26 @@ const LocalAgentApp: React.FC = () => {
const healthDot = health === 'healthy' ? p.good : health === 'warning' ? p.warn : p.bad
const healthLabel = health === 'healthy' ? 'Online' : health === 'warning' ? 'Warning' : 'Error'
const rootDisk = system?.system.disk_info.find(disk => disk.is_root)
?? system?.system.disk_info[0]
const memoryColor = (system?.system.memory_info.used_percent ?? 0) >= 90
? p.bad
: (system?.system.memory_info.used_percent ?? 0) >= 80 ? p.warn : p.good
const diskColor = (rootDisk?.used_percent ?? 0) >= 90
? p.bad
: (rootDisk?.used_percent ?? 0) >= 80 ? p.warn : p.good
return (
<div style={{ background: p.bg, color: p.text, fontFamily: "'Inter', system-ui, sans-serif", minHeight: '100vh', fontSize: '13px' }}>
{/* Title bar */}
<div style={{ background: p.bgHeader, borderBottom: `1px solid ${p.border}`, padding: '10px 14px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{/* RedFlag is the product; this computer is the first surface. */}
<div style={{ background: p.bgHeader, borderBottom: `1px solid ${p.border}`, padding: '14px 18px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ width: '8px', height: '8px', borderRadius: '50%', background: healthDot, flexShrink: 0 }} />
<span style={{ fontWeight: 600, fontSize: '13px', color: theme === 'dark' ? '#f3f4f6' : '#111827' }}>
{snapshot?.identity.display_name || snapshot?.identity.hostname || 'RedFlag Agent'}
</span>
<span style={{ color: p.textDim, fontSize: '11px' }}>{healthLabel}</span>
<div style={{ width: '4px', height: '28px', background: p.accent, borderRadius: '2px' }} />
<div>
<div style={{ fontWeight: 750, fontSize: '19px', letterSpacing: '-0.02em', color: theme === 'dark' ? '#f9fafb' : '#111827' }}>RedFlag</div>
<div style={{ color: p.textDim, fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.12em' }}>This Computer</div>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<button
@ -259,18 +307,63 @@ const LocalAgentApp: React.FC = () => {
</div>
</div>
<div style={{ padding: '18px', borderBottom: `1px solid ${p.border}`, background: p.bgAlt }}>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '20px', alignItems: 'flex-start' }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{ width: '9px', height: '9px', borderRadius: '50%', background: healthDot }} />
<span style={{ fontSize: '20px', fontWeight: 650, color: p.text }}>
{snapshot?.identity.display_name || system?.system.hostname || snapshot?.identity.hostname || 'This computer'}
</span>
</div>
<div style={{ marginTop: '4px', color: p.textMuted, fontSize: '12px' }}>
{system?.system.device_model || system?.system.device_type || 'Computer'} · {system?.system.os_version || snapshot?.identity.os_type || 'Unknown OS'} · {healthLabel}
</div>
</div>
{system?.system.reboot_required && (
<div style={{ border: `1px solid ${p.warn}`, color: p.warn, borderRadius: '4px', padding: '5px 9px', fontSize: '11px' }}>
Reboot required{system.system.reboot_reason ? ` · ${system.system.reboot_reason}` : ''}
</div>
)}
</div>
</div>
{error && (
<div style={{ background: p.errorBg, border: `1px solid ${p.errorBorder}`, borderLeft: `3px solid ${p.bad}`, margin: '10px 12px', padding: '7px 10px', borderRadius: '3px', fontSize: '12px', color: p.errorText, fontFamily: 'monospace' }}>
{error}
</div>
)}
{/* Status strip */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1px', background: p.border, borderBottom: `1px solid ${p.border}` }}>
<StatCell p={p} label="Mode" value={snapshot?.identity.registered ? 'Fleet-joined' : 'Standalone'} accent={snapshot?.identity.registered ? p.good : p.text} />
{/* System health is the landing view. */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, minmax(0, 1fr))', gap: '1px', background: p.border, borderBottom: `1px solid ${p.border}` }}>
<StatCell p={p} label="Memory" value={system ? `${system.system.memory_info.used_percent.toFixed(0)}%` : '—'} accent={memoryColor} />
<StatCell p={p} label="Root disk" value={rootDisk ? `${rootDisk.used_percent.toFixed(0)}%` : '—'} accent={diskColor} />
<StatCell p={p} label="Processes" value={system ? String(system.system.running_processes) : '—'} accent={p.text} />
<StatCell p={p} label="Uptime" value={system?.system.uptime || '—'} accent={p.text} />
<StatCell p={p} label="Updates" value={String(snapshot?.status.update_count ?? 0)} accent={(snapshot?.status.update_count ?? 0) > 0 ? p.warn : p.good} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.15fr 0.85fr', borderBottom: `1px solid ${p.border}` }}>
<Section p={p} label="Computer health">
<Row p={p} label="Processor" value={system?.system.cpu_info.model_name || '—'} />
<Row p={p} label="CPU" value={system ? `${system.system.cpu_info.cores} cores · ${system.system.cpu_info.threads} threads` : '—'} />
<Row p={p} label="Memory" value={system ? `${formatBytes(system.system.memory_info.used)} of ${formatBytes(system.system.memory_info.total)}` : '—'} />
<Row p={p} label="Root storage" value={rootDisk ? `${formatBytes(rootDisk.used)} of ${formatBytes(rootDisk.total)} · ${rootDisk.filesystem}` : '—'} />
<Row p={p} label="Network" value={system?.system.ip_address || '—'} mono />
</Section>
<Section p={p} label="Top processes">
{(system?.top_processes.length ?? 0) === 0 ? (
<div style={{ padding: '10px 14px', color: p.textDim, fontSize: '12px' }}>Process data unavailable.</div>
) : system?.top_processes.slice(0, 6).map(process => (
<div key={process.pid} style={{ display: 'grid', gridTemplateColumns: '1fr 54px 54px', gap: '8px', padding: '4px 14px', borderBottom: `1px solid ${p.borderInner}`, fontSize: '11px' }}>
<span style={{ fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{process.name}</span>
<span style={{ color: p.textDim, textAlign: 'right' }}>{process.cpu.toFixed(1)}% CPU</span>
<span style={{ color: p.textDim, textAlign: 'right' }}>{process.mem.toFixed(1)}% RAM</span>
</div>
))}
</Section>
</div>
{/* Pending updates — the standalone admin surface */}
<UpdatesSection
p={p}
@ -560,4 +653,12 @@ function maybeRelative(value?: string): string {
return formatRelativeTime(value)
}
function formatBytes(value: number): string {
if (!Number.isFinite(value) || value <= 0) return '0 B'
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
const unit = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1)
const amount = value / (1024 ** unit)
return `${amount >= 10 || unit === 0 ? amount.toFixed(0) : amount.toFixed(1)} ${units[unit]}`
}
export default LocalAgentApp