Add a GPU process table to the Performance tab showing which processes use the selected GPU, with per-process VRAM, GPU utilization, CPU, host memory, and command. Includes a kill endpoint (SIGTERM/SIGKILL, with optional parent kill) behind a confirm dialog. - nvcurve/hal/processes.py: NVML + /proc process enumeration - server.py: GET /api/processes, POST /api/processes/kill (refuses to signal init/kthreadd) - frontend: ProcessList component, API client, types - tests: test_processes.py - docs: API reference for the new endpoints
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
export interface DialogAction {
|
|
label: string;
|
|
onClick: () => void;
|
|
className?: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
interface Props {
|
|
message: string;
|
|
detail?: string;
|
|
confirmLabel?: string;
|
|
isDestructive?: boolean;
|
|
/** Required unless `actions` is provided (which replaces the single confirm button). */
|
|
onConfirm?: () => void;
|
|
onCancel: () => void;
|
|
/** When provided, rendered after Cancel instead of the single confirm button. */
|
|
actions?: DialogAction[];
|
|
}
|
|
|
|
export function ConfirmDialog({
|
|
message,
|
|
detail,
|
|
confirmLabel = "Confirm",
|
|
isDestructive = false,
|
|
onConfirm,
|
|
onCancel,
|
|
actions,
|
|
}: Props) {
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70"
|
|
onMouseDown={(e) => {
|
|
if (e.target === e.currentTarget) onCancel();
|
|
}}
|
|
>
|
|
<div className="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-6 w-96 max-w-[90vw]">
|
|
<h2 className="text-zinc-100 font-semibold text-sm mb-1">{message}</h2>
|
|
{detail && <p className="text-zinc-400 text-xs mb-4">{detail}</p>}
|
|
{!detail && <div className="mb-4" />}
|
|
<div className="flex justify-end gap-2">
|
|
<button
|
|
onClick={onCancel}
|
|
className="px-3 py-1.5 rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
{actions ? (
|
|
actions.map((a) => (
|
|
<button
|
|
key={a.label}
|
|
onClick={a.onClick}
|
|
disabled={a.disabled}
|
|
className={[
|
|
a.className ??
|
|
"px-3 py-1.5 rounded bg-zinc-800 hover:bg-zinc-700 text-zinc-200 text-xs font-semibold transition-colors",
|
|
a.disabled ? "opacity-40 cursor-not-allowed" : "",
|
|
].join(" ")}
|
|
>
|
|
{a.label}
|
|
</button>
|
|
))
|
|
) : (
|
|
<button
|
|
onClick={() => onConfirm?.()}
|
|
className={[
|
|
"px-3 py-1.5 rounded text-xs font-semibold transition-colors",
|
|
isDestructive
|
|
? "bg-red-600 hover:bg-red-500 text-white"
|
|
: "bg-emerald-600 hover:bg-emerald-500 text-white",
|
|
].join(" ")}
|
|
>
|
|
{confirmLabel}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|