mirror of
https://github.com/tale/headplane.git
synced 2026-08-31 01:09:25 +00:00
fix: change nodeList to existingTags
This commit is contained in:
@@ -9,7 +9,7 @@ import { ExpiryTag } from '~/components/tags/Expiry';
|
|||||||
import { HeadplaneAgentTag } from '~/components/tags/HeadplaneAgent';
|
import { HeadplaneAgentTag } from '~/components/tags/HeadplaneAgent';
|
||||||
import { SubnetTag } from '~/components/tags/Subnet';
|
import { SubnetTag } from '~/components/tags/Subnet';
|
||||||
import { TailscaleSSHTag } from '~/components/tags/TailscaleSSH';
|
import { TailscaleSSHTag } from '~/components/tags/TailscaleSSH';
|
||||||
import type { Machine, User } from '~/types';
|
import type { User } from '~/types';
|
||||||
import cn from '~/utils/cn';
|
import cn from '~/utils/cn';
|
||||||
import * as hinfo from '~/utils/host-info';
|
import * as hinfo from '~/utils/host-info';
|
||||||
import { PopulatedNode } from '~/utils/node-info';
|
import { PopulatedNode } from '~/utils/node-info';
|
||||||
@@ -22,7 +22,7 @@ interface Props {
|
|||||||
isAgent?: boolean;
|
isAgent?: boolean;
|
||||||
magic?: string;
|
magic?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
nodeList?: Machine[];
|
existingTags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MachineRow({
|
export default function MachineRow({
|
||||||
@@ -31,7 +31,7 @@ export default function MachineRow({
|
|||||||
isAgent,
|
isAgent,
|
||||||
magic,
|
magic,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
nodeList,
|
existingTags,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const uiTags = useMemo(() => {
|
const uiTags = useMemo(() => {
|
||||||
const tags = uiTagsForNode(node, isAgent);
|
const tags = uiTagsForNode(node, isAgent);
|
||||||
@@ -148,10 +148,10 @@ export default function MachineRow({
|
|||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-0.5">
|
<td className="py-2 pr-0.5">
|
||||||
<MenuOptions
|
<MenuOptions
|
||||||
|
existingTags={existingTags}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
magic={magic}
|
magic={magic}
|
||||||
node={node}
|
node={node}
|
||||||
nodeList={nodeList}
|
|
||||||
users={users}
|
users={users}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Cog, Ellipsis, SquareTerminal } from 'lucide-react';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Button from '~/components/Button';
|
import Button from '~/components/Button';
|
||||||
import Menu from '~/components/Menu';
|
import Menu from '~/components/Menu';
|
||||||
import type { Machine, User } from '~/types';
|
import type { User } from '~/types';
|
||||||
import cn from '~/utils/cn';
|
import cn from '~/utils/cn';
|
||||||
import { PopulatedNode } from '~/utils/node-info';
|
import { PopulatedNode } from '~/utils/node-info';
|
||||||
import Delete from '../dialogs/delete';
|
import Delete from '../dialogs/delete';
|
||||||
@@ -18,7 +18,7 @@ interface MenuProps {
|
|||||||
magic?: string;
|
magic?: string;
|
||||||
isFullButton?: boolean;
|
isFullButton?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
nodeList?: Machine[];
|
existingTags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type Modal = 'rename' | 'expire' | 'remove' | 'routes' | 'move' | 'tags' | null;
|
type Modal = 'rename' | 'expire' | 'remove' | 'routes' | 'move' | 'tags' | null;
|
||||||
@@ -29,7 +29,7 @@ export default function MachineMenu({
|
|||||||
users,
|
users,
|
||||||
isFullButton,
|
isFullButton,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
nodeList,
|
existingTags,
|
||||||
}: MenuProps) {
|
}: MenuProps) {
|
||||||
const [modal, setModal] = useState<Modal>(null);
|
const [modal, setModal] = useState<Modal>(null);
|
||||||
const supportsTailscaleSSH =
|
const supportsTailscaleSSH =
|
||||||
@@ -77,9 +77,9 @@ export default function MachineMenu({
|
|||||||
)}
|
)}
|
||||||
{modal === 'tags' && (
|
{modal === 'tags' && (
|
||||||
<Tags
|
<Tags
|
||||||
|
existingTags={existingTags}
|
||||||
isOpen={modal === 'tags'}
|
isOpen={modal === 'tags'}
|
||||||
machine={node}
|
machine={node}
|
||||||
nodeList={nodeList}
|
|
||||||
setIsOpen={(isOpen) => {
|
setIsOpen={(isOpen) => {
|
||||||
if (!isOpen) setModal(null);
|
if (!isOpen) setModal(null);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ interface TagsProps {
|
|||||||
machine: Machine;
|
machine: Machine;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: (isOpen: boolean) => void;
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
nodeList?: Machine[];
|
existingTags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Tags({
|
export default function Tags({
|
||||||
machine,
|
machine,
|
||||||
isOpen,
|
isOpen,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
nodeList,
|
existingTags,
|
||||||
}: TagsProps) {
|
}: TagsProps) {
|
||||||
const [tags, setTags] = useState(machine.forcedTags);
|
const [tags, setTags] = useState(machine.forcedTags);
|
||||||
const [tag, setTag] = useState('tag:');
|
const [tag, setTag] = useState('tag:');
|
||||||
@@ -28,13 +28,7 @@ export default function Tags({
|
|||||||
}, [tag, tags]);
|
}, [tag, tags]);
|
||||||
|
|
||||||
const validNodeTags = useMemo(() => {
|
const validNodeTags = useMemo(() => {
|
||||||
if (!nodeList?.length) return [];
|
return existingTags?.filter((nodeTag) => !tags.includes(nodeTag)) || [];
|
||||||
const allNodeTags = Array.from(
|
|
||||||
new Set(
|
|
||||||
nodeList.flatMap((node) => [...node.validTags, ...node.forcedTags]),
|
|
||||||
),
|
|
||||||
).sort();
|
|
||||||
return allNodeTags.filter((nodeTag) => !tags.includes(nodeTag));
|
|
||||||
}, [tags]);
|
}, [tags]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import StatusCircle from '~/components/StatusCircle';
|
|||||||
import Tooltip from '~/components/Tooltip';
|
import Tooltip from '~/components/Tooltip';
|
||||||
import cn from '~/utils/cn';
|
import cn from '~/utils/cn';
|
||||||
import { getOSInfo, getTSVersion } from '~/utils/host-info';
|
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 type { Route } from './+types/machine';
|
||||||
import { mapTagsToComponents, uiTagsForNode } from './components/machine-row';
|
import { mapTagsToComponents, uiTagsForNode } from './components/machine-row';
|
||||||
import MenuOptions from './components/menu';
|
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 api = context.hsApi.getRuntimeClient(session.api_key);
|
||||||
const [node, users, allNodes] = await Promise.all([
|
const [nodes, users] = await Promise.all([api.getNodes(), api.getUsers()]);
|
||||||
api.getNode(params.id),
|
const node = nodes.find((node) => node.id === params.id);
|
||||||
api.getUsers(),
|
|
||||||
api.getNodes(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const lookup = await context.agents?.lookup([node.nodeKey]);
|
const lookup = await context.agents?.lookup([node.nodeKey]);
|
||||||
const [enhancedNode] = mapNodes([node], lookup);
|
const [enhancedNode] = mapNodes([node], lookup);
|
||||||
@@ -54,14 +51,14 @@ export async function loader({ request, params, context }: Route.LoaderArgs) {
|
|||||||
magic,
|
magic,
|
||||||
agent: context.agents?.agentID(),
|
agent: context.agents?.agentID(),
|
||||||
stats: lookup?.[enhancedNode.nodeKey],
|
stats: lookup?.[enhancedNode.nodeKey],
|
||||||
nodeList: allNodes,
|
existingTags: sortNodeTags(nodes),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const action = machineAction;
|
export const action = machineAction;
|
||||||
|
|
||||||
export default function Page({
|
export default function Page({
|
||||||
loaderData: { node, tags, users, magic, agent, stats, nodeList },
|
loaderData: { node, tags, users, magic, agent, stats, existingTags },
|
||||||
}: Route.ComponentProps) {
|
}: Route.ComponentProps) {
|
||||||
const [showRouting, setShowRouting] = useState(false);
|
const [showRouting, setShowRouting] = useState(false);
|
||||||
|
|
||||||
@@ -90,10 +87,10 @@ export default function Page({
|
|||||||
<StatusCircle className="w-4 h-4" isOnline={node.online} />
|
<StatusCircle className="w-4 h-4" isOnline={node.online} />
|
||||||
</span>
|
</span>
|
||||||
<MenuOptions
|
<MenuOptions
|
||||||
|
existingTags={existingTags}
|
||||||
isFullButton
|
isFullButton
|
||||||
magic={magic}
|
magic={magic}
|
||||||
node={node}
|
node={node}
|
||||||
nodeList={nodeList}
|
|
||||||
users={users}
|
users={users}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Link from '~/components/Link';
|
|||||||
import Tooltip from '~/components/Tooltip';
|
import Tooltip from '~/components/Tooltip';
|
||||||
import { Capabilities } from '~/server/web/roles';
|
import { Capabilities } from '~/server/web/roles';
|
||||||
import cn from '~/utils/cn';
|
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 type { Route } from './+types/overview';
|
||||||
import MachineRow from './components/machine-row';
|
import MachineRow from './components/machine-row';
|
||||||
import NewMachine from './dialogs/new';
|
import NewMachine from './dialogs/new';
|
||||||
@@ -126,6 +126,7 @@ export default function Page({ loaderData }: Route.ComponentProps) {
|
|||||||
>
|
>
|
||||||
{loaderData.populatedNodes.map((node) => (
|
{loaderData.populatedNodes.map((node) => (
|
||||||
<MachineRow
|
<MachineRow
|
||||||
|
existingTags={sortNodeTags(loaderData.nodes)}
|
||||||
isAgent={
|
isAgent={
|
||||||
loaderData.agent ? loaderData.agent === node.nodeKey : undefined
|
loaderData.agent ? loaderData.agent === node.nodeKey : undefined
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,6 @@ export default function Page({ loaderData }: Route.ComponentProps) {
|
|||||||
key={node.id}
|
key={node.id}
|
||||||
magic={loaderData.magic}
|
magic={loaderData.magic}
|
||||||
node={node}
|
node={node}
|
||||||
nodeList={loaderData.nodes}
|
|
||||||
users={loaderData.users}
|
users={loaderData.users}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user