system-monitor: Workspace/Panel layout with a scope list (bTop / pm2 / dockers)
/system-monitor is now a WorkspaceView (like /music): a left ScopeList panel selects the scope over the 'monitor:scope' channel, the right MonitorMain panel renders it. Three scopes: - bTop — the existing system snapshot (CPU/mem/disks/top processes) - pm2 processes — new GET /api/system-monitor/pm2 (pm2 jlist → name/status/cpu/ mem/restarts/uptime table) - dockers — new GET /api/system-monitor/docker (docker ps → container cards with state/status/image/ports) Both new endpoints degrade gracefully to an error field. Persisted as screens/system-monitor; owner-only. Needs a restart (backend routes + rebundle) + hard-refresh to appear. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -141,3 +141,61 @@ systemMonitorRouter.get('/stats', async (ctx) => {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
// GET /pm2 — pm2 process list (the "pm2 processes" scope).
|
||||
systemMonitorRouter.get('/pm2', async (ctx) => {
|
||||
try {
|
||||
const { stdout } = await exec('pm2', ['jlist'], { maxBuffer: 8 * 1024 * 1024 });
|
||||
const list = JSON.parse(stdout) as Array<{
|
||||
pm_id?: number;
|
||||
name?: string;
|
||||
pid?: number;
|
||||
monit?: { cpu?: number; memory?: number };
|
||||
pm2_env?: { status?: string; restart_time?: number; pm_uptime?: number };
|
||||
}>;
|
||||
const now = Date.now();
|
||||
return ctx.json({
|
||||
processes: list.map((p) => {
|
||||
const env = p.pm2_env ?? {};
|
||||
return {
|
||||
id: p.pm_id ?? -1,
|
||||
name: p.name ?? '?',
|
||||
status: env.status ?? 'unknown',
|
||||
pid: p.pid ?? null,
|
||||
cpuPct: p.monit?.cpu ?? 0,
|
||||
memBytes: p.monit?.memory ?? 0,
|
||||
restarts: env.restart_time ?? 0,
|
||||
uptimeMs: env.status === 'online' && env.pm_uptime ? now - env.pm_uptime : 0,
|
||||
};
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
return ctx.json({ processes: [], error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /docker — running docker containers (the "dockers" scope).
|
||||
systemMonitorRouter.get('/docker', async (ctx) => {
|
||||
try {
|
||||
const { stdout } = await exec('docker', ['ps', '--no-trunc', '--format', '{{json .}}'], { maxBuffer: 8 * 1024 * 1024 });
|
||||
return ctx.json({
|
||||
containers: stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.map((line) => {
|
||||
const c = JSON.parse(line) as Record<string, string>;
|
||||
return {
|
||||
id: (c.ID ?? '').slice(0, 12),
|
||||
name: c.Names ?? '',
|
||||
image: c.Image ?? '',
|
||||
state: c.State ?? '',
|
||||
status: c.Status ?? '',
|
||||
ports: c.Ports ?? '',
|
||||
};
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
return ctx.json({ containers: [], error: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user