import { FormEvent, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertTriangle, CheckCircle2, Clock3, Eye, GitBranch, Pencil, Play, Plus, Shield, Trash2 } from "lucide-react"; import { api, Policy, Project, ServiceCatalogItem } from "../api/client"; import { DataTable } from "../components/DataTable"; import { buttonClass, Field, iconButtonClass, inputClass, selectClass } from "../components/FormControls"; import { LoadingOverlay } from "../components/LoadingOverlay"; import { Modal } from "../components/Modal"; import { PageHeader } from "../components/PageHeader"; function policyValue(policy: Policy, key: string) { return String(policy.definition?.[key] ?? ""); } function policyService(policy: Policy) { const service = policy.definition?.service; if (!service || typeof service !== "object") { return ""; } const value = service as { protocol?: unknown; ports?: unknown }; return `${String(value.protocol ?? "")}/${String(value.ports ?? "")}`; } function policyStatus(policy: Policy) { const status = policy.deployment_status ?? {}; return { state: String(status.state ?? "unknown"), label: String(status.label ?? "Unknown"), expectedRules: Number(status.expected_rules ?? 0), activeRules: Number(status.active_rules ?? 0), staleRules: Number(status.stale_rules ?? 0), }; } function statusClass(state: string) { if (state === "active") { return "border-accent/40 bg-accent/10 text-accent"; } if (state === "audit") { return "border-amber-400/40 bg-amber-400/10 text-amber-300"; } if (["stale", "partial", "unresolved"].includes(state)) { return "border-danger/40 bg-danger/10 text-danger"; } return "border-border bg-canvas text-slate-500"; } function StatusIcon({ state }: { state: string }) { if (state === "active") { return ; } if (state === "audit") { return ; } if (["stale", "partial", "unresolved"].includes(state)) { return ; } return ; } function PolicyDeploymentStatus({ policy }: { policy: Policy }) { const status = policyStatus(policy); const detail = status.state === "audit" ? "No live firewall write" : `${status.activeRules}/${status.expectedRules} active${status.staleRules ? ` ยท ${status.staleRules} stale` : ""}`; return (
{status.label} {detail}
); } const defaultPolicyForm = { project_id: "", name: "Web to DB", source: "sg:Web Tier", destination: "sg:Database", service_id: "", protocol: "tcp", ports: "5432", action: "allow", direction: "egress", enforcement_mode: "enforced", logging: true, description: "Allow application database traffic", }; export function Policies() { const queryClient = useQueryClient(); const policies = useQuery({ queryKey: ["policies"], queryFn: () => api("/policies") }); const projects = useQuery({ queryKey: ["projects"], queryFn: () => api("/projects") }); const services = useQuery({ queryKey: ["service-catalog"], queryFn: () => api("/service-catalog") }); const [preview, setPreview] = useState(""); const [open, setOpen] = useState(false); const [editing, setEditing] = useState(null); const [form, setForm] = useState(defaultPolicyForm); const [busyMessage, setBusyMessage] = useState(""); const save = useMutation({ mutationFn: () => { const service = services.data?.find((item) => item.id === form.service_id); return api(editing ? `/policies/${editing.id}` : "/policies", { method: editing ? "PATCH" : "POST", body: JSON.stringify({ project_id: form.project_id || null, name: form.name, enabled: true, definition: { source: form.source, destination: form.destination, service: { protocol: service?.protocol ?? form.protocol, ports: service?.ports ?? form.ports }, action: form.action, direction: form.direction, enforcement_mode: form.enforcement_mode, logging: form.logging, description: form.description, }, }), }); }, onSuccess: () => { setOpen(false); setEditing(null); queryClient.invalidateQueries({ queryKey: ["policies"] }); }, }); const remove = useMutation({ mutationFn: (policy: Policy) => api(`/policies/${policy.id}`, { method: "DELETE" }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ["policies"] }), }); async function submit(event: FormEvent) { event.preventDefault(); await save.mutateAsync(); } async function compile(policy: Policy) { setBusyMessage(`Compiling ${policy.name}...`); try { const data = await api(`/policies/${policy.id}/compile`, { method: "POST" }); setPreview(JSON.stringify(data.last_compiled, null, 2)); await queryClient.invalidateQueries({ queryKey: ["policies"] }); } finally { setBusyMessage(""); } } async function firewallPreview(policy: Policy) { setBusyMessage(`Generating preview for ${policy.name}...`); try { const data = await api>(`/firewall/preview/${policy.id}`, { method: "POST" }); setPreview(JSON.stringify(data, null, 2)); } finally { setBusyMessage(""); } } function addPolicy() { setEditing(null); setForm(defaultPolicyForm); setOpen(true); } function editPolicy(policy: Policy) { const service = policy.definition?.service as { protocol?: string; ports?: string } | undefined; setEditing(policy); setForm({ project_id: policy.project_id ?? "", name: policy.name, source: policyValue(policy, "source") || "any", destination: policyValue(policy, "destination") || "any", service_id: "", protocol: service?.protocol ?? "tcp", ports: service?.ports ?? "", action: policyValue(policy, "action") || "allow", direction: policyValue(policy, "direction") || "ingress", enforcement_mode: policy.enforcement_mode || "enforced", logging: Boolean(policy.definition?.logging), description: policyValue(policy, "description"), }); setOpen(true); } function chooseService(serviceId: string) { const service = services.data?.find((item) => item.id === serviceId); setForm({ ...form, service_id: serviceId, protocol: service?.protocol ?? form.protocol, ports: service?.ports ?? form.ports, }); } function deletePolicy(policy: Policy) { if (window.confirm(`Delete policy "${policy.name}"?`)) { remove.mutate(policy); } } return ( <>
setOpen(false)}>
{editing ? "Edit Policy" : "Add Policy"}
setForm({ ...form, name: event.target.value })} />
setForm({ ...form, source: event.target.value })} /> setForm({ ...form, destination: event.target.value })} />
setForm({ ...form, ports: event.target.value })} />
setForm({ ...form, description: event.target.value })} />
[]} columns={[ { key: "name", label: "Policy" }, { key: "source", label: "Source", render: (row) => policyValue(row as unknown as Policy, "source") }, { key: "destination", label: "Destination", render: (row) => policyValue(row as unknown as Policy, "destination") }, { key: "service", label: "Service", render: (row) => policyService(row as unknown as Policy) }, { key: "enforcement_mode", label: "Mode" }, { key: "deployment_status", label: "Status", render: (row) => }, { key: "version", label: "Version" }, { key: "actions", label: "Actions", render: (row) => { const policy = row as unknown as Policy; return (
); }, }, ]} />
{preview || "No policy output yet."}
); }