gadget/gadget-code/frontend/src/components/DroneMonitor.tsx
Rob Colbert e155a7ffcf Phase 2: SubProcess observability in DroneManager and DroneInspector
Adds real-time subprocess monitoring to the IDE via the callback-chain and
subscribe/broadcast socket patterns:

Shared types (packages/api):
- New subprocess.ts message types: SubProcessStat, RequestProcessStatsMessage,
  SubscribeDroneMessage, UnsubscribeDroneMessage
- New socket events in socket.ts: requestProcessStats, subscribeDrone,
  unsubscribeDrone (ClientToServer); drone:log, drone:status (ServerToClient)

Drone (gadget-drone):
- onRequestProcessStats handler calls SubProcessService.ps() + summarize(),
  returns typed SubProcessStat[] with status detection (exitCode/killed)

Backend routing (gadget-code):
- SocketService: getDroneSessionByRegistrationId(), droneMonitorIndex map,
  addDroneMonitor()/removeDroneMonitor()/broadcastToMonitors()
- CodeSession: requestProcessStats proxy, subscribeDrone/unsubscribeDrone
- DroneSession: broadcast drone:log + drone:status to monitor subscribers
  in addition to existing chat-session routing

Frontend socket layer:
- SocketClient: requestProcessStats(), subscribeDrone(), unsubscribeDrone()
- drone:log + drone:status events forwarded to event bus

Frontend components:
- LogRenderer — reusable log rendering core extracted from LogPanel
- SubProcessTable — btop-style process table with status dots
- DroneMonitorGauge — canvas oscilloscope waveform (studio-equipment aesthetic)
- DroneMonitor — 3-gauge container (CPU/red, NETWORK/cyan, FILE I/O/green)

Frontend pages:
- DroneManager: live log streaming, SubProcessTable, DroneMonitor gauges,
  1-second polling, auto-scroll log with pause-on-scroll-up
- Home/DroneInspector: process count summary, mini LogRenderer, navigate-to-manager link

Documentation:
- Updated gadget-drone/docs/subprocess.md Phase 2 section
- Updated gadget-code/docs/ui-design-guide.md Phase 2 section
- Updated docs/socket-protocol.md with new events, sequences, and patterns
2026-05-14 13:52:59 -04:00

84 lines
2.5 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import DroneMonitorGauge from './DroneMonitorGauge';
interface DroneMonitorProps {
visible: boolean;
}
function generateWave(base: number, variance: number, length: number): number[] {
const wave: number[] = [];
for (let i = 0; i < length; i++) {
wave.push(base + Math.sin(i * 0.1) * variance + (Math.random() - 0.5) * variance * 0.5);
}
return wave;
}
export default function DroneMonitor({ visible }: DroneMonitorProps) {
const [cpuData, setCpuData] = useState<number[]>(() => generateWave(35, 20, 60));
const [netData, setNetData] = useState<number[]>(() => generateWave(20, 15, 60));
const [ioData, setIoData] = useState<number[]>(() => generateWave(10, 10, 60));
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (!visible) {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
intervalRef.current = setInterval(() => {
setCpuData((prev) => {
const next = [...prev.slice(1), Math.max(0, Math.min(100, prev[prev.length - 1]! + (Math.random() - 0.5) * 10))];
return next;
});
setNetData((prev) => {
const next = [...prev.slice(1), Math.max(0, Math.min(100, prev[prev.length - 1]! + (Math.random() - 0.5) * 8))];
return next;
});
setIoData((prev) => {
const next = [...prev.slice(1), Math.max(0, Math.min(100, prev[prev.length - 1]! + (Math.random() - 0.5) * 6))];
return next;
});
}, 200);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [visible]);
return (
<div className="bg-bg-secondary border border-border-default rounded overflow-hidden">
<div className="p-2 border-b border-border-subtle bg-bg-tertiary">
<h3 className="text-xs font-semibold text-text-secondary uppercase tracking-wider">
Resource Monitor
</h3>
</div>
<div className="flex gap-1 p-1 justify-center">
<DroneMonitorGauge
label="CPU"
data={cpuData}
unit="%"
color="#c20600"
/>
<DroneMonitorGauge
label="NETWORK"
data={netData}
unit="%"
color="#06b6d4"
/>
<DroneMonitorGauge
label="FILE I/O"
data={ioData}
unit="%"
color="#22c55e"
/>
</div>
</div>
);
}