connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-08 16:39:47 +09:00
parent f21c350400
commit a252397023
4 changed files with 90 additions and 5 deletions
+66 -4
View File
@@ -24,7 +24,41 @@ export type TeachingAgentNode = {
lastSeenAt: string | null
}
export type HeadscaleNodeStatus = {
id: string
name: string
hostname: string
user: string
ip: string
ips: string[]
tags: string[]
routes: string[]
connected: boolean
expired: boolean
lastSeenAt: string | null
}
class HeadscaleNodeDiscovery {
async listNodesStatus(): Promise<HeadscaleNodeStatus[]> {
const nodes = await this.listNodes()
return nodes.map((node) => {
const ip = this.extractIpAddress(node)
return {
id: this.readString(node, 'id') || this.readString(node, 'ID') || this.readString(node, 'nodeId') || this.readString(node, 'node_id') || ip || '',
name: this.readString(node, 'name') || this.readString(node, 'givenName') || this.readString(node, 'given_name') || this.readString(node, 'hostname') || ip || '-',
hostname: this.readString(node, 'hostname') || this.readString(node, 'givenName') || this.readString(node, 'given_name') || this.readString(node, 'name') || '-',
user: this.extractUserName(node) || '-',
ip: ip ?? '-',
ips: this.extractIpAddresses(node),
tags: this.extractTags(node),
routes: this.extractRoutes(node),
connected: this.readBoolean(node, 'online') || this.readBoolean(node, 'connected') || this.readString(node, 'connected').toLowerCase() === 'online',
expired: this.readBoolean(node, 'expired'),
lastSeenAt: this.extractLastSeenAt(node),
}
})
}
async listTeachingAgents(accountEmail?: string): Promise<TeachingAgentNode[]> {
const nodes = await this.listNodes().catch(() => [])
const agents = nodes
@@ -190,6 +224,10 @@ class HeadscaleNodeDiscovery {
}
private extractIpAddress(node: HeadscaleNode): string | null {
return this.extractIpAddresses(node)[0] ?? null
}
private extractIpAddresses(node: HeadscaleNode): string[] {
const values = [
node.ipAddresses,
node.ip_addresses,
@@ -198,14 +236,38 @@ class HeadscaleNodeDiscovery {
]
for (const value of values) {
if (typeof value === 'string' && this.isTailscaleIp(value)) return value
if (typeof value === 'string' && this.isTailscaleIp(value)) return [value]
if (Array.isArray(value)) {
const ipAddress = value.find((entry) => typeof entry === 'string' && this.isTailscaleIp(entry))
if (typeof ipAddress === 'string') return ipAddress
const ipAddresses = value.filter((entry): entry is string => typeof entry === 'string' && this.isTailscaleIp(entry))
if (ipAddresses.length) return ipAddresses
}
}
return null
return []
}
private extractRoutes(node: HeadscaleNode): string[] {
const direct = [
node.advertisedRoutes,
node.approvedRoutes,
node.subnetRoutes,
node.routes,
]
for (const value of direct) {
if (!Array.isArray(value)) continue
const routes = value
.map((entry) => {
if (typeof entry === 'string') return entry
if (!entry || typeof entry !== 'object') return ''
const record = entry as Record<string, unknown>
return this.readString(record, 'prefix') || this.readString(record, 'route') || this.readString(record, 'ipPrefix')
})
.filter(Boolean)
if (routes.length) return routes
}
return []
}
private extractLastSeenAt(node: HeadscaleNode): string | null {