import { Alert, Badge, Button, Dropdown, Input, Table } from "react-daisyui"; import { Node } from "../types"; import { cn, handleError, readableBytes } from "@/lib/utils"; import { Check, CheckCircle, Cylinder, EllipsisVertical, Info, Network, RouteIcon, Share2, Trash2, X, } from "lucide-react"; import { useMemo, useState } from "react"; import ConnectNodeDialog from "./connect-node-dialog"; import AssignNodeDialog from "./assign-node-dialog"; import { assignNodeDialog } from "../stores"; import { useApplyChanges, useClusterLayout, useRevertChanges, useUnassignNode, } from "../hooks"; import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; type NodeListProps = { nodes: Node[]; }; const NodesList = ({ nodes }: NodeListProps) => { const { data, refetch } = useClusterLayout(); const [filter, setFilter] = useState({ search: "", }); const queryClient = useQueryClient(); const unassignNode = useUnassignNode({ onSuccess: () => { toast.success("Node unassigned!"); refetch(); }, onError: handleError, }); const revertChanges = useRevertChanges({ onSuccess: () => { toast.success("Layout reverted!"); refetch(); }, onError: handleError, }); const applyChanges = useApplyChanges({ onSuccess: () => { toast.success("Layout applied!"); setTimeout(refetch, 100); queryClient.invalidateQueries({ queryKey: ["status"] }); }, onError: handleError, }); const items = useMemo(() => { return nodes .filter((item) => { if (filter.search) { const q = filter.search.toLowerCase(); return ( item.hostname.toLowerCase().includes(q) || item.id.includes(q) || item.addr.includes(q) || item.role?.zone?.includes(q) || item.role?.tags?.find((tag) => tag.toLowerCase().includes(q)) ); } return true; }) .map((item) => { const role = data?.roles?.find((r) => r.id === item.role?.id); const stagedChanges = data?.stagedRoleChanges?.find( (i) => i.id === item.id ); return { ...item, role: stagedChanges || role || item.role, isStaged: !!stagedChanges, }; }); }, [nodes, data, filter]); const onAssign = (node: Node) => { assignNodeDialog.open({ nodeId: node.id, zone: node.role?.zone, capacity: node.role?.capacity, tags: node.role?.tags, }); }; const onUnassign = (id: string) => { if (window.confirm("Are you sure you want to unassign this node?")) { unassignNode.mutate(id); } }; const onRevert = () => { if ( window.confirm("Are you sure you want to revert layout changes?") && data?.version != null ) { revertChanges.mutate(data?.version + 1); } }; const onApply = () => { if ( window.confirm("Are you sure you want to revert layout changes?") && data?.version != null ) { applyChanges.mutate(data?.version + 1); } }; const hasStagedChanges = data && data.stagedRoleChanges?.length > 0; return ( <>
{ setFilter((state) => ({ ...state, search: e.target.value })); }} />
{hasStagedChanges ? ( <> ) : ( )}
{hasStagedChanges && ( }> There are staged layout changes that need to be applied. Press Apply to apply them, or Revert to discard them. )} {applyChanges.data?.message ? ( } className="items-start overflow-x-auto relative text-sm" >
{applyChanges.data.message.join("\n")}
) : null}
# ID Hostname Zone Capacity Status {items.map((item, idx) => ( {idx + 1}

{item.id}

<>

{item.hostname}

{item.addr}

<>

{item.role?.zone || "-"}

{item.role?.tags?.map((tag: any) => ( {tag} ))}
<>

{item.role?.capacity === null ? ( <> Gateway ) : ( readableBytes(item.role?.capacity, 1000) )}

{item.role?.capacity !== null && item.dataPartition ? (

{readableBytes(item.dataPartition?.available) + ` (${Math.round( (item.dataPartition.available / item.dataPartition.total) * 100 )}%)`}

) : null} {item.draining ? "Draining" : item.isUp ? "Active" : "Inactive"} onAssign(item)}> Assign {item.role != null && ( onUnassign(item.id)} > Remove )}
))}
); }; export default NodesList;