1
0
forked from wrenn/wrenn

Add per-capsule stats detail page with live CPU/RAM charts

- New detail page at /dashboard/capsules/[id] with Stats and Files tabs
- Stats tab shows capsule info card (status, template, CPU, memory, disk,
  started, idle timeout) and two stacked Chart.js charts with live values
- Metrics API client with 10s polling and moving-average smoothing
- Capsule ID in list table is now a clickable link to the detail page
- Layout breadcrumb header (Capsules > sb-xxx) with back navigation
- Fix metrics sampler: use v.PID() directly as Firecracker PID since
  unshare -m execs (not forks) through the bash/ip-netns-exec/firecracker
  chain, so all share the same PID. Removes unused findChildPID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 22:31:05 +06:00
parent 27ff828e60
commit ed7880bc6c
7 changed files with 665 additions and 65 deletions

View File

@ -20,6 +20,10 @@ export async function listCapsules(): Promise<ApiResult<Capsule[]>> {
return apiFetch('GET', '/api/v1/sandboxes');
}
export async function getCapsule(id: string): Promise<ApiResult<Capsule>> {
return apiFetch('GET', `/api/v1/sandboxes/${id}`);
}
export type CreateCapsuleParams = {
template?: string;
vcpus?: number;

View File

@ -0,0 +1,25 @@
import { apiFetch, type ApiResult } from '$lib/api/client';
export type MetricRange = '5m' | '10m' | '1h' | '6h' | '24h';
export type MetricPoint = {
timestamp_unix: number;
cpu_pct: number;
mem_bytes: number;
disk_bytes: number;
};
export type MetricsResponse = {
sandbox_id: string;
range: MetricRange;
points: MetricPoint[];
};
export async function fetchSandboxMetrics(id: string, range: MetricRange): Promise<ApiResult<MetricsResponse>> {
return apiFetch('GET', `/api/v1/sandboxes/${id}/metrics?range=${range}`);
}
export const METRIC_RANGES: MetricRange[] = ['5m', '10m', '1h', '6h', '24h'];
// All ranges poll every 10 seconds.
export const METRIC_POLL_INTERVAL = 10_000;