feat: add automatic cluster sync and IPAM discovery with configurable intervals, firewall log file parsing, and enhanced flow prioritization

Add RuntimeSettingsRead/RuntimeSettingsUpdate schemas with auto_node_sync_enabled/auto_node_sync_interval_minutes/auto_ipam_sync_enabled/auto_ipam_sync_interval_minutes/last_node_auto_sync_at/last_ipam_auto_sync_at fields, implement firewall_log_lines_from_files to parse /var/log/pve-firewall.log with max_lines limit and error collection, extend collect_firewall_log
This commit is contained in:
2026-07-10 08:13:40 +02:00
parent 1531b7ea47
commit 67eee0662a
10 changed files with 493 additions and 65 deletions
+22 -2
View File
@@ -1,8 +1,8 @@
import { FormEvent, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Database, Download, Pencil, Plus } from "lucide-react";
import { Database, Download, Pencil, Plus, RefreshCcw } from "lucide-react";
import { api, authorizedFetch, IpAddress, Network, Subnet } from "../api/client";
import { api, authorizedFetch, IpAddress, Network, RuntimeSettings, Subnet } from "../api/client";
import { DataTable } from "../components/DataTable";
import { buttonClass, Field, iconButtonClass, inputClass, secondaryButtonClass, selectClass } from "../components/FormControls";
import { LoadingOverlay } from "../components/LoadingOverlay";
@@ -16,6 +16,7 @@ export function Ipam() {
const networks = useQuery({ queryKey: ["networks"], queryFn: () => api<Network[]>("/networks") });
const subnets = useQuery({ queryKey: ["subnets"], queryFn: () => api<Subnet[]>("/ipam/subnets") });
const addresses = useQuery({ queryKey: ["addresses"], queryFn: () => api<IpAddress[]>("/ipam/addresses") });
const settings = useQuery({ queryKey: ["settings"], queryFn: () => api<RuntimeSettings>("/settings") });
const [message, setMessage] = useState("");
const [subnetForm, setSubnetForm] = useState(emptySubnetForm);
const [ipForm, setIpForm] = useState({ subnet_id: "", address: "10.50.0.20", status: "reserved", note: "" });
@@ -46,6 +47,13 @@ export function Ipam() {
},
onError: (error) => setMessage(error instanceof Error ? error.message : "IP reservation failed."),
});
const toggleAutoDiscover = useMutation({
mutationFn: () => api<RuntimeSettings>("/settings", {
method: "PATCH",
body: JSON.stringify({ auto_ipam_sync_enabled: !settings.data?.auto_ipam_sync_enabled }),
}),
onSuccess: () => settings.refetch(),
});
async function submitSubnet(event: FormEvent) {
event.preventDefault();
@@ -110,6 +118,18 @@ export function Ipam() {
<PageHeader title="IPAM" subtitle="Manage subnets, reservations, assignments, conflicts, and export state." />
<LoadingOverlay open={Boolean(busyMessage)} message={busyMessage} />
<div className="space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-border bg-panel p-3">
<div>
<div className="text-sm font-medium">Automatic IPAM discovery</div>
<div className="text-xs text-slate-500">
{settings.data?.auto_ipam_sync_enabled ? `Enabled every ${settings.data.auto_ipam_sync_interval_minutes} minutes` : "Disabled"}
</div>
</div>
<button className={secondaryButtonClass} disabled={toggleAutoDiscover.isPending || !settings.data} onClick={() => toggleAutoDiscover.mutate()}>
<RefreshCcw size={16} />
{settings.data?.auto_ipam_sync_enabled ? "Disable Auto Discover" : "Enable Auto Discover"}
</button>
</div>
<div className="flex flex-wrap gap-2">
<button className={buttonClass} onClick={addSubnet}><Plus size={16} /> Add Subnet</button>
<button className={buttonClass} onClick={() => setIpOpen(true)}><Plus size={16} /> Reserve IP</button>