feat: add comprehensive CRUD endpoints, cluster sync improvements, and firewall orchestration
Add create endpoints for users, roles, tenants, projects, networks, subnets, and security rules with audit logging, implement commit_or_400 helper for IntegrityError handling with 409 responses, enhance cluster sync to populate nodes, workloads, and networks from provider inventory with last_sync_at tracking, add update/delete operations for IP addresses and policies with version tracking, implement IP
This commit is contained in:
@@ -1,33 +1,68 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Play } from "lucide-react";
|
||||
import { Play, ShieldCheck } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { api, Policy } from "../api/client";
|
||||
import { api, Cluster, Policy } from "../api/client";
|
||||
import { buttonClass, Field, secondaryButtonClass, selectClass } from "../components/FormControls";
|
||||
import { PageHeader } from "../components/PageHeader";
|
||||
|
||||
export function FirewallPreview() {
|
||||
const policies = useQuery({ queryKey: ["policies"], queryFn: () => api<Policy[]>("/policies") });
|
||||
const clusters = useQuery({ queryKey: ["clusters"], queryFn: () => api<Cluster[]>("/clusters") });
|
||||
const [policyId, setPolicyId] = useState("");
|
||||
const [clusterId, setClusterId] = useState("");
|
||||
const [dryRun, setDryRun] = useState(true);
|
||||
const preview = useMutation({
|
||||
mutationFn: (policyId: string) => api<Record<string, unknown>>(`/firewall/preview/${policyId}`, { method: "POST" }),
|
||||
});
|
||||
const firstPolicy = policies.data?.[0];
|
||||
const apply = useMutation({
|
||||
mutationFn: () =>
|
||||
api<Record<string, unknown>>("/firewall/apply", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
policy_id: policyId || policies.data?.[0]?.id,
|
||||
cluster_id: clusterId || clusters.data?.[0]?.id,
|
||||
confirm: true,
|
||||
dry_run: dryRun,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
const selectedPolicyId = policyId || policies.data?.[0]?.id || "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Firewall Preview" subtitle="Compile policies into provider-specific rules before any apply operation." />
|
||||
<div className="rounded-md border border-border bg-panel p-4">
|
||||
<button
|
||||
className="inline-flex h-10 items-center gap-2 rounded-md bg-accent px-4 text-sm text-white disabled:opacity-50"
|
||||
disabled={!firstPolicy}
|
||||
onClick={() => firstPolicy && preview.mutate(firstPolicy.id)}
|
||||
>
|
||||
<Play size={18} />
|
||||
Generate Preview
|
||||
</button>
|
||||
<pre className="mt-4 max-h-[520px] overflow-auto rounded-md border border-border bg-canvas p-4 text-xs">
|
||||
{preview.data ? JSON.stringify(preview.data, null, 2) : "No preview generated yet."}
|
||||
<div className="grid gap-4 lg:grid-cols-[360px_1fr]">
|
||||
<section className="rounded-md border border-border bg-panel p-4">
|
||||
<div className="grid gap-3">
|
||||
<Field label="Policy">
|
||||
<select className={selectClass} value={selectedPolicyId} onChange={(event) => setPolicyId(event.target.value)}>
|
||||
{(policies.data ?? []).map((policy) => <option key={policy.id} value={policy.id}>{policy.name}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<Field label="Cluster">
|
||||
<select className={selectClass} value={clusterId || clusters.data?.[0]?.id || ""} onChange={(event) => setClusterId(event.target.value)}>
|
||||
{(clusters.data ?? []).map((cluster) => <option key={cluster.id} value={cluster.id}>{cluster.name}</option>)}
|
||||
</select>
|
||||
</Field>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={dryRun} onChange={(event) => setDryRun(event.target.checked)} />
|
||||
Dry run
|
||||
</label>
|
||||
<button className={secondaryButtonClass} disabled={!selectedPolicyId} onClick={() => preview.mutate(selectedPolicyId)}>
|
||||
<Play size={18} />
|
||||
Generate Preview
|
||||
</button>
|
||||
<button className={buttonClass} disabled={!selectedPolicyId || apply.isPending} onClick={() => apply.mutate()}>
|
||||
<ShieldCheck size={18} />
|
||||
Apply Confirmed
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<pre className="max-h-[620px] overflow-auto rounded-md border border-border bg-panel p-4 text-xs">
|
||||
{apply.data ? JSON.stringify(apply.data, null, 2) : preview.data ? JSON.stringify(preview.data, null, 2) : "No firewall output yet."}
|
||||
</pre>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user