feat: add initial setup wizard, workload insights, and policy audit mode
Add setup wizard with status tracking via SystemSetting model, implement /setup/status and /setup/complete endpoints to create initial admin user and optional cluster configuration, add workload insights endpoint with traffic analysis and policy matching including audit mode detection, implement enforcement_mode property on Policy model with audit/enforced states, add Modal component for dialogs, create SetupWizard page with multi
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Activity, Server } from "lucide-react";
|
||||
|
||||
import { api, Workload, WorkloadInsight } from "../api/client";
|
||||
import { DataTable } from "../components/DataTable";
|
||||
import { secondaryButtonClass } from "../components/FormControls";
|
||||
import { PageHeader } from "../components/PageHeader";
|
||||
|
||||
export function Workloads() {
|
||||
const workloads = useQuery({ queryKey: ["workloads"], queryFn: () => api<Workload[]>("/vms") });
|
||||
const [selectedId, setSelectedId] = useState("");
|
||||
const selected = selectedId || workloads.data?.[0]?.id || "";
|
||||
const insight = useQuery({
|
||||
queryKey: ["workload-insight", selected],
|
||||
queryFn: () => api<WorkloadInsight>(`/vms/${selected}/insights`),
|
||||
enabled: Boolean(selected),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="VMs/LXCs" subtitle="Inspect workloads, observed traffic, and matching policy decisions." />
|
||||
<div className="grid gap-4 xl:grid-cols-[1fr_440px]">
|
||||
<section className="space-y-3">
|
||||
<DataTable
|
||||
rows={(workloads.data ?? []) as unknown as Record<string, unknown>[]}
|
||||
columns={[{ key: "name", label: "Name" }, { key: "kind", label: "Kind" }, { key: "status", label: "Status" }, { key: "external_id", label: "VMID" }]}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(workloads.data ?? []).map((workload) => (
|
||||
<button key={workload.id} className={secondaryButtonClass} onClick={() => setSelectedId(workload.id)}>
|
||||
<Server size={16} />
|
||||
{workload.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<aside className="rounded-md border border-border bg-panel p-4">
|
||||
<div className="mb-4 flex items-center gap-2 font-medium"><Activity size={18} /> Workload Detail</div>
|
||||
{insight.data ? (
|
||||
<div className="space-y-4 text-sm">
|
||||
<div>
|
||||
<div className="text-lg font-semibold">{insight.data.workload.name}</div>
|
||||
<div className="text-slate-500">{insight.data.workload.kind} · {insight.data.workload.status} · decision {insight.data.effective_decision}</div>
|
||||
</div>
|
||||
<section>
|
||||
<div className="mb-2 font-medium">Traffic</div>
|
||||
<div className="space-y-2">
|
||||
{insight.data.traffic.map((flow, index) => (
|
||||
<div key={index} className="rounded-md border border-border p-3">
|
||||
<div>{String(flow.source)} → {String(flow.destination)}</div>
|
||||
<div className="text-xs text-slate-500">{String(flow.protocol)}:{String(flow.port)} · {String(flow.bytes)} bytes · {String(flow.decision)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div className="mb-2 font-medium">Matching Policies</div>
|
||||
<div className="space-y-2">
|
||||
{insight.data.matching_policies.map((policy) => (
|
||||
<div key={policy.id} className="rounded-md border border-border p-3">
|
||||
<div>{policy.name}</div>
|
||||
<div className="text-xs text-slate-500">v{policy.version} · {policy.enforcement_mode}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
{insight.data.audit_mode_notes.length ? (
|
||||
<section>
|
||||
<div className="mb-2 font-medium">Audit Mode</div>
|
||||
{insight.data.audit_mode_notes.map((note) => <div key={note} className="rounded-md border border-border p-3 text-xs">{note}</div>)}
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-slate-500">Select a workload.</div>
|
||||
)}
|
||||
</aside>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user