import { useState, useEffect, useCallback } from "react"; import { Monitor, Plus, Loader2, Search, MoreHorizontal, ChevronRight, Zap, ZapOff, Clock, Trash2, Pencil, Laptop, Smartphone, Server, CheckCircle, XCircle, AlertCircle, Globe, Users, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; import { api, ApiError, Device, DeviceNetworkMembership, ActivationSession, MembershipState, PortalNetwork, UserNetworkApproval, ApprovalState, } from "@/lib/api"; import { useCurrentOrganizationId } from "@/hooks/useCurrentOrganization"; function cn(...classes: (string | boolean | undefined | null)[]) { return classes.filter(Boolean).join(" "); } function formatDate(d: string | null | undefined) { if (!d) return "—"; return new Date(d).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } function formatExpiry(d: string | null | undefined) { if (!d) return "—"; const date = new Date(d); const now = new Date(); if (date < now) return "Expired"; const diff = Math.floor((date.getTime() - now.getTime()) / 1000 / 60); if (diff < 60) return `${diff}m left`; if (diff < 1440) return `${Math.floor(diff / 60)}h ${diff % 60}m left`; return `${Math.floor(diff / 1440)}d ${Math.floor((diff % 1440) / 60)}h left`; } function MembershipStateBadge({ state }: { state: MembershipState }) { const config: Record = { pending_device_registration: { color: "bg-gray-500/10 text-gray-600 border-gray-200", icon: , label: "Pending Registration" }, pending_request: { color: "bg-yellow-500/10 text-yellow-600 border-yellow-200", icon: , label: "Pending Request" }, pending_manager_approval: { color: "bg-yellow-500/10 text-yellow-600 border-yellow-200", icon: , label: "Pending Approval" }, approved_inactive: { color: "bg-blue-500/10 text-blue-600 border-blue-200", icon: , label: "Approved (Inactive)" }, joined_deauthorized: { color: "bg-orange-500/10 text-orange-600 border-orange-200", icon: , label: "Joined (Deauth)" }, active_authorized: { color: "bg-green-500/10 text-green-600 border-green-200", icon: , label: "Active" }, activation_expired: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Expired" }, suspended: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Suspended" }, revoked: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Revoked" }, rejected: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Rejected" }, }; const { color, icon, label } = config[state] ?? { color: "bg-gray-500/10 text-gray-600 border-gray-200", icon: null, label: state }; return ( {icon}{label} ); } function ApprovalStateBadge({ state }: { state: ApprovalState }) { const config: Record = { pending: { color: "bg-yellow-500/10 text-yellow-600 border-yellow-200", icon: , label: "Pending" }, approved: { color: "bg-green-500/10 text-green-600 border-green-200", icon: , label: "Approved" }, rejected: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Rejected" }, revoked: { color: "bg-red-500/10 text-red-600 border-red-200", icon: , label: "Revoked" }, suspended: { color: "bg-orange-500/10 text-orange-600 border-orange-200", icon: , label: "Suspended" }, }; const { color, icon, label } = config[state] ?? { color: "bg-gray-500/10 text-gray-600 border-gray-200", icon: null, label: state }; return ( {icon}{label} ); } function DeviceTypeIcon({ nickname, hostname }: { nickname: string | null; hostname: string | null }) { const text = (nickname || hostname || "").toLowerCase(); if (text.includes("server") || text.includes("host") || text.includes("node")) return ; if (text.includes("phone") || text.includes("mobile")) return ; return ; } export default function DevicesPage() { const { orgId } = useCurrentOrganizationId(); const { toast } = useToast(); const [devices, setDevices] = useState([]); const [memberships, setMemberships] = useState([]); const [sessions, setSessions] = useState([]); const [networks, setNetworks] = useState([]); const [myApprovals, setMyApprovals] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [search, setSearch] = useState(""); const [showRegister, setShowRegister] = useState(false); const [regNodeId, setRegNodeId] = useState(""); const [regNickname, setRegNickname] = useState(""); const [regHostname, setRegHostname] = useState(""); const [regAssetTag, setRegAssetTag] = useState(""); const [regSerial, setRegSerial] = useState(""); const [isRegistering, setIsRegistering] = useState(false); const [regError, setRegError] = useState(null); const [selectedDevice, setSelectedDevice] = useState(null); const [deviceMemberships, setDeviceMemberships] = useState([]); const [isDrawerLoading, setIsDrawerLoading] = useState(false); const [editDevice, setEditDevice] = useState(null); const [editNickname, setEditNickname] = useState(""); const [editHostname, setEditHostname] = useState(""); const [isEditing, setIsEditing] = useState(false); const [deleteDevice, setDeleteDevice] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [activatingId, setActivatingId] = useState(null); const [deactivatingId, setDeactivatingId] = useState(null); const [activateLifetime, setActivateLifetime] = useState("480"); const [showActivateDialog, setShowActivateDialog] = useState(null); const [showJoinDialog, setShowJoinDialog] = useState(null); const [joinDeviceId, setJoinDeviceId] = useState(""); const [isJoining, setIsJoining] = useState(false); const [showRequestDialog, setShowRequestDialog] = useState(null); const [requestDeviceId, setRequestDeviceId] = useState(""); const [requestJustification, setRequestJustification] = useState(""); const [isRequesting, setIsRequesting] = useState(false); const [requestError, setRequestError] = useState(null); const [deletingMembershipId, setDeletingMembershipId] = useState(null); const fetchData = useCallback(async () => { if (!orgId) { setIsLoading(false); return; } setIsLoading(true); setError(null); try { const [devicesRes, membershipsRes, sessionsRes, networksRes, approvalsRes] = await Promise.allSettled([ api.zerotier.listDevices(orgId), api.zerotier.listMemberships(orgId), api.zerotier.listSessions(orgId), api.zerotier.listNetworks(orgId), api.zerotier.listMyApprovals(orgId), ]); if (devicesRes.status === "fulfilled") setDevices(devicesRes.value.devices || []); if (membershipsRes.status === "fulfilled") setMemberships(membershipsRes.value.memberships || []); if (sessionsRes.status === "fulfilled") setSessions(sessionsRes.value.sessions || []); if (networksRes.status === "fulfilled") setNetworks(networksRes.value.networks || []); if (approvalsRes.status === "fulfilled") setMyApprovals(approvalsRes.value.approvals || []); } catch { setError("Failed to load data. Please try again."); } finally { setIsLoading(false); } }, [orgId]); useEffect(() => { setDevices([]); setMemberships([]); fetchData(); }, [fetchData]); const openDeviceDrawer = async (device: Device) => { setSelectedDevice(device); setIsDrawerLoading(true); setDeviceMemberships([]); try { const deviceMem = memberships.filter((m) => m.device_id === device.id); setDeviceMemberships(deviceMem); } catch { // non-fatal } finally { setIsDrawerLoading(false); } }; const closeDrawer = () => { setSelectedDevice(null); setDeviceMemberships([]); }; const handleRegister = async () => { if (!orgId) return; setRegError(null); if (!regNodeId.trim()) { setRegError("Node ID is required."); return; } if (regNodeId.trim().length !== 10) { setRegError("Node ID must be exactly 10 characters."); return; } setIsRegistering(true); try { await api.zerotier.registerDevice(orgId, { node_id: regNodeId.trim(), nickname: regNickname.trim() || undefined, hostname: regHostname.trim() || undefined, asset_tag: regAssetTag.trim() || undefined, serial_number: regSerial.trim() || undefined, }); toast({ title: "Device registered", description: `Node ${regNodeId} has been registered.` }); setShowRegister(false); setRegNodeId(""); setRegNickname(""); setRegHostname(""); setRegAssetTag(""); setRegSerial(""); fetchData(); } catch (err) { setRegError(err instanceof ApiError ? err.message : "Failed to register device."); } finally { setIsRegistering(false); } }; const handleEdit = async () => { if (!orgId || !editDevice) return; setIsEditing(true); try { await api.zerotier.updateDevice(orgId, editDevice.id, { nickname: editNickname.trim() || undefined, hostname: editHostname.trim() || undefined, }); toast({ title: "Device updated" }); setEditDevice(null); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to update device", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setIsEditing(false); } }; const handleDelete = async () => { if (!orgId || !deleteDevice) return; setIsDeleting(true); try { await api.zerotier.removeDevice(orgId, deleteDevice.id); toast({ title: "Device removed", description: `${deleteDevice.node_id} has been removed.` }); setDeleteDevice(null); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to remove device", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setIsDeleting(false); } }; const handleActivate = async (membershipId: string) => { if (!orgId) return; setActivatingId(membershipId); try { const lifetime = parseInt(activateLifetime); await api.zerotier.activateMembership(orgId, membershipId, lifetime); toast({ title: "Membership activated", description: `Active for ${lifetime} minutes.` }); setShowActivateDialog(null); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to activate", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setActivatingId(null); } }; const handleDeactivate = async (membershipId: string) => { if (!orgId) return; setDeactivatingId(membershipId); try { await api.zerotier.deactivateMembership(orgId, membershipId); toast({ title: "Membership deactivated" }); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to deactivate", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setDeactivatingId(null); } }; const handleActivateAll = async () => { if (!orgId) return; setActivatingId("all"); try { const lifetime = parseInt(activateLifetime); const res = await api.zerotier.activateAllMemberships(orgId, lifetime); toast({ title: "All memberships activated", description: `${res.count} memberships activated for ${lifetime} minutes.` }); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to activate all", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setActivatingId(null); } }; const handleJoinNetwork = async () => { if (!orgId || !showJoinDialog || !joinDeviceId) return; setIsJoining(true); setRequestError(null); try { await api.zerotier.joinNetworkForDevice(orgId, joinDeviceId, showJoinDialog.id); toast({ title: "Joined network", description: `Device is now a member of ${showJoinDialog.name}.` }); setShowJoinDialog(null); setJoinDeviceId(""); fetchData(); } catch (err) { setRequestError(err instanceof ApiError ? err.message : "Failed to join network."); } finally { setIsJoining(false); } }; const handleRequestAccess = async () => { if (!orgId || !showRequestDialog || !requestDeviceId) return; setIsRequesting(true); setRequestError(null); try { await api.zerotier.requestAccess(orgId, { portal_network_id: showRequestDialog.id, device_id: requestDeviceId, justification: requestJustification.trim() || undefined, }); toast({ title: "Access requested", description: `Request sent for ${showRequestDialog.name}.` }); setShowRequestDialog(null); setRequestDeviceId(""); setRequestJustification(""); fetchData(); } catch (err) { setRequestError(err instanceof ApiError ? err.message : "Failed to request access."); } finally { setIsRequesting(false); } }; const handleDeleteMembership = async (membershipId: string) => { if (!orgId) return; setDeletingMembershipId(membershipId); try { await api.zerotier.deleteMembership(orgId, membershipId); toast({ title: "Membership removed" }); fetchData(); } catch (err) { toast({ variant: "destructive", title: "Failed to remove membership", description: err instanceof ApiError ? err.message : "Something went wrong." }); } finally { setDeletingMembershipId(null); } }; const filteredDevices = devices.filter((d) => { const q = search.toLowerCase(); return ( d.node_id.toLowerCase().includes(q) || (d.device_nickname?.toLowerCase().includes(q) ?? false) || (d.hostname?.toLowerCase().includes(q) ?? false) ); }); const getActiveSession = (membershipId: string): ActivationSession | null => { return sessions.find((s) => s.device_network_membership_id === membershipId && s.is_active) ?? null; }; const getMembershipForDeviceAndNetwork = (deviceId: string, networkId: string): DeviceNetworkMembership | null => { return memberships.find((m) => m.device_id === deviceId && m.portal_network_id === networkId) ?? null; }; const getApprovalForNetwork = (networkId: string): UserNetworkApproval | null => { return myApprovals.find((a) => a.portal_network_id === networkId) ?? null; }; const filteredApprovals = myApprovals.filter((a) => { if (search) { const q = search.toLowerCase(); const network = networks.find((n) => n.id === a.portal_network_id); if (!network?.name.toLowerCase().includes(q) && !a.portal_network_id.toLowerCase().includes(q)) return false; } return true; }); return (

ZeroTier Access

Manage your devices, networks, and access requests

My Devices {!isLoading && devices.length > 0 && ( {devices.length} )} My Networks {!isLoading && networks.length > 0 && ( {networks.length} )} My Requests {!isLoading && myApprovals.filter((a) => a.state === "pending").length > 0 && ( {myApprovals.filter((a) => a.state === "pending").length} )} {/* ── Tab A: My Devices ── */}
setSearch(e.target.value)} className="pl-10" />
Registered Devices {!isLoading && {devices.length}} Click a device to view memberships and activation status {isLoading ? (
Loading devices…
) : error ? (
{error}
) : filteredDevices.length === 0 ? (
{search ? "No devices match your search." : "No devices registered. Register your first ZeroTier node."}
) : (
{filteredDevices.map((device) => { const activeCount = memberships.filter( (m) => m.device_id === device.id && m.currently_authorized ).length; return ( { e.stopPropagation(); openDeviceDrawer(device); }}> View memberships { e.stopPropagation(); setEditDevice(device); setEditNickname(device.device_nickname || ""); setEditHostname(device.hostname || ""); }}> Edit { e.stopPropagation(); setDeleteDevice(device); }} > Remove ); })}
)}
{sessions.filter((s) => s.is_active).length > 0 && (
{sessions.filter((s) => s.is_active).length} active session(s)
{sessions.filter((s) => s.is_active).map((session) => (
{session.device_network_membership_id}
Expires: {formatExpiry(session.expires_at)}
))}
)}
{/* ── Tab B: My Networks ── */}
setSearch(e.target.value)} className="pl-10" />
Available Networks {!isLoading && {networks.length}} Join open networks or request access to approval-required networks {isLoading ? (
Loading networks…
) : error ? (
{error}
) : networks.length === 0 ? (
No networks available in this organization.
) : (
{networks.filter((n) => { if (!n.is_active) return false; const q = search.toLowerCase(); if (q && !n.name.toLowerCase().includes(q) && !n.zerotier_network_id.toLowerCase().includes(q)) return false; return true; }).map((network) => { const approval = getApprovalForNetwork(network.id); const hasMembership = memberships.some((m) => m.portal_network_id === network.id && !m.deleted_at); const myDeviceMemberships = devices.map((d) => getMembershipForDeviceAndNetwork(d.id, network.id)).filter(Boolean) as DeviceNetworkMembership[]; return (

{network.name}

{network.environment} {network.request_mode === "open" ? "Open" : "Approval Required"} {hasMembership && Member}

{network.zerotier_network_id}

{approval && (
{approval.justification && ( "{approval.justification}" )}
)}
{network.request_mode === "open" && !hasMembership && ( )} {network.request_mode === "approval_required" && !hasMembership && ( )} {hasMembership && (
Member
)}
{myDeviceMemberships.length > 0 && (
{myDeviceMemberships.map((m) => (
d.id === m.device_id)?.device_nickname || null} hostname={devices.find((d) => d.id === m.device_id)?.hostname || null} /> {devices.find((d) => d.id === m.device_id)?.device_nickname || devices.find((d) => d.id === m.device_id)?.node_id}
{m.approved_for_activation && !m.currently_authorized && ( )} {m.currently_authorized && ( )}
))}
)}
); })}
)}
{/* ── Tab C: My Requests ── */}
setSearch(e.target.value)} className="pl-10" />
My Access Requests {!isLoading && {myApprovals.length}} Track your network access requests and approvals {isLoading ? (
Loading requests…
) : error ? (
{error}
) : filteredApprovals.length === 0 ? (
{search ? "No requests match your search." : "No access requests yet. Browse networks to request access."}
) : (
{filteredApprovals.map((approval) => { const network = networks.find((n) => n.id === approval.portal_network_id); const relatedMemberships = memberships.filter((m) => m.portal_network_id === approval.portal_network_id); return (

{network?.name || approval.portal_network_id}

{network?.environment}

{approval.grant_type === "requested" ? "You requested" : "Assigned by admin"} {approval.justification && ` — "${approval.justification}"`}

{formatDate(approval.created_at)} {approval.granted_by_user_id && ` · Granted by manager`}

{relatedMemberships.length > 0 && (
{relatedMemberships.map((m) => { const dev = devices.find((d) => d.id === m.device_id); return ( {dev?.device_nickname || dev?.node_id}: ); })}
)}
{approval.state === "pending" && ( )}
); })}
)}
{/* Register Device Dialog */} { if (!open) setShowRegister(false); }}> Register Device Add a ZeroTier node to your account. Find your Node ID in the ZeroTier client.
setRegNodeId(e.target.value.toLowerCase())} className="font-mono" />

