import { useCallback, useEffect, useState } from "react" import { CheckCircle2, Globe, Network, Plus, RefreshCw, Shield, Trash2, Wifi, } from "lucide-react" import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client" import { AlertBanner, ConfirmDialog, EmptyState, LoadingOverlay } from "@crema/feedback-ui" import { AppShell } from "~/components/layout/app-shell" import { Badge } from "~/components/ui/badge" import { Button } from "~/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "~/components/ui/card" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "~/components/ui/dialog" import { Input } from "~/components/ui/input" import { Label } from "~/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select" import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs" import { assignFloatingIp, createDnsRecord, deleteDnsRecord, deleteFirewall, listDnsRecords, listDomains, listFirewalls, listFloatingIps, listVpcs, unassignFloatingIp, type DnsRecord, type Domain, type Firewall, type FloatingIp, type Vpc, } from "~/lib/arcadia/networking" import { listDroplets, type Droplet } from "~/lib/arcadia/monitoring" import { pageTitle } from "~/lib/page-meta" import { useSession } from "~/lib/session" import { useRegisterContext } from "@crema/aifirst-ui/context" export const meta = () => pageTitle("Networking") const DNS_TYPES = ["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SRV", "CAA"] export default function NetworkingRoute() { const session = useSession() const arcadia = useArcadiaClient() const [firewalls, setFirewalls] = useState([]) const [vpcs, setVpcs] = useState([]) const [domains, setDomains] = useState([]) const [floatingIps, setFloatingIps] = useState([]) const [droplets, setDroplets] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [info, setInfo] = useState(null) const refresh = useCallback(async () => { setError(null) setLoading(true) try { const [f, v, d, fi, dr] = await Promise.all([ listFirewalls(arcadia), listVpcs(arcadia), listDomains(arcadia), listFloatingIps(arcadia), listDroplets(arcadia), ]) setFirewalls(f) setVpcs(v) setDomains(d) setFloatingIps(fi) setDroplets(dr) } catch (err) { setError(err instanceof ArcadiaError ? err.message : "Failed to load networking.") } finally { setLoading(false) } }, [arcadia]) useEffect(() => { if (session) refresh() }, [session, refresh]) useRegisterContext("networking", { firewalls: firewalls.length, vpcs: vpcs.length, domains: domains.length, floating_ips: floatingIps.length, droplets: droplets.length, }) return (

Networking

Firewalls, VPCs, DNS, and floating IPs on the platform's underlying provider.

{error ? ( setError(null)}> {error} ) : null} {info ? ( setInfo(null)}> {info} ) : null} Firewalls ({firewalls.length}) VPCs ({vpcs.length}) DNS ({domains.length}) Floating IPs ({floatingIps.length})
) } // --- Firewalls panel --------------------------------------------------- function FirewallsPanel({ firewalls, loading, onChanged, onError, onInfo, }: { firewalls: Firewall[] loading: boolean onChanged: () => Promise onError: (msg: string | null) => void onInfo: (msg: string | null) => void }) { const arcadia = useArcadiaClient() const [pendingDelete, setPendingDelete] = useState(null) if (loading && firewalls.length === 0) { return ( ) } if (firewalls.length === 0) { return ( } title="No firewalls." description="Create a firewall on your provider, or configure DigitalOcean access in arcadia's .env to see existing ones." className="py-8" /> ) } return ( <>
    {firewalls.map((f) => (
    {f.name} {f.status ? {f.status} : null}
    Inbound rules: {f.inbound_rules?.length ?? 0} · Outbound rules:{" "} {f.outbound_rules?.length ?? 0} · Droplets attached:{" "} {f.droplet_ids?.length ?? 0}
    ))}
