Merge pull request #436 from kronenpj/Adapt-owner-change-2922

Adapt to removal of Node Ownership Change from Headscale #2922
This commit is contained in:
Aarnav Tale
2026-02-08 11:06:52 -05:00
committed by GitHub
6 changed files with 27 additions and 9 deletions
@@ -27,6 +27,7 @@ interface Props {
magic?: string;
isDisabled?: boolean;
existingTags?: string[];
supportsNodeOwnerChange: boolean;
}
export default function MachineRow({
@@ -36,6 +37,7 @@ export default function MachineRow({
magic,
isDisabled,
existingTags,
supportsNodeOwnerChange,
}: Props) {
const uiTags = useMemo(() => uiTagsForNode(node, isAgent), [node, isAgent]);
@@ -142,6 +144,7 @@ export default function MachineRow({
magic={magic}
node={node}
users={users}
supportsNodeOwnerChange={supportsNodeOwnerChange}
/>
</td>
</tr>
+3 -1
View File
@@ -19,6 +19,7 @@ interface MenuProps {
isFullButton?: boolean;
isDisabled?: boolean;
existingTags?: string[];
supportsNodeOwnerChange: boolean;
}
type Modal = 'rename' | 'expire' | 'remove' | 'routes' | 'move' | 'tags' | null;
@@ -30,6 +31,7 @@ export default function MachineMenu({
isFullButton,
isDisabled,
existingTags,
supportsNodeOwnerChange,
}: MenuProps) {
const [modal, setModal] = useState<Modal>(null);
const supportsTailscaleSSH =
@@ -164,7 +166,7 @@ export default function MachineMenu({
<Menu.Item key="rename">Edit machine name</Menu.Item>
<Menu.Item key="routes">Edit route settings</Menu.Item>
<Menu.Item key="tags">Edit ACL tags</Menu.Item>
<Menu.Item key="move">Change owner</Menu.Item>
{supportsNodeOwnerChange && <Menu.Item key="move">Change owner</Menu.Item>}
</Menu.Section>
<Menu.Section>
<Menu.Item key="expire" textValue="Expire">
+4 -1
View File
@@ -44,6 +44,7 @@ export async function loader({ request, params, context }: Route.LoaderArgs) {
const lookup = await context.agents?.lookup([node.nodeKey]);
const [enhancedNode] = mapNodes([node], lookup);
const tags = [...node.tags].sort();
const supportsNodeOwnerChange = ! context.hsApi.clientHelpers.isAtleast("0.28.0-beta.1");
return {
node: enhancedNode,
@@ -53,13 +54,14 @@ export async function loader({ request, params, context }: Route.LoaderArgs) {
agent: context.agents?.agentID(),
stats: lookup?.[enhancedNode.nodeKey],
existingTags: sortNodeTags(nodes),
supportsNodeOwnerChange: supportsNodeOwnerChange,
};
}
export const action = machineAction;
export default function Page({
loaderData: { node, tags, users, magic, agent, stats, existingTags },
loaderData: { node, tags, users, magic, agent, stats, existingTags, supportsNodeOwnerChange },
}: Route.ComponentProps) {
const [showRouting, setShowRouting] = useState(false);
@@ -93,6 +95,7 @@ export default function Page({
magic={magic}
node={node}
users={users}
supportsNodeOwnerChange={supportsNodeOwnerChange}
/>
</div>
<div className="mb-4 flex gap-1">
+3
View File
@@ -48,6 +48,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
const stats = await context.agents?.lookup(nodes.map((node) => node.nodeKey));
const populatedNodes = mapNodes(nodes, stats);
const supportsNodeOwnerChange = ! context.hsApi.clientHelpers.isAtleast("0.28.0-beta.1");
return {
populatedNodes,
@@ -63,6 +64,7 @@ export async function loader({ request, context }: Route.LoaderArgs) {
Capabilities.generate_authkeys,
),
subject: user.subject,
supportsNodeOwnerChange: supportsNodeOwnerChange,
};
}
@@ -380,6 +382,7 @@ export default function Page({ loaderData }: Route.ComponentProps) {
magic={loaderData.magic}
node={node}
users={loaderData.users}
supportsNodeOwnerChange={loaderData.supportsNodeOwnerChange}
/>
))
)}
+9 -7
View File
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest';
import { getNode, getRuntimeClient, HS_VERSIONS } from './setup/env';
import { getNode, getRuntimeClient, getIsAtLeast, HS_VERSIONS } from './setup/env';
describe.sequential.for(HS_VERSIONS)('Headscale %s: Users', (version) => {
let workingNodeId: string;
@@ -41,13 +41,15 @@ describe.sequential.for(HS_VERSIONS)('Headscale %s: Users', (version) => {
});
test('nodes can be reassigned to another user', async () => {
const client = await getRuntimeClient(version);
const user = await client.createUser('node-reassign@');
if (! getIsAtLeast("0.28.0")) {
const client = await getRuntimeClient(version);
const user = await client.createUser('node-reassign@');
await client.setNodeUser(workingNodeId, user.id);
const reassignedNode = await client.getNode(workingNodeId);
expect(reassignedNode).toBeDefined();
expect(reassignedNode.user.name).toBe(user.name);
await client.setNodeUser(workingNodeId, user.id);
const reassignedNode = await client.getNode(workingNodeId);
expect(reassignedNode).toBeDefined();
expect(reassignedNode.user.name).toBe(user.name);
}
});
test('nodes can be expired', async () => {
+5
View File
@@ -43,6 +43,11 @@ export async function getRuntimeClient(version: Version) {
return bootstrap.getRuntimeClient(env.apiKey);
}
export async function getIsAtLeast(version: Version) {
const { env, bootstrap } = await ensureVersion(version);
return bootstrap.clientHelpers.isAtleast;
}
export async function getNode(version: Version) {
const { tailscaleNode } = await ensureVersion(version);
return {