"use client"; import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema"; import { Network, Plus, Trash2 } from "lucide-react"; import { useState } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; // Only bridge and overlay can be created: "host"/"none" are Docker // singletons and macvlan/ipvlan need driver options we don't expose. const networkDriverEnum = ["bridge", "overlay"] as const; const ipamConfigEntrySchema = z.object({ subnet: z.string().optional(), ipRange: z.string().optional(), gateway: z.string().optional(), }); const networkFormSchema = z .object({ name: z.string().min(1, "Name is required"), driver: z.enum(networkDriverEnum), internal: z.boolean(), attachable: z.boolean(), enableIPv4: z.boolean(), enableIPv6: z.boolean(), mtu: z .string() .refine( (value) => value === "" || (/^\d+$/.test(value) && +value >= 68 && +value <= 65535), { message: "MTU must be a number between 68 and 65535" }, ), ipamDriver: z.string().optional(), ipamConfig: z.array(ipamConfigEntrySchema), }) .superRefine((input, ctx) => { if (!input.enableIPv4 && !input.enableIPv6) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["enableIPv4"], message: "IPv4 or IPv6 must be enabled", }); } for (const [index, entry] of input.ipamConfig.entries()) { if (!entry.subnet && (entry.gateway || entry.ipRange)) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["ipamConfig", index, "subnet"], message: "Gateway and IP range require a subnet", }); } } }); type NetworkFormValues = z.infer; const defaultValues: NetworkFormValues = { name: "", driver: "bridge", internal: false, attachable: false, enableIPv4: true, enableIPv6: false, mtu: "", ipamDriver: "", ipamConfig: [], }; const toggleOptions = [ { name: "internal", label: "Internal", description: "Containers on this network cannot reach external networks.", }, { name: "attachable", label: "Attachable", description: "Allow standalone containers to attach (overlay networks only).", }, { name: "enableIPv4", label: "Enable IPv4", description: "Enable IPv4 addressing on the network.", }, { name: "enableIPv6", label: "Enable IPv6", description: "Enable IPv6 addressing on the network.", }, ] as const; interface HandleNetworkProps { /** Target server; undefined creates on the local Dokploy server */ serverId?: string; children?: React.ReactNode; } // Docker networks are immutable, so this dialog only creates them; // changing a network means deleting and recreating it. export const HandleNetwork = ({ serverId, children }: HandleNetworkProps) => { const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { mutateAsync, isPending } = api.network.create.useMutation(); const form = useForm({ resolver: zodResolver(networkFormSchema), defaultValues, }); const ipamConfigFieldArray = useFieldArray({ control: form.control, name: "ipamConfig", }); const onSubmit = async (data: NetworkFormValues) => { try { await mutateAsync({ name: data.name, driver: data.driver, serverId, internal: data.internal, attachable: data.attachable, enableIPv4: data.enableIPv4, enableIPv6: data.enableIPv6, mtu: data.mtu ? Number(data.mtu) : undefined, ipam: { driver: data.ipamDriver || undefined, config: data.ipamConfig, }, }); toast.success("Network created"); await utils.network.all.invalidate(); setIsOpen(false); form.reset(defaultValues); } catch (error) { toast.error("Error creating network", { description: error instanceof Error ? error.message : "Unknown error", }); } }; const trigger = children ?? ( ); return ( {trigger} Add network Create a new Docker network for your organization. Networks are immutable: to change one, delete it and create it again.
( Name )} /> ( Driver bridge for single-server containers; overlay for Swarm services. )} /> ( MTU (optional) Maximum transmission unit. Leave empty to use Docker's default. )} />
{toggleOptions.map((option) => ( (
{option.label} {option.description}
)} /> ))}
IPAM

IP address management settings for this network.

( Driver (optional) )} />
Config (subnet / gateway / IP range) {ipamConfigFieldArray.fields.map((field, index) => (
( )} /> ( )} /> ( )} />
))}
); };