feat: add policy deployment status tracking with cluster-level rule state monitoring and live firewall rule version comparison
Add policy_read_payload helper to build policy response with deployment status, implement nexafabric_rule_version to parse policy ID and version from rule comments, add policy_deployment_status to check active/stale/partial/unresolved states by comparing expected rules from preview against live firewall rules per cluster with version matching, extend PolicyRead schema with
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { FormEvent, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Eye, GitBranch, Pencil, Play, Plus, Trash2 } from "lucide-react";
|
||||
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";
|
||||
@@ -22,6 +22,60 @@ function policyService(policy: Policy) {
|
||||
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 <CheckCircle2 size={15} />;
|
||||
}
|
||||
if (state === "audit") {
|
||||
return <Shield size={15} />;
|
||||
}
|
||||
if (["stale", "partial", "unresolved"].includes(state)) {
|
||||
return <AlertTriangle size={15} />;
|
||||
}
|
||||
return <Clock3 size={15} />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className={`inline-flex w-fit items-center gap-1.5 rounded-md border px-2 py-1 text-xs ${statusClass(status.state)}`}>
|
||||
<StatusIcon state={status.state} />
|
||||
{status.label}
|
||||
</span>
|
||||
<span className="text-xs text-slate-500">{detail}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const defaultPolicyForm = {
|
||||
project_id: "",
|
||||
name: "Web to DB",
|
||||
@@ -200,6 +254,7 @@ export function Policies() {
|
||||
{ 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) => <PolicyDeploymentStatus policy={row as unknown as Policy} /> },
|
||||
{ key: "version", label: "Version" },
|
||||
{
|
||||
key: "actions",
|
||||
|
||||
Reference in New Issue
Block a user