diff --git a/app/routes/machines/components/machine-row.tsx b/app/routes/machines/components/machine-row.tsx index 2c7f662..aa4dea9 100644 --- a/app/routes/machines/components/machine-row.tsx +++ b/app/routes/machines/components/machine-row.tsx @@ -9,7 +9,7 @@ import { ExpiryTag } from '~/components/tags/Expiry'; import { HeadplaneAgentTag } from '~/components/tags/HeadplaneAgent'; import { SubnetTag } from '~/components/tags/Subnet'; import { TailscaleSSHTag } from '~/components/tags/TailscaleSSH'; -import type { Machine, User } from '~/types'; +import type { User } from '~/types'; import cn from '~/utils/cn'; import * as hinfo from '~/utils/host-info'; import { PopulatedNode } from '~/utils/node-info'; @@ -22,7 +22,7 @@ interface Props { isAgent?: boolean; magic?: string; isDisabled?: boolean; - nodeList?: Machine[]; + existingTags?: string[]; } export default function MachineRow({ @@ -31,7 +31,7 @@ export default function MachineRow({ isAgent, magic, isDisabled, - nodeList, + existingTags, }: Props) { const uiTags = useMemo(() => { const tags = uiTagsForNode(node, isAgent); @@ -148,10 +148,10 @@ export default function MachineRow({ diff --git a/app/routes/machines/components/menu.tsx b/app/routes/machines/components/menu.tsx index 2da92ae..cb0d8d6 100644 --- a/app/routes/machines/components/menu.tsx +++ b/app/routes/machines/components/menu.tsx @@ -2,7 +2,7 @@ import { Cog, Ellipsis, SquareTerminal } from 'lucide-react'; import { useState } from 'react'; import Button from '~/components/Button'; import Menu from '~/components/Menu'; -import type { Machine, User } from '~/types'; +import type { User } from '~/types'; import cn from '~/utils/cn'; import { PopulatedNode } from '~/utils/node-info'; import Delete from '../dialogs/delete'; @@ -18,7 +18,7 @@ interface MenuProps { magic?: string; isFullButton?: boolean; isDisabled?: boolean; - nodeList?: Machine[]; + existingTags?: string[]; } type Modal = 'rename' | 'expire' | 'remove' | 'routes' | 'move' | 'tags' | null; @@ -29,7 +29,7 @@ export default function MachineMenu({ users, isFullButton, isDisabled, - nodeList, + existingTags, }: MenuProps) { const [modal, setModal] = useState(null); const supportsTailscaleSSH = @@ -77,9 +77,9 @@ export default function MachineMenu({ )} {modal === 'tags' && ( { if (!isOpen) setModal(null); }} diff --git a/app/routes/machines/dialogs/tags.tsx b/app/routes/machines/dialogs/tags.tsx index 504d5bc..bbc280f 100644 --- a/app/routes/machines/dialogs/tags.tsx +++ b/app/routes/machines/dialogs/tags.tsx @@ -12,14 +12,14 @@ interface TagsProps { machine: Machine; isOpen: boolean; setIsOpen: (isOpen: boolean) => void; - nodeList?: Machine[]; + existingTags?: string[]; } export default function Tags({ machine, isOpen, setIsOpen, - nodeList, + existingTags, }: TagsProps) { const [tags, setTags] = useState(machine.forcedTags); const [tag, setTag] = useState('tag:'); @@ -28,13 +28,7 @@ export default function Tags({ }, [tag, tags]); const validNodeTags = useMemo(() => { - if (!nodeList?.length) return []; - const allNodeTags = Array.from( - new Set( - nodeList.flatMap((node) => [...node.validTags, ...node.forcedTags]), - ), - ).sort(); - return allNodeTags.filter((nodeTag) => !tags.includes(nodeTag)); + return existingTags?.filter((nodeTag) => !tags.includes(nodeTag)) || []; }, [tags]); return ( diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index 86b7cf0..0b751f0 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -10,7 +10,7 @@ import StatusCircle from '~/components/StatusCircle'; import Tooltip from '~/components/Tooltip'; import cn from '~/utils/cn'; import { getOSInfo, getTSVersion } from '~/utils/host-info'; -import { mapNodes } from '~/utils/node-info'; +import { mapNodes, sortNodeTags } from '~/utils/node-info'; import type { Route } from './+types/machine'; import { mapTagsToComponents, uiTagsForNode } from './components/machine-row'; import MenuOptions from './components/menu'; @@ -35,11 +35,8 @@ export async function loader({ request, params, context }: Route.LoaderArgs) { } const api = context.hsApi.getRuntimeClient(session.api_key); - const [node, users, allNodes] = await Promise.all([ - api.getNode(params.id), - api.getUsers(), - api.getNodes(), - ]); + const [nodes, users] = await Promise.all([api.getNodes(), api.getUsers()]); + const node = nodes.find((node) => node.id === params.id); const lookup = await context.agents?.lookup([node.nodeKey]); const [enhancedNode] = mapNodes([node], lookup); @@ -54,14 +51,14 @@ export async function loader({ request, params, context }: Route.LoaderArgs) { magic, agent: context.agents?.agentID(), stats: lookup?.[enhancedNode.nodeKey], - nodeList: allNodes, + existingTags: sortNodeTags(nodes), }; } export const action = machineAction; export default function Page({ - loaderData: { node, tags, users, magic, agent, stats, nodeList }, + loaderData: { node, tags, users, magic, agent, stats, existingTags }, }: Route.ComponentProps) { const [showRouting, setShowRouting] = useState(false); @@ -90,10 +87,10 @@ export default function Page({ diff --git a/app/routes/machines/overview.tsx b/app/routes/machines/overview.tsx index d3c12d0..3b23f8a 100644 --- a/app/routes/machines/overview.tsx +++ b/app/routes/machines/overview.tsx @@ -4,7 +4,7 @@ import Link from '~/components/Link'; import Tooltip from '~/components/Tooltip'; import { Capabilities } from '~/server/web/roles'; import cn from '~/utils/cn'; -import { mapNodes } from '~/utils/node-info'; +import { mapNodes, sortNodeTags } from '~/utils/node-info'; import type { Route } from './+types/overview'; import MachineRow from './components/machine-row'; import NewMachine from './dialogs/new'; @@ -126,6 +126,7 @@ export default function Page({ loaderData }: Route.ComponentProps) { > {loaderData.populatedNodes.map((node) => ( ))} diff --git a/app/utils/node-info.ts b/app/utils/node-info.ts index ec0598e..3e54f3c 100644 --- a/app/utils/node-info.ts +++ b/app/utils/node-info.ts @@ -52,3 +52,14 @@ export function mapNodes( }; }); } + +export function sortNodeTags(nodes: Machine[]): string[] { + return Array.from( + new Set( + nodes.flatMap(({ validTags, forcedTags }) => [ + ...validTags, + ...forcedTags, + ]), + ), + ).sort(); +}