import { useEffect, useState } from 'react'; import { Button } from '@/components/ui/button'; import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/ui/modal'; import { Combobox } from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { TogglePill } from '@/components/ui/toggle-pill'; import { apiFetch } from '@/lib/api'; import { toast } from '@/components/ui/toast-store'; const NETWORK_DRIVERS = ['bridge', 'overlay', 'macvlan', 'host', 'none'] as const; type NetworkDriver = (typeof NETWORK_DRIVERS)[number]; interface CreateNetworkForm { name: string; driver: NetworkDriver; subnet: string; gateway: string; internal: boolean; attachable: boolean; } const EMPTY_FORM: CreateNetworkForm = { name: '', driver: 'bridge', subnet: '', gateway: '', internal: false, attachable: false, }; interface CreateNetworkDialogProps { open: boolean; onOpenChange: (open: boolean) => void; initialName?: string; onCreated?: (network: { name: string }) => void | Promise; } /** * Self-contained "Create network" modal. Owns its form state and posts to * `/system/networks`, so it can be reused from the Resources Networks tab and * the stack-detail Networking tab without sharing parent state. */ export function CreateNetworkDialog({ open, onOpenChange, initialName, onCreated }: CreateNetworkDialogProps) { const [form, setForm] = useState(EMPTY_FORM); const [creating, setCreating] = useState(false); useEffect(() => { if (open) setForm((current) => ({ ...current, name: initialName ?? current.name })); }, [initialName, open]); const handleCreate = async () => { setCreating(true); try { const res = await apiFetch('/system/networks', { method: 'POST', body: JSON.stringify({ name: form.name, driver: form.driver, subnet: form.subnet || undefined, gateway: form.gateway || undefined, internal: form.internal, attachable: form.attachable, }), }); if (!res.ok) { const data = await res.json().catch(() => null); throw new Error(data?.error || `Failed to create network (${res.status})`); } toast.success(`Network "${form.name}" created`); onOpenChange(false); setForm(EMPTY_FORM); await onCreated?.({ name: form.name }); } catch (error) { const err = error as Record; toast.error(String(err?.message || err?.error || 'Something went wrong.')); } finally { setCreating(false); } }; return (
setForm(f => ({ ...f, name: e.target.value }))} />
({ value: d, label: d }))} value={form.driver} onValueChange={v => setForm(f => ({ ...f, driver: (v || 'bridge') as NetworkDriver }))} placeholder="Select driver..." searchPlaceholder="Search drivers..." emptyText="No matching driver." />
setForm(f => ({ ...f, subnet: e.target.value }))} />
setForm(f => ({ ...f, gateway: e.target.value }))} />
setForm(f => ({ ...f, internal: v }))} />
setForm(f => ({ ...f, attachable: v }))} />
onOpenChange(false)} disabled={creating}> Cancel } primary={ } />
); }