This commit is contained in:
@@ -20,6 +20,16 @@ adminRoutes.get('/admin/tailscale/nodes', async (c) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
adminRoutes.delete('/admin/tailscale/nodes/:nodeId', async (c) => {
|
||||||
|
const nodeId = c.req.param('nodeId').trim()
|
||||||
|
if (!nodeId) {
|
||||||
|
return c.json({ error: 'node_id_required' }, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
await headscaleNodeDiscovery.deleteNode(nodeId)
|
||||||
|
return c.json({ deleted: true, nodeId })
|
||||||
|
})
|
||||||
|
|
||||||
adminRoutes.get('/admin/tailscale/routes', async (c) => {
|
adminRoutes.get('/admin/tailscale/routes', async (c) => {
|
||||||
return c.json({
|
return c.json({
|
||||||
routes: await headscaleNodeDiscovery.listRouteStatus(),
|
routes: await headscaleNodeDiscovery.listRouteStatus(),
|
||||||
|
|||||||
@@ -71,6 +71,23 @@ class HeadscaleNodeDiscovery {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteNode(nodeId: string): Promise<void> {
|
||||||
|
if (!config.headscaleApiKey) {
|
||||||
|
throw new Error('HEADSCALE_API_KEY is not configured')
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodedNodeId = encodeURIComponent(nodeId)
|
||||||
|
let response = await this.fetchHeadscale(`/api/v1/node/${encodedNodeId}`, { method: 'DELETE' })
|
||||||
|
if (response.status === 404) {
|
||||||
|
response = await this.fetchHeadscale(`/api/v1/machine/${encodedNodeId}`, { method: 'DELETE' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const detail = await response.text().catch(() => '')
|
||||||
|
throw new Error(`Headscale node deletion failed (${response.status})${detail ? `: ${detail}` : ''}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async listRouteStatus(): Promise<HeadscaleRouteStatus[]> {
|
async listRouteStatus(): Promise<HeadscaleRouteStatus[]> {
|
||||||
const nodes = await this.listNodes()
|
const nodes = await this.listNodes()
|
||||||
return nodes.flatMap((node) => {
|
return nodes.flatMap((node) => {
|
||||||
@@ -141,9 +158,10 @@ class HeadscaleNodeDiscovery {
|
|||||||
return this.extractNodeList(await response.json().catch(() => null))
|
return this.extractNodeList(await response.json().catch(() => null))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchHeadscale(path: string) {
|
private fetchHeadscale(path: string, init?: RequestInit) {
|
||||||
return fetch(`${config.headscaleBaseUrl}${path}`, {
|
return fetch(`${config.headscaleBaseUrl}${path}`, {
|
||||||
headers: { authorization: `Bearer ${config.headscaleApiKey}` },
|
headers: { authorization: `Bearer ${config.headscaleApiKey}` },
|
||||||
|
...init,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user