connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user