10-character ZeroTier Node ID from your client.

setRegNickname(e.target.value)} />
setRegHostname(e.target.value)} />
setRegAssetTag(e.target.value)} />
setRegSerial(e.target.value)} />
{regError &&

{regError}

}
{/* Edit Device Dialog */} { if (!open) setEditDevice(null); }}> Edit Device Update device nickname or hostname. {editDevice && (
setEditNickname(e.target.value)} />
setEditHostname(e.target.value)} />
)}
{/* Delete Device Confirmation */} { if (!open) setDeleteDevice(null); }}> Remove Device Remove "{deleteDevice?.node_id}" from your account? Active sessions will be terminated. {/* Activate Lifetime Dialog */} { if (!open) setShowActivateDialog(null); }}> Set Activation Duration How long should this membership be active?
setActivateLifetime(e.target.value)} placeholder="480" />

e.g. 480 = 8 hours, 60 = 1 hour

{/* Join Network Dialog */} { if (!open) { setShowJoinDialog(null); setJoinDeviceId(""); } }}> Join Network Select a registered device to join {showJoinDialog?.name}.
{requestError &&

{requestError}

}
{/* Request Access Dialog */} { if (!open) { setShowRequestDialog(null); setRequestDeviceId(""); setRequestJustification(""); setRequestError(null); } }}> Request Network Access Request access to {showRequestDialog?.name}. A manager will review your request.
setRequestJustification(e.target.value)} />
{requestError &&

{requestError}

}
{/* Device Detail Drawer */} { if (!open) closeDrawer(); }}> {selectedDevice && ( <>
{selectedDevice.device_nickname || selectedDevice.node_id}
{selectedDevice.node_id}
{selectedDevice.hostname && ( <> Hostname {selectedDevice.hostname} )} {selectedDevice.asset_tag && ( <> Asset Tag {selectedDevice.asset_tag} )} {selectedDevice.serial_number && ( <> Serial {selectedDevice.serial_number} )} Registered {formatDate(selectedDevice.created_at)} Status {selectedDevice.status}

Network Memberships ({deviceMemberships.length})

{isDrawerLoading ? (
) : deviceMemberships.length === 0 ? (
No memberships found. Request network access to get started.
) : (
{deviceMemberships.map((m) => { const session = getActiveSession(m.id); const network = networks.find((n) => n.id === m.portal_network_id); return (
{network?.name || m.portal_network_id}
{m.approved_for_activation && !m.currently_authorized && ( )} {m.currently_authorized && ( )}
{session && (
Session expires: {formatExpiry(session.expires_at)}
)}
{m.join_seen ? ( <> Joined network ) : ( <> Not yet joined )}
); })}
)} )}
); }