import { useMemo, useState } from "react" import { loadBalancersApi } from "@/api/loadbalancers" import type { DtoLoadBalancerResponse } from "@/sdk" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { CircleSlash2, Network, Pencil, Plus, Search } from "lucide-react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { truncateMiddle } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Form, FormControl, 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" // --- schemas --- const createLoadBalancerSchema = z.object({ name: z.string().trim().min(1, "Name is required").max(120, "Max 120 chars"), kind: z.enum(["glueops", "public"]).default("public"), public_ip_address: z .string() .trim() .min(1, "Public IP/hostname is required") .max(255, "Max 255 chars"), private_ip_address: z .string() .trim() .min(1, "Private IP/hostname is required") .max(255, "Max 255 chars"), }) type CreateLoadBalancerInput = z.input const updateLoadBalancerSchema = createLoadBalancerSchema.partial() type UpdateLoadBalancerValues = z.infer // --- badge --- function LoadBalancerBadge({ lb, }: { lb: Pick }) { return ( {lb.name} · {lb.kind} · {lb.public_ip_address} → {lb.private_ip_address} ) } export const LoadBalancersPage = () => { const [filter, setFilter] = useState("") const [createOpen, setCreateOpen] = useState(false) const [updateOpen, setUpdateOpen] = useState(false) const [deleteId, setDeleteId] = useState(null) const [editingId, setEditingId] = useState(null) const qc = useQueryClient() const lbsQ = useQuery({ queryKey: ["loadBalancers"], queryFn: () => loadBalancersApi.listLoadBalancers(), }) // --- Create --- const createForm = useForm({ resolver: zodResolver(createLoadBalancerSchema), defaultValues: { name: "", kind: "public", public_ip_address: "", private_ip_address: "", }, }) const createMut = useMutation({ mutationFn: (values: CreateLoadBalancerInput) => loadBalancersApi.createLoadBalancer(values), onSuccess: async () => { await qc.invalidateQueries({ queryKey: ["loadBalancers"] }) createForm.reset() setCreateOpen(false) toast.success("Load balancer created successfully.") }, onError: (err: any) => { toast.error(err?.message ?? "There was an error while creating the load balancer") }, }) const onCreateSubmit = (values: CreateLoadBalancerInput) => { createMut.mutate(values) } // --- Update --- const updateForm = useForm({ resolver: zodResolver(updateLoadBalancerSchema), defaultValues: {}, }) const updateMut = useMutation({ mutationFn: ({ id, values }: { id: string; values: UpdateLoadBalancerValues }) => loadBalancersApi.updateLoadBalancer(id, values), onSuccess: async () => { await qc.invalidateQueries({ queryKey: ["loadBalancers"] }) updateForm.reset() setUpdateOpen(false) toast.success("Load balancer updated successfully.") }, onError: (err: any) => { toast.error(err?.message ?? "There was an error while updating the load balancer") }, }) const openEdit = (lb: DtoLoadBalancerResponse) => { setEditingId(lb.id!) updateForm.reset({ name: lb.name ?? "", kind: (lb.kind as "public" | "glueops") ?? "public", public_ip_address: lb.public_ip_address ?? "", private_ip_address: lb.private_ip_address ?? "", }) setUpdateOpen(true) } // --- Delete --- const deleteMut = useMutation({ mutationFn: (id: string) => loadBalancersApi.deleteLoadBalancer(id), onSuccess: async () => { await qc.invalidateQueries({ queryKey: ["loadBalancers"] }) setDeleteId(null) toast.success("Load balancer deleted successfully.") }, onError: (err: any) => { toast.error(err?.message ?? "There was an error while deleting the load balancer") }, }) // --- Filter --- const filtered = useMemo(() => { const data = lbsQ.data ?? [] const q = filter.trim().toLowerCase() return q ? data.filter((lb: any) => { return ( lb.name?.toLowerCase().includes(q) || lb.kind?.toLowerCase().includes(q) || lb.public_ip_address?.toLowerCase().includes(q) || lb.private_ip_address?.toLowerCase().includes(q) ) }) : data }, [filter, lbsQ.data]) if (lbsQ.isLoading) return
Loading load balancers…
if (lbsQ.error) return
Error loading load balancers.
return (

Load Balancers

setFilter(e.target.value)} placeholder="Search load balancers" className="w-64 pl-8" />
Create Load Balancer
( Name )} /> ( Kind )} /> ( Public IP )} /> ( Private IP )} />
Name Kind Public IP / Hostname Private IP / Hostname Summary Actions {filtered.map((lb: DtoLoadBalancerResponse) => ( {lb.name} {lb.kind} {lb.public_ip_address} {lb.private_ip_address}
{lb.id && ( {truncateMiddle(lb.id, 6)} )}
))} {filtered.length === 0 && ( No load balancers match your search. )}
{/* Update dialog */} Edit Load Balancer
{ if (!editingId) return updateMut.mutate({ id: editingId, values }) })} > ( Name )} /> ( Kind )} /> ( Public IP / Hostname )} /> ( Private IP / Hostname )} />
{/* Delete confirm dialog */} !open && setDeleteId(null)}> Delete load balancer

This action cannot be undone. Are you sure you want to delete this load balancer?

) }