mirror of
https://github.com/tale/headplane.git
synced 2026-07-27 16:18:57 +00:00
948cfad58c
Closes HP-555.
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
import type { Machine } from "~/types";
|
|
|
|
import type { Capabilities } from "../capabilities";
|
|
import type { Transport } from "../transport";
|
|
|
|
interface RawMachine extends Omit<Machine, "tags"> {
|
|
tags?: string[];
|
|
forcedTags?: string[];
|
|
validTags?: string[];
|
|
invalidTags?: string[];
|
|
}
|
|
|
|
export interface NodeApi {
|
|
list(): Promise<Machine[]>;
|
|
get(id: string): Promise<Machine>;
|
|
delete(id: string): Promise<void>;
|
|
register(user: string, key: string): Promise<Machine>;
|
|
approveRoutes(id: string, routes: string[]): Promise<void>;
|
|
expire(id: string): Promise<void>;
|
|
rename(id: string, newName: string): Promise<void>;
|
|
setTags(id: string, tags: string[]): Promise<void>;
|
|
/**
|
|
* Reassign a node to a different user. Only present when
|
|
* `capabilities.nodeOwnerIsImmutable` is false (Headscale < 0.28).
|
|
*/
|
|
reassignUser?: (id: string, user: string) => Promise<void>;
|
|
}
|
|
|
|
export function makeNodeApi(
|
|
transport: Transport,
|
|
capabilities: Capabilities,
|
|
apiKey: string,
|
|
): NodeApi {
|
|
function normalize(raw: RawMachine): Machine {
|
|
if (capabilities.nodeTagsAreFlat) {
|
|
return { ...raw, tags: raw.tags ?? [] } as Machine;
|
|
}
|
|
const tags = Array.from(new Set([...(raw.forcedTags ?? []), ...(raw.validTags ?? [])]));
|
|
return { ...raw, tags } as Machine;
|
|
}
|
|
|
|
const api: NodeApi = {
|
|
list: async () => {
|
|
const { nodes } = await transport.request<{ nodes: RawMachine[] }>({
|
|
method: "GET",
|
|
path: "v1/node",
|
|
apiKey,
|
|
});
|
|
return nodes.map(normalize);
|
|
},
|
|
get: async (id) => {
|
|
const { node } = await transport.request<{ node: RawMachine }>({
|
|
method: "GET",
|
|
path: `v1/node/${id}`,
|
|
apiKey,
|
|
});
|
|
return normalize(node);
|
|
},
|
|
delete: async (id) => {
|
|
await transport.request({ method: "DELETE", path: `v1/node/${id}`, apiKey });
|
|
},
|
|
register: async (user, key) => {
|
|
// Headscale's node-register endpoint expects the registration
|
|
// params as both query string and body — preserved as-is.
|
|
// Pre-0.29 expects the raw 24-char registration ID; 0.29+ expects
|
|
// the full `hskey-authreq-<id>` AuthID.
|
|
const registerKey = capabilities.registerKeyIncludesAuthReqPrefix
|
|
? key
|
|
: key.replace(/^hskey-authreq-/, "");
|
|
const qp = new URLSearchParams();
|
|
qp.append("user", user);
|
|
qp.append("key", registerKey);
|
|
const { node } = await transport.request<{ node: RawMachine }>({
|
|
method: "POST",
|
|
path: `v1/node/register?${qp.toString()}`,
|
|
apiKey,
|
|
body: { user, key: registerKey },
|
|
});
|
|
return normalize(node);
|
|
},
|
|
approveRoutes: async (id, routes) => {
|
|
await transport.request({
|
|
method: "POST",
|
|
path: `v1/node/${id}/approve_routes`,
|
|
apiKey,
|
|
body: { routes },
|
|
});
|
|
},
|
|
expire: async (id) => {
|
|
await transport.request({ method: "POST", path: `v1/node/${id}/expire`, apiKey });
|
|
},
|
|
rename: async (id, newName) => {
|
|
await transport.request({
|
|
method: "POST",
|
|
path: `v1/node/${id}/rename/${encodeURIComponent(newName)}`,
|
|
apiKey,
|
|
});
|
|
},
|
|
setTags: async (id, tags) => {
|
|
await transport.request({
|
|
method: "POST",
|
|
path: `v1/node/${id}/tags`,
|
|
apiKey,
|
|
body: { tags },
|
|
});
|
|
},
|
|
};
|
|
|
|
if (!capabilities.nodeOwnerIsImmutable) {
|
|
api.reassignUser = async (id, user) => {
|
|
await transport.request({
|
|
method: "POST",
|
|
path: `v1/node/${id}/user`,
|
|
apiKey,
|
|
body: { user },
|
|
});
|
|
};
|
|
}
|
|
|
|
return api;
|
|
}
|