mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-29 15:29:14 +00:00
fix(web): preserve managed config and status compatibility (#2389)
Fix web/frontend compat bugs in managed config & runtime status - Preserve [[peer]].peer_public_key when TOML configs round-trip through the web/managed NetworkConfig path - Keep old peer_urls clients working while adding structured peer metadata for new clients - Make frontend protobuf JSON normalization preserve omitted-field semantics instead of turning missing data into misleading defaults - Harden runtime status rendering against omitted or string-encoded backend fields - Expose peer-route feature flags in the web status UI
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import type { PeerRoutePair } from '../types/network'
|
||||
|
||||
export function numericValue(value: unknown): number | undefined {
|
||||
if (typeof value === 'number')
|
||||
return Number.isFinite(value) ? value : undefined
|
||||
|
||||
if (typeof value !== 'string' || value.trim() === '')
|
||||
return undefined
|
||||
|
||||
const parsed = Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : undefined
|
||||
}
|
||||
|
||||
export function peerConns(info: PeerRoutePair) {
|
||||
return info.peer?.conns || []
|
||||
}
|
||||
|
||||
function defaultConnId(info: PeerRoutePair) {
|
||||
const defaultConn = info.peer?.default_conn_id
|
||||
if (!defaultConn)
|
||||
return undefined
|
||||
|
||||
const part1 = defaultConn.part1 ?? 0
|
||||
const part2 = defaultConn.part2 ?? 0
|
||||
const part3 = defaultConn.part3 ?? 0
|
||||
const part4 = defaultConn.part4 ?? 0
|
||||
if (part1 === 0 && part2 === 0 && part3 === 0 && part4 === 0)
|
||||
return undefined
|
||||
|
||||
const toHex = (value: number) => value.toString(16).padStart(8, '0')
|
||||
const part1Hex = toHex(part1)
|
||||
const part2Hex = toHex(part2)
|
||||
const part3Hex = toHex(part3)
|
||||
const part4Hex = toHex(part4)
|
||||
return `${part1Hex}-${part2Hex.slice(0, 4)}-${part2Hex.slice(4, 8)}-${part3Hex.slice(0, 4)}-${part3Hex.slice(4, 8)}${part4Hex}`
|
||||
}
|
||||
|
||||
function defaultConnFirst(info: PeerRoutePair) {
|
||||
const conns = peerConns(info)
|
||||
const connId = defaultConnId(info)
|
||||
if (!connId)
|
||||
return conns
|
||||
|
||||
const defaultConn = conns.find(conn => conn.conn_id === connId)
|
||||
return defaultConn ? [defaultConn, ...conns.filter(conn => conn !== defaultConn)] : conns
|
||||
}
|
||||
|
||||
export function latencyMs(info: PeerRoutePair) {
|
||||
const connId = defaultConnId(info)
|
||||
let minLatencyUs: number | undefined
|
||||
|
||||
for (const conn of peerConns(info)) {
|
||||
if (!conn.stats)
|
||||
continue
|
||||
|
||||
const latencyUs = numericValue(conn.stats.latency_us)
|
||||
if (latencyUs === undefined)
|
||||
continue
|
||||
|
||||
if (connId === conn.conn_id)
|
||||
return `${Math.ceil(latencyUs / 1000)}ms`
|
||||
|
||||
minLatencyUs = Math.min(minLatencyUs ?? latencyUs, latencyUs)
|
||||
}
|
||||
|
||||
if (minLatencyUs === undefined)
|
||||
return ''
|
||||
|
||||
return `${Math.ceil(minLatencyUs / 1000)}ms`
|
||||
}
|
||||
|
||||
export function lossRate(info: PeerRoutePair) {
|
||||
for (const conn of defaultConnFirst(info)) {
|
||||
const loss = numericValue(conn.loss_rate)
|
||||
if (loss === undefined)
|
||||
continue
|
||||
|
||||
return `${Math.round(loss * 100)}%`
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
@@ -1,24 +1,30 @@
|
||||
import { IPv4, IPv6 } from 'ip-num/IPNumber'
|
||||
import { Ipv4Addr, Ipv4Inet, Ipv6Addr } from '../types/network'
|
||||
|
||||
export function ipv4ToString(ip: Ipv4Addr) {
|
||||
return IPv4.fromNumber(ip.addr).toString()
|
||||
export function ipv4ToString(ip: Ipv4Addr | null | undefined) {
|
||||
if (!ip) {
|
||||
return ''
|
||||
}
|
||||
return IPv4.fromNumber(ip.addr ?? 0).toString()
|
||||
}
|
||||
|
||||
export function ipv4InetToString(ip: Ipv4Inet | undefined) {
|
||||
if (ip?.address === undefined) {
|
||||
return 'undefined'
|
||||
}
|
||||
return `${ipv4ToString(ip.address)}/${ip.network_length}`
|
||||
return `${ipv4ToString(ip.address)}/${ip.network_length ?? 0}`
|
||||
}
|
||||
|
||||
export function ipv6ToString(ip: Ipv6Addr) {
|
||||
export function ipv6ToString(ip: Ipv6Addr | null | undefined) {
|
||||
if (!ip) {
|
||||
return ''
|
||||
}
|
||||
return IPv6.fromBigInt(
|
||||
(BigInt(ip.part1) << BigInt(96))
|
||||
+ (BigInt(ip.part2) << BigInt(64))
|
||||
+ (BigInt(ip.part3) << BigInt(32))
|
||||
+ BigInt(ip.part4),
|
||||
)
|
||||
(BigInt(ip.part1 ?? 0) << BigInt(96))
|
||||
+ (BigInt(ip.part2 ?? 0) << BigInt(64))
|
||||
+ (BigInt(ip.part3 ?? 0) << BigInt(32))
|
||||
+ BigInt(ip.part4 ?? 0),
|
||||
).toString()
|
||||
}
|
||||
|
||||
function toHexString(uint64: bigint, padding = 9): string {
|
||||
@@ -43,14 +49,17 @@ function uint32ToUuid(part1: number, part2: number, part3: number, part4: number
|
||||
}
|
||||
|
||||
export interface UUID {
|
||||
part1: number;
|
||||
part2: number;
|
||||
part3: number;
|
||||
part4: number;
|
||||
part1?: number;
|
||||
part2?: number;
|
||||
part3?: number;
|
||||
part4?: number;
|
||||
}
|
||||
|
||||
export function UuidToStr(uuid: UUID): string {
|
||||
return uint32ToUuid(uuid.part1, uuid.part2, uuid.part3, uuid.part4);
|
||||
export function UuidToStr(uuid: UUID | null | undefined): string {
|
||||
if (!uuid) {
|
||||
return '';
|
||||
}
|
||||
return uint32ToUuid(uuid.part1 ?? 0, uuid.part2 ?? 0, uuid.part3 ?? 0, uuid.part4 ?? 0);
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
@@ -71,11 +80,12 @@ export interface DeviceInfo {
|
||||
}
|
||||
|
||||
export function buildDeviceInfo(device: any): DeviceInfo {
|
||||
const runningInstances = device.info?.running_network_instances ?? [];
|
||||
let dev_info: DeviceInfo = {
|
||||
hostname: device.info?.hostname,
|
||||
public_ip: device.client_url,
|
||||
running_network_instances: device.info?.running_network_instances.map((instance: any) => UuidToStr(instance)),
|
||||
running_network_count: device.info?.running_network_instances.length,
|
||||
running_network_instances: runningInstances.map((instance: any) => UuidToStr(instance)),
|
||||
running_network_count: runningInstances.length,
|
||||
report_time: device.info?.report_time,
|
||||
easytier_version: device.info?.easytier_version,
|
||||
machine_id: UuidToStr(device.info?.machine_id),
|
||||
|
||||
Reference in New Issue
Block a user