feat: add container network filtering, enhance IP address display, and improve workload insights UI
Add is_docker_or_container_network helper to detect Docker bridge, Kubernetes CNI, and loopback networks, implement cleanup_discovered_container_networks to remove container bridge IPs from discovered networks during IPAM discovery, add ip_address_payload helper to enrich IP addresses with subnet CIDR and workload details, update ProxmoxProvider to ignore guest interfaces matching common container pref
This commit is contained in:
@@ -63,8 +63,8 @@ export function Ipam() {
|
||||
|
||||
async function discoverIpam() {
|
||||
try {
|
||||
const result = await api<{ imported: number; errors: Array<Record<string, unknown>> }>("/ipam/discover", { method: "POST" });
|
||||
setMessage(`Discovery imported ${result.imported} IP addresses${result.errors.length ? " with errors" : ""}.`);
|
||||
const result = await api<{ imported: number; removed: number; errors: Array<Record<string, unknown>> }>("/ipam/discover", { method: "POST" });
|
||||
setMessage(`Discovery imported ${result.imported} IP addresses and removed ${result.removed} container bridge entries${result.errors.length ? " with errors" : ""}.`);
|
||||
await queryClient.invalidateQueries({ queryKey: ["subnets"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["addresses"] });
|
||||
} catch (error) {
|
||||
@@ -122,7 +122,17 @@ export function Ipam() {
|
||||
</Modal>
|
||||
<section className="space-y-4">
|
||||
<DataTable rows={(subnets.data ?? []) as unknown as Record<string, unknown>[]} columns={[{ key: "cidr", label: "Subnet" }, { key: "gateway", label: "Gateway" }, { key: "dhcp_enabled", label: "DHCP" }]} />
|
||||
<DataTable rows={(addresses.data ?? []) as unknown as Record<string, unknown>[]} columns={[{ key: "address", label: "Address" }, { key: "status", label: "Status" }, { key: "note", label: "Note" }]} />
|
||||
<DataTable
|
||||
rows={(addresses.data ?? []) as unknown as Record<string, unknown>[]}
|
||||
columns={[
|
||||
{ key: "address", label: "Address" },
|
||||
{ key: "subnet_cidr", label: "Subnet" },
|
||||
{ key: "status", label: "Status" },
|
||||
{ key: "workload_name", label: "VM/LXC" },
|
||||
{ key: "workload_external_id", label: "VMID" },
|
||||
{ key: "note", label: "Note" },
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Activity } from "lucide-react";
|
||||
import { Activity, CircuitBoard, Hash, Network, ShieldCheck } from "lucide-react";
|
||||
|
||||
import { api, Workload, WorkloadInsight } from "../api/client";
|
||||
import { DataTable } from "../components/DataTable";
|
||||
@@ -32,10 +32,30 @@ export function Workloads() {
|
||||
<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>
|
||||
<header className="rounded-md border border-border bg-canvas p-4">
|
||||
<div className="mb-3 text-lg font-semibold">{insight.data.workload.name}</div>
|
||||
<div className="grid gap-2 text-xs text-slate-500 sm:grid-cols-2">
|
||||
<div className="flex items-center gap-2"><CircuitBoard size={14} /> Type: {insight.data.workload.kind}</div>
|
||||
<div className="flex items-center gap-2"><Activity size={14} /> Status: {insight.data.workload.status}</div>
|
||||
<div className="flex items-center gap-2"><Hash size={14} /> VMID: {insight.data.workload.external_id}</div>
|
||||
<div className="flex items-center gap-2"><ShieldCheck size={14} /> Decision: {insight.data.effective_decision}</div>
|
||||
</div>
|
||||
</header>
|
||||
<section>
|
||||
<div className="mb-2 flex items-center gap-2 font-medium"><Network size={16} /> Assigned IPs</div>
|
||||
{insight.data.assigned_ips.length ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{insight.data.assigned_ips.map((ip) => (
|
||||
<div key={ip.id} className="rounded-md border border-border px-3 py-2 text-xs">
|
||||
<div className="font-medium">{ip.address}</div>
|
||||
<div className="text-slate-500">{ip.subnet_cidr ?? "unknown subnet"}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border border-border p-3 text-xs text-slate-500">No assigned IP address was discovered for this workload yet.</div>
|
||||
)}
|
||||
</section>
|
||||
<section>
|
||||
<div className="mb-2 font-medium">Traffic</div>
|
||||
<div className="space-y-2">
|
||||
@@ -51,12 +71,12 @@ export function Workloads() {
|
||||
<section>
|
||||
<div className="mb-2 font-medium">Matching Policies</div>
|
||||
<div className="space-y-2">
|
||||
{insight.data.matching_policies.map((policy) => (
|
||||
{insight.data.matching_policies.length ? 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 className="rounded-md border border-border p-3 text-xs text-slate-500">No matching policy for this workload yet.</div>}
|
||||
</div>
|
||||
</section>
|
||||
{insight.data.audit_mode_notes.length ? (
|
||||
|
||||
Reference in New Issue
Block a user