!o && setPendingDelete(null)} title="Delete firewall?" description={ pendingDelete ? `${pendingDelete.name} will be removed. Attached droplets lose this rule set.` : "" } confirmLabel="Delete" variant="danger" onConfirm={async () => { if (!pendingDelete) return try { await deleteFirewall(arcadia, pendingDelete.id) setPendingDelete(null) onInfo("Firewall deleted.") await onChanged() } catch (err) { onError(err instanceof ArcadiaError ? err.message : "Delete failed.") setPendingDelete(null) } }} /> ) } // --- VPCs panel -------------------------------------------------------- function VpcsPanel({ vpcs, loading }: { vpcs: Vpc[]; loading: boolean }) { if (loading && vpcs.length === 0) { return ( ) } if (vpcs.length === 0) { return ( } title="No VPCs." description="Read-only view; create VPCs on your provider directly." className="py-8" /> ) } return (
    {vpcs.map((v) => (
    {v.name} {v.default ? default : null}
    Region: {v.region ?? "—"}
    IP range: {v.ip_range ?? "—"}
    ))}
) } // --- Domains + DNS records panel --------------------------------------- function DomainsPanel({ domains, loading, onError, onInfo, onChanged, }: { domains: Domain[] loading: boolean onError: (msg: string | null) => void onInfo: (msg: string | null) => void onChanged: () => Promise }) { const arcadia = useArcadiaClient() const [selectedName, setSelectedName] = useState(() => domains[0]?.name ?? "") const [records, setRecords] = useState([]) const [loadingRecords, setLoadingRecords] = useState(false) const [createOpen, setCreateOpen] = useState(false) const [pendingDelete, setPendingDelete] = useState(null) useEffect(() => { if (!selectedName && domains.length > 0) setSelectedName(domains[0].name) }, [domains, selectedName]) const loadRecords = useCallback( async (name: string) => { if (!name) { setRecords([]) return } setLoadingRecords(true) try { setRecords(await listDnsRecords(arcadia, name)) } catch (err) { onError(err instanceof ArcadiaError ? err.message : "Failed to load DNS records.") } finally { setLoadingRecords(false) } }, [arcadia, onError], ) useEffect(() => { loadRecords(selectedName) }, [selectedName, loadRecords]) if (loading && domains.length === 0) { return ( ) } if (domains.length === 0) { return ( } title="No domains." description="Add a domain on your provider; arcadia surfaces it here for record management." className="py-8" /> ) } return (
{records.length === 0 && !loadingRecords ? ( } title="No records on this domain." className="py-8" /> ) : (
    {records.map((r) => (
  • {r.type} {r.name} {r.data} {r.ttl ? ( TTL {r.ttl}s ) : null}
  • ))}
)}
setCreateOpen(false)} onCreated={async () => { setCreateOpen(false) onInfo("DNS record created.") await loadRecords(selectedName) await onChanged() }} onError={onError} /> !o && setPendingDelete(null)} title="Delete DNS record?" description={ pendingDelete ? `${pendingDelete.type} ${pendingDelete.name} → ${pendingDelete.data}. This is destructive and may break traffic.` : "" } confirmLabel="Delete" variant="danger" onConfirm={async () => { if (!pendingDelete) return try { await deleteDnsRecord(arcadia, selectedName, pendingDelete.id) setPendingDelete(null) onInfo("Record deleted.") await loadRecords(selectedName) } catch (err) { onError(err instanceof ArcadiaError ? err.message : "Delete failed.") setPendingDelete(null) } }} />
) } function DnsCreateDialog({ open, domainName, onClose, onCreated, onError, }: { open: boolean domainName: string onClose: () => void onCreated: () => Promise onError: (msg: string | null) => void }) { const arcadia = useArcadiaClient() const [type, setType] = useState("A") const [name, setName] = useState("@") const [data, setData] = useState("") const [ttl, setTtl] = useState("3600") const [priority, setPriority] = useState("") const [saving, setSaving] = useState(false) useEffect(() => { if (!open) { setType("A") setName("@") setData("") setTtl("3600") setPriority("") } }, [open]) const submit = async () => { onError(null) setSaving(true) try { await createDnsRecord(arcadia, domainName, { type, name, data, ttl: ttl ? Number(ttl) : undefined, priority: priority ? Number(priority) : undefined, }) await onCreated() } catch (err) { onError(err instanceof ArcadiaError ? err.message : "Create failed.") } finally { setSaving(false) } } return ( !o && onClose()}> New DNS record On {domainName}.
setName(e.target.value)} placeholder="@ or sub" data-action="dns-form-name" />
setData(e.target.value)} placeholder={ type === "A" ? "1.2.3.4" : type === "CNAME" ? "target.example.com." : type === "TXT" ? '"verification=..."' : "value" } className="font-mono" data-action="dns-form-data" />
setTtl(e.target.value)} data-action="dns-form-ttl" />
{type === "MX" || type === "SRV" ? (
setPriority(e.target.value)} data-action="dns-form-priority" />
) : null}
) } // --- Floating IPs panel ------------------------------------------------ function FloatingIpsPanel({ ips, droplets, loading, onChanged, onError, onInfo, }: { ips: FloatingIp[] droplets: Droplet[] loading: boolean onChanged: () => Promise onError: (msg: string | null) => void onInfo: (msg: string | null) => void }) { const arcadia = useArcadiaClient() const [assigning, setAssigning] = useState<{ ip: string; dropletId: string } | null>(null) if (loading && ips.length === 0) { return ( ) } if (ips.length === 0) { return ( } title="No floating IPs." description="Reserve a floating IP on your provider to surface it here." className="py-8" /> ) } return (
    {ips.map((ip) => { const region = typeof ip.region === "string" ? ip.region : ip.region?.slug ?? "—" return (
  • {ip.ip} {region} {ip.droplet ? ( → {ip.droplet.name ?? ip.droplet.id} ) : ( unassigned )}
    {ip.droplet ? ( ) : ( <> )}
  • ) })}
) }