Replace state blob with resource-oriented API endpoints

Split /api/v1/state into dedicated resource endpoints:
- /api/v1/operator, /api/v1/central-domain
- /api/v1/ddns/config, /api/v1/dns/settings
- /api/v1/dhcp/config (moved from /dnsmasq/dhcp/)
- /api/v1/nftables/config, /api/v1/haproxy/routes

Each page now talks to its own resource instead of deep-merging
a blob. Removes useConfig hook, CentralState type, and legacy
config methods.
This commit is contained in:
2026-07-10 22:45:36 +00:00
parent 79c0c32b98
commit 79f38d2750
12 changed files with 528 additions and 335 deletions

View File

@@ -5,13 +5,20 @@ import { Input, Label } from './ui';
import { Alert, AlertDescription } from './ui/alert';
import { Wifi, Loader2, Edit2, Check, X, Trash2, Plus, RotateCw, CheckCircle, AlertCircle } from 'lucide-react';
import { Badge } from './ui/badge';
import { useConfig } from '../hooks';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useDhcp } from '../hooks/useDhcp';
import { useDnsmasq } from '../hooks/useDnsmasq';
import { dhcpConfigApi, type DHCPConfig } from '../services/api/settings';
import type { DhcpStaticLease } from '../services/api/networking';
export function DhcpComponent() {
const { config: globalConfig, updateConfig, isUpdating } = useConfig();
const queryClient = useQueryClient();
const { data: dhcpConfig } = useQuery({ queryKey: ['dhcp', 'config'], queryFn: () => dhcpConfigApi.get() });
const dhcpMutation = useMutation({
mutationFn: (data: Partial<DHCPConfig>) => dhcpConfigApi.update(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dhcp', 'config'] }),
});
const isUpdating = dhcpMutation.isPending;
const { leases, isLoadingLeases, refetchLeases, addStatic, isAddingStatic, deleteStatic } = useDhcp();
const { generateConfig: generateDnsConfig } = useDnsmasq();
@@ -36,33 +43,23 @@ export function DhcpComponent() {
const handleDhcpConfigEdit = () => {
setDhcpConfigForm({
enabled: globalConfig?.cloud?.dnsmasq?.dhcp?.enabled ?? false,
rangeStart: globalConfig?.cloud?.dnsmasq?.dhcp?.rangeStart ?? '',
rangeEnd: globalConfig?.cloud?.dnsmasq?.dhcp?.rangeEnd ?? '',
leaseTime: globalConfig?.cloud?.dnsmasq?.dhcp?.leaseTime ?? '24h',
gateway: globalConfig?.cloud?.dnsmasq?.dhcp?.gateway ?? '',
enabled: dhcpConfig?.enabled ?? false,
rangeStart: dhcpConfig?.rangeStart ?? '',
rangeEnd: dhcpConfig?.rangeEnd ?? '',
leaseTime: dhcpConfig?.leaseTime ?? '24h',
gateway: dhcpConfig?.gateway ?? '',
});
setEditingDhcpConfig(true);
};
const handleDhcpConfigSave = async () => {
if (!globalConfig) return;
try {
await updateConfig({
...globalConfig,
cloud: {
...globalConfig.cloud,
dnsmasq: {
...globalConfig.cloud?.dnsmasq,
dhcp: {
enabled: dhcpConfigForm.enabled,
rangeStart: dhcpConfigForm.rangeStart,
rangeEnd: dhcpConfigForm.rangeEnd,
leaseTime: dhcpConfigForm.leaseTime,
gateway: dhcpConfigForm.gateway || undefined,
},
},
},
await dhcpMutation.mutateAsync({
enabled: dhcpConfigForm.enabled,
rangeStart: dhcpConfigForm.rangeStart,
rangeEnd: dhcpConfigForm.rangeEnd,
leaseTime: dhcpConfigForm.leaseTime,
gateway: dhcpConfigForm.gateway || undefined,
});
setEditingDhcpConfig(false);
if (dhcpConfigForm.enabled) {
@@ -104,9 +101,9 @@ export function DhcpComponent() {
<div className="flex items-center justify-between">
<CardTitle>Configuration</CardTitle>
<div className="flex items-center gap-2">
<Badge variant={globalConfig?.cloud?.dnsmasq?.dhcp?.enabled ? 'success' : 'secondary'} className="gap-1">
{globalConfig?.cloud?.dnsmasq?.dhcp?.enabled && <CheckCircle className="h-3 w-3" />}
{globalConfig?.cloud?.dnsmasq?.dhcp?.enabled ? 'Enabled' : 'Disabled'}
<Badge variant={dhcpConfig?.enabled ? 'success' : 'secondary'} className="gap-1">
{dhcpConfig?.enabled && <CheckCircle className="h-3 w-3" />}
{dhcpConfig?.enabled ? 'Enabled' : 'Disabled'}
</Badge>
{!editingDhcpConfig && (
<Button variant="outline" size="sm" onClick={handleDhcpConfigEdit} className="gap-1 h-7 text-xs">
@@ -189,24 +186,24 @@ export function DhcpComponent() {
</div>
) : (
<div className="space-y-2 text-sm">
{globalConfig?.cloud?.dnsmasq?.dhcp?.enabled ? (
{dhcpConfig?.enabled ? (
<>
<div className="flex items-center gap-4">
<div>
<span className="text-muted-foreground">Range: </span>
<span className="font-mono">
{globalConfig.cloud.dnsmasq.dhcp.rangeStart} – {globalConfig.cloud.dnsmasq.dhcp.rangeEnd}
{dhcpConfig!.rangeStart} – {dhcpConfig!.rangeEnd}
</span>
</div>
<div>
<span className="text-muted-foreground">Lease: </span>
<span className="font-mono">{globalConfig.cloud.dnsmasq.dhcp.leaseTime || '24h'}</span>
<span className="font-mono">{dhcpConfig!.leaseTime || '24h'}</span>
</div>
</div>
{globalConfig.cloud.dnsmasq.dhcp.gateway && (
{dhcpConfig!.gateway && (
<div>
<span className="text-muted-foreground">Gateway: </span>
<span className="font-mono">{globalConfig.cloud.dnsmasq.dhcp.gateway}</span>
<span className="font-mono">{dhcpConfig!.gateway}</span>
</div>
)}
</>