Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a322308623 | |||
| ec5374900c | |||
| 49ce265d7e |
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-26 - 13.22.0 - feat(remoteingress)
|
||||||
|
add remote ingress performance configuration and expose tunnel transport metrics
|
||||||
|
|
||||||
|
- upgrade @serve.zone/remoteingress to support performance tuning and richer tunnel status data
|
||||||
|
- pass remote ingress performance settings through router startup and config APIs
|
||||||
|
- serialize allowed-edge sync operations and await route update hooks to avoid tunnel sync races
|
||||||
|
- expose UDP listen ports and transport, flow control, queue, and traffic metrics in remote ingress APIs and ops UI
|
||||||
|
|
||||||
## 2026-04-26 - 13.21.1 - fix(deps)
|
## 2026-04-26 - 13.21.1 - fix(deps)
|
||||||
bump @push.rocks/smartproxy to ^27.8.1
|
bump @push.rocks/smartproxy to ^27.8.1
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "13.21.1",
|
"version": "13.22.0",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"@push.rocks/smartnetwork": "^4.6.0",
|
"@push.rocks/smartnetwork": "^4.6.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartproxy": "^27.8.1",
|
"@push.rocks/smartproxy": "^27.8.2",
|
||||||
"@push.rocks/smartradius": "^1.1.1",
|
"@push.rocks/smartradius": "^1.1.1",
|
||||||
"@push.rocks/smartrequest": "^5.0.1",
|
"@push.rocks/smartrequest": "^5.0.1",
|
||||||
"@push.rocks/smartrx": "^3.0.10",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"@push.rocks/taskbuffer": "^8.0.2",
|
"@push.rocks/taskbuffer": "^8.0.2",
|
||||||
"@serve.zone/catalog": "^2.12.4",
|
"@serve.zone/catalog": "^2.12.4",
|
||||||
"@serve.zone/interfaces": "^5.4.3",
|
"@serve.zone/interfaces": "^5.4.3",
|
||||||
"@serve.zone/remoteingress": "^4.15.3",
|
"@serve.zone/remoteingress": "^4.17.0",
|
||||||
"@tsclass/tsclass": "^9.5.0",
|
"@tsclass/tsclass": "^9.5.0",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"lru-cache": "^11.3.5",
|
"lru-cache": "^11.3.5",
|
||||||
|
|||||||
Generated
+10
-10
@@ -81,8 +81,8 @@ importers:
|
|||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
'@push.rocks/smartproxy':
|
'@push.rocks/smartproxy':
|
||||||
specifier: ^27.8.1
|
specifier: ^27.8.2
|
||||||
version: 27.8.1
|
version: 27.8.2
|
||||||
'@push.rocks/smartradius':
|
'@push.rocks/smartradius':
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
@@ -111,8 +111,8 @@ importers:
|
|||||||
specifier: ^5.4.3
|
specifier: ^5.4.3
|
||||||
version: 5.4.3
|
version: 5.4.3
|
||||||
'@serve.zone/remoteingress':
|
'@serve.zone/remoteingress':
|
||||||
specifier: ^4.15.3
|
specifier: ^4.17.0
|
||||||
version: 4.15.3
|
version: 4.17.0
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.5.0
|
specifier: ^9.5.0
|
||||||
version: 9.5.0
|
version: 9.5.0
|
||||||
@@ -1284,8 +1284,8 @@ packages:
|
|||||||
'@push.rocks/smartpromise@4.2.3':
|
'@push.rocks/smartpromise@4.2.3':
|
||||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@27.8.1':
|
'@push.rocks/smartproxy@27.8.2':
|
||||||
resolution: {integrity: sha512-1Gaubl/M7tMdlNUi1gKV8A2LQ4+9/Eu3ynVzMO2SRXWHdBBOUms2v2Iv+gOUf+8OIYRL16FreJyF2/DbsrQjKg==}
|
resolution: {integrity: sha512-4T20SKk4oewAg/ztazxxtkHIip3lM0ksZmXZN/zx2uC68HdZRroK5oekMYcIeD2AfvjGYUK1vI1MMgQz+glHXQ==}
|
||||||
|
|
||||||
'@push.rocks/smartpuppeteer@2.0.5':
|
'@push.rocks/smartpuppeteer@2.0.5':
|
||||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||||
@@ -1594,8 +1594,8 @@ packages:
|
|||||||
'@serve.zone/interfaces@5.4.3':
|
'@serve.zone/interfaces@5.4.3':
|
||||||
resolution: {integrity: sha512-9ijFhHoC7GYyyAUJbBoDYmcoCmIXTFPiD6fI3x68SWiC0xA+2LG0nOe14D32c1QN9X/3i2Ac5/1sUibfjHsIGg==}
|
resolution: {integrity: sha512-9ijFhHoC7GYyyAUJbBoDYmcoCmIXTFPiD6fI3x68SWiC0xA+2LG0nOe14D32c1QN9X/3i2Ac5/1sUibfjHsIGg==}
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.15.3':
|
'@serve.zone/remoteingress@4.17.0':
|
||||||
resolution: {integrity: sha512-kg/bmR+qcFRFuigTDr5Fao72cb7m/mSkI5APm7KZDKSUYTFuytNoj6KCIE0ICkc3Nh34y8oDwFJsS6oFo64AyQ==}
|
resolution: {integrity: sha512-q1g2Zm1Yh825cMiF8/W1iQlOLGqgmWBrtzDqNgF5hH31HP2zHHtC2+XPyB+1kEphsztlXzPMlcRpfCRwuQUexA==}
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0':
|
'@sindresorhus/is@5.6.0':
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
@@ -6512,7 +6512,7 @@ snapshots:
|
|||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3': {}
|
'@push.rocks/smartpromise@4.2.3': {}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@27.8.1':
|
'@push.rocks/smartproxy@27.8.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartlog': 3.2.2
|
'@push.rocks/smartlog': 3.2.2
|
||||||
@@ -6933,7 +6933,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@tsclass/tsclass': 9.5.0
|
'@tsclass/tsclass': 9.5.0
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.15.3':
|
'@serve.zone/remoteingress@4.17.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartnftables': 1.1.0
|
'@push.rocks/smartnftables': 1.1.0
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '13.21.1',
|
version: '13.22.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-3
@@ -178,6 +178,8 @@ export interface IDcRouterOptions {
|
|||||||
certPath?: string;
|
certPath?: string;
|
||||||
keyPath?: string;
|
keyPath?: string;
|
||||||
};
|
};
|
||||||
|
/** Performance profile and limits for remote ingress hub/edge tunnels. */
|
||||||
|
performance?: import('../ts_interfaces/data/remoteingress.js').IRemoteIngressPerformanceConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -570,12 +572,16 @@ export class DcRouter {
|
|||||||
this.referenceResolver,
|
this.referenceResolver,
|
||||||
// Sync routes to RemoteIngressManager whenever routes change,
|
// Sync routes to RemoteIngressManager whenever routes change,
|
||||||
// then push updated derived ports to the Rust hub binary
|
// then push updated derived ports to the Rust hub binary
|
||||||
(routes) => {
|
async (routes) => {
|
||||||
if (this.remoteIngressManager) {
|
if (this.remoteIngressManager) {
|
||||||
this.remoteIngressManager.setRoutes(routes as any[]);
|
this.remoteIngressManager.setRoutes(routes as any[]);
|
||||||
}
|
}
|
||||||
if (this.tunnelManager) {
|
if (this.tunnelManager) {
|
||||||
this.tunnelManager.syncAllowedEdges();
|
try {
|
||||||
|
await this.tunnelManager.syncAllowedEdges();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
logger.log('error', `Failed to sync Remote Ingress allowed edges: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@@ -1120,7 +1126,12 @@ export class DcRouter {
|
|||||||
// to SmartProxy with PROXY protocol v1 headers to preserve client IPs.
|
// to SmartProxy with PROXY protocol v1 headers to preserve client IPs.
|
||||||
if (this.options.remoteIngressConfig?.enabled) {
|
if (this.options.remoteIngressConfig?.enabled) {
|
||||||
smartProxyConfig.acceptProxyProtocol = true;
|
smartProxyConfig.acceptProxyProtocol = true;
|
||||||
smartProxyConfig.proxyIPs = ['127.0.0.1'];
|
if (!smartProxyConfig.proxyIPs) {
|
||||||
|
smartProxyConfig.proxyIPs = [];
|
||||||
|
}
|
||||||
|
if (!smartProxyConfig.proxyIPs.includes('127.0.0.1')) {
|
||||||
|
smartProxyConfig.proxyIPs.push('127.0.0.1');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VPN uses socket mode with PP v2 — SmartProxy must accept proxy protocol from localhost
|
// VPN uses socket mode with PP v2 — SmartProxy must accept proxy protocol from localhost
|
||||||
@@ -2270,6 +2281,7 @@ export class DcRouter {
|
|||||||
tunnelPort: riCfg.tunnelPort ?? 8443,
|
tunnelPort: riCfg.tunnelPort ?? 8443,
|
||||||
targetHost: '127.0.0.1',
|
targetHost: '127.0.0.1',
|
||||||
tls: tlsConfig,
|
tls: tlsConfig,
|
||||||
|
performance: riCfg.performance,
|
||||||
});
|
});
|
||||||
await this.tunnelManager.start();
|
await this.tunnelManager.start();
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class RouteConfigManager {
|
|||||||
private getHttp3Config?: () => IHttp3Config | undefined,
|
private getHttp3Config?: () => IHttp3Config | undefined,
|
||||||
private getVpnClientIpsForRoute?: (route: IDcRouterRouteConfig, routeId?: string) => TIpAllowEntry[],
|
private getVpnClientIpsForRoute?: (route: IDcRouterRouteConfig, routeId?: string) => TIpAllowEntry[],
|
||||||
private referenceResolver?: ReferenceResolver,
|
private referenceResolver?: ReferenceResolver,
|
||||||
private onRoutesApplied?: (routes: plugins.smartproxy.IRouteConfig[]) => void,
|
private onRoutesApplied?: (routes: plugins.smartproxy.IRouteConfig[]) => void | Promise<void>,
|
||||||
private getRuntimeRoutes?: () => plugins.smartproxy.IRouteConfig[],
|
private getRuntimeRoutes?: () => plugins.smartproxy.IRouteConfig[],
|
||||||
private hydrateStoredRoute?: (storedRoute: IRoute) => plugins.smartproxy.IRouteConfig | undefined,
|
private hydrateStoredRoute?: (storedRoute: IRoute) => plugins.smartproxy.IRouteConfig | undefined,
|
||||||
) {}
|
) {}
|
||||||
@@ -540,7 +540,7 @@ export class RouteConfigManager {
|
|||||||
|
|
||||||
// Notify listeners (e.g. RemoteIngressManager) of the route set
|
// Notify listeners (e.g. RemoteIngressManager) of the route set
|
||||||
if (this.onRoutesApplied) {
|
if (this.onRoutesApplied) {
|
||||||
this.onRoutesApplied(enabledRoutes);
|
await this.onRoutesApplied(enabledRoutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('info', `Applied ${enabledRoutes.length} routes to SmartProxy (${this.routes.size} total)`);
|
logger.log('info', `Applied ${enabledRoutes.length} routes to SmartProxy (${this.routes.size} total)`);
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ export class ConfigHandler {
|
|||||||
hubDomain: riCfg?.hubDomain || null,
|
hubDomain: riCfg?.hubDomain || null,
|
||||||
tlsMode,
|
tlsMode,
|
||||||
connectedEdgeIps,
|
connectedEdgeIps,
|
||||||
|
performance: riCfg?.performance,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export class RemoteIngressHandler {
|
|||||||
...e,
|
...e,
|
||||||
secret: '********', // Never expose secrets via API
|
secret: '********', // Never expose secrets via API
|
||||||
effectiveListenPorts: manager.getEffectiveListenPorts(e),
|
effectiveListenPorts: manager.getEffectiveListenPorts(e),
|
||||||
|
effectiveListenPortsUdp: manager.getEffectiveListenPortsUdp(e),
|
||||||
manualPorts: breakdown.manual,
|
manualPorts: breakdown.manual,
|
||||||
derivedPorts: breakdown.derived,
|
derivedPorts: breakdown.derived,
|
||||||
};
|
};
|
||||||
@@ -133,6 +134,7 @@ export class RemoteIngressHandler {
|
|||||||
...edge,
|
...edge,
|
||||||
secret: '********',
|
secret: '********',
|
||||||
effectiveListenPorts: manager.getEffectiveListenPorts(edge),
|
effectiveListenPorts: manager.getEffectiveListenPorts(edge),
|
||||||
|
effectiveListenPortsUdp: manager.getEffectiveListenPortsUdp(edge),
|
||||||
manualPorts: breakdown.manual,
|
manualPorts: breakdown.manual,
|
||||||
derivedPorts: breakdown.derived,
|
derivedPorts: breakdown.derived,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface ITunnelManagerConfig {
|
|||||||
certPem?: string;
|
certPem?: string;
|
||||||
keyPem?: string;
|
keyPem?: string;
|
||||||
};
|
};
|
||||||
|
performance?: import('../../ts_interfaces/data/remoteingress.js').IRemoteIngressPerformanceConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +21,7 @@ export class TunnelManager {
|
|||||||
private config: ITunnelManagerConfig;
|
private config: ITunnelManagerConfig;
|
||||||
private edgeStatuses: Map<string, IRemoteIngressStatus> = new Map();
|
private edgeStatuses: Map<string, IRemoteIngressStatus> = new Map();
|
||||||
private reconcileInterval: ReturnType<typeof setInterval> | null = null;
|
private reconcileInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
private syncChain: Promise<void> = Promise.resolve();
|
||||||
|
|
||||||
constructor(manager: RemoteIngressManager, config: ITunnelManagerConfig = {}) {
|
constructor(manager: RemoteIngressManager, config: ITunnelManagerConfig = {}) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@@ -66,7 +68,8 @@ export class TunnelManager {
|
|||||||
tunnelPort: this.config.tunnelPort ?? 8443,
|
tunnelPort: this.config.tunnelPort ?? 8443,
|
||||||
targetHost: this.config.targetHost ?? '127.0.0.1',
|
targetHost: this.config.targetHost ?? '127.0.0.1',
|
||||||
tls: this.config.tls,
|
tls: this.config.tls,
|
||||||
});
|
...(this.config.performance ? { performance: this.config.performance } : {}),
|
||||||
|
} as any);
|
||||||
|
|
||||||
// Send allowed edges to the hub
|
// Send allowed edges to the hub
|
||||||
await this.syncAllowedEdges();
|
await this.syncAllowedEdges();
|
||||||
@@ -107,20 +110,23 @@ export class TunnelManager {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
existing.activeTunnels = rustEdge.activeStreams;
|
existing.activeTunnels = rustEdge.activeStreams;
|
||||||
existing.lastHeartbeat = Date.now();
|
existing.lastHeartbeat = Date.now();
|
||||||
|
this.applyRustStatus(existing, rustEdge);
|
||||||
// Update peer address if available from Rust hub
|
// Update peer address if available from Rust hub
|
||||||
if (rustEdge.peerAddr) {
|
if (rustEdge.peerAddr) {
|
||||||
existing.publicIp = rustEdge.peerAddr;
|
existing.publicIp = rustEdge.peerAddr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Missed edgeConnected event — add entry
|
// Missed edgeConnected event — add entry
|
||||||
this.edgeStatuses.set(rustEdge.edgeId, {
|
const status: IRemoteIngressStatus = {
|
||||||
edgeId: rustEdge.edgeId,
|
edgeId: rustEdge.edgeId,
|
||||||
connected: true,
|
connected: true,
|
||||||
publicIp: rustEdge.peerAddr || null,
|
publicIp: rustEdge.peerAddr || null,
|
||||||
activeTunnels: rustEdge.activeStreams,
|
activeTunnels: rustEdge.activeStreams,
|
||||||
lastHeartbeat: Date.now(),
|
lastHeartbeat: Date.now(),
|
||||||
connectedAt: rustEdge.connectedAt * 1000,
|
connectedAt: rustEdge.connectedAt * 1000,
|
||||||
});
|
};
|
||||||
|
this.applyRustStatus(status, rustEdge);
|
||||||
|
this.edgeStatuses.set(rustEdge.edgeId, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +143,22 @@ export class TunnelManager {
|
|||||||
* Call this after creating/deleting/updating edges.
|
* Call this after creating/deleting/updating edges.
|
||||||
*/
|
*/
|
||||||
public async syncAllowedEdges(): Promise<void> {
|
public async syncAllowedEdges(): Promise<void> {
|
||||||
const edges = this.manager.getAllowedEdges();
|
const run = this.syncChain.catch(() => {}).then(async () => {
|
||||||
await this.hub.updateAllowedEdges(edges);
|
const edges = this.manager.getAllowedEdges();
|
||||||
|
await this.hub.updateAllowedEdges(edges as any);
|
||||||
|
});
|
||||||
|
this.syncChain = run;
|
||||||
|
await run;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyRustStatus(status: IRemoteIngressStatus, rustEdge: any): void {
|
||||||
|
status.transportMode = rustEdge.transportMode;
|
||||||
|
status.fallbackUsed = rustEdge.fallbackUsed;
|
||||||
|
status.performance = rustEdge.performance;
|
||||||
|
status.flowControl = rustEdge.flowControl;
|
||||||
|
status.queues = rustEdge.queues;
|
||||||
|
status.traffic = rustEdge.traffic;
|
||||||
|
status.udp = rustEdge.udp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ export class RemoteIngress {
|
|||||||
public name: string;
|
public name: string;
|
||||||
public secret: string;
|
public secret: string;
|
||||||
public listenPorts: number[];
|
public listenPorts: number[];
|
||||||
|
public listenPortsUdp?: number[];
|
||||||
public enabled: boolean;
|
public enabled: boolean;
|
||||||
public autoDerivePorts: boolean;
|
public autoDerivePorts: boolean;
|
||||||
public tags?: string[];
|
public tags?: string[];
|
||||||
public createdAt: number;
|
public createdAt: number;
|
||||||
public updatedAt: number;
|
public updatedAt: number;
|
||||||
public effectiveListenPorts?: number[];
|
public effectiveListenPorts?: number[];
|
||||||
|
public effectiveListenPortsUdp?: number[];
|
||||||
public manualPorts?: number[];
|
public manualPorts?: number[];
|
||||||
public derivedPorts?: number[];
|
public derivedPorts?: number[];
|
||||||
|
|
||||||
@@ -24,12 +26,14 @@ export class RemoteIngress {
|
|||||||
this.name = data.name;
|
this.name = data.name;
|
||||||
this.secret = data.secret;
|
this.secret = data.secret;
|
||||||
this.listenPorts = data.listenPorts;
|
this.listenPorts = data.listenPorts;
|
||||||
|
this.listenPortsUdp = data.listenPortsUdp;
|
||||||
this.enabled = data.enabled;
|
this.enabled = data.enabled;
|
||||||
this.autoDerivePorts = data.autoDerivePorts;
|
this.autoDerivePorts = data.autoDerivePorts;
|
||||||
this.tags = data.tags;
|
this.tags = data.tags;
|
||||||
this.createdAt = data.createdAt;
|
this.createdAt = data.createdAt;
|
||||||
this.updatedAt = data.updatedAt;
|
this.updatedAt = data.updatedAt;
|
||||||
this.effectiveListenPorts = data.effectiveListenPorts;
|
this.effectiveListenPorts = data.effectiveListenPorts;
|
||||||
|
this.effectiveListenPortsUdp = data.effectiveListenPortsUdp;
|
||||||
this.manualPorts = data.manualPorts;
|
this.manualPorts = data.manualPorts;
|
||||||
this.derivedPorts = data.derivedPorts;
|
this.derivedPorts = data.derivedPorts;
|
||||||
}
|
}
|
||||||
@@ -52,11 +56,13 @@ export class RemoteIngress {
|
|||||||
const edge = response.edge;
|
const edge = response.edge;
|
||||||
this.name = edge.name;
|
this.name = edge.name;
|
||||||
this.listenPorts = edge.listenPorts;
|
this.listenPorts = edge.listenPorts;
|
||||||
|
this.listenPortsUdp = edge.listenPortsUdp;
|
||||||
this.enabled = edge.enabled;
|
this.enabled = edge.enabled;
|
||||||
this.autoDerivePorts = edge.autoDerivePorts;
|
this.autoDerivePorts = edge.autoDerivePorts;
|
||||||
this.tags = edge.tags;
|
this.tags = edge.tags;
|
||||||
this.updatedAt = edge.updatedAt;
|
this.updatedAt = edge.updatedAt;
|
||||||
this.effectiveListenPorts = edge.effectiveListenPorts;
|
this.effectiveListenPorts = edge.effectiveListenPorts;
|
||||||
|
this.effectiveListenPortsUdp = edge.effectiveListenPortsUdp;
|
||||||
this.manualPorts = edge.manualPorts;
|
this.manualPorts = edge.manualPorts;
|
||||||
this.derivedPorts = edge.derivedPorts;
|
this.derivedPorts = edge.derivedPorts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,64 @@ export interface IRemoteIngressStatus {
|
|||||||
activeTunnels: number;
|
activeTunnels: number;
|
||||||
lastHeartbeat: number | null;
|
lastHeartbeat: number | null;
|
||||||
connectedAt: number | null;
|
connectedAt: number | null;
|
||||||
|
transportMode?: 'tcpTls' | 'quic' | 'quicWithFallback';
|
||||||
|
fallbackUsed?: boolean;
|
||||||
|
performance?: IRemoteIngressPerformanceEffective;
|
||||||
|
flowControl?: IRemoteIngressFlowControlStatus;
|
||||||
|
queues?: IRemoteIngressQueueStatus;
|
||||||
|
traffic?: IRemoteIngressTrafficStatus;
|
||||||
|
udp?: IRemoteIngressUdpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TRemoteIngressPerformanceProfile = 'balanced' | 'throughput' | 'highConcurrency';
|
||||||
|
|
||||||
|
export interface IRemoteIngressPerformanceConfig {
|
||||||
|
profile?: TRemoteIngressPerformanceProfile;
|
||||||
|
maxStreamsPerEdge?: number;
|
||||||
|
totalWindowBudgetBytes?: number;
|
||||||
|
minStreamWindowBytes?: number;
|
||||||
|
maxStreamWindowBytes?: number;
|
||||||
|
sustainedStreamWindowBytes?: number;
|
||||||
|
quicDatagramReceiveBufferBytes?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteIngressPerformanceEffective {
|
||||||
|
profile: TRemoteIngressPerformanceProfile;
|
||||||
|
maxStreamsPerEdge: number;
|
||||||
|
totalWindowBudgetBytes: number;
|
||||||
|
minStreamWindowBytes: number;
|
||||||
|
maxStreamWindowBytes: number;
|
||||||
|
sustainedStreamWindowBytes: number;
|
||||||
|
quicDatagramReceiveBufferBytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteIngressFlowControlStatus {
|
||||||
|
applies: boolean;
|
||||||
|
currentWindowBytes: number;
|
||||||
|
minWindowBytes: number;
|
||||||
|
maxWindowBytes: number;
|
||||||
|
totalWindowBudgetBytes: number;
|
||||||
|
estimatedInFlightBytes: number;
|
||||||
|
stalledStreams: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteIngressQueueStatus {
|
||||||
|
ctrlQueueDepth: number;
|
||||||
|
dataQueueDepth: number;
|
||||||
|
sustainedQueueDepth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteIngressTrafficStatus {
|
||||||
|
bytesIn: number;
|
||||||
|
bytesOut: number;
|
||||||
|
streamsOpenedTotal: number;
|
||||||
|
streamsClosedTotal: number;
|
||||||
|
rejectedStreams: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRemoteIngressUdpStatus {
|
||||||
|
activeSessions: number;
|
||||||
|
droppedDatagrams: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export interface IConfigData {
|
|||||||
hubDomain: string | null;
|
hubDomain: string | null;
|
||||||
tlsMode: 'custom' | 'acme' | 'self-signed';
|
tlsMode: 'custom' | 'acme' | 'self-signed';
|
||||||
connectedEdgeIps: string[];
|
connectedEdgeIps: string[];
|
||||||
|
performance?: import('../data/remoteingress.js').IRemoteIngressPerformanceConfig;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '13.21.1',
|
version: '13.22.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|||||||
color: ${cssManager.bdTheme('#047857', '#34d399')};
|
color: ${cssManager.bdTheme('#047857', '#34d399')};
|
||||||
border: 1px dashed ${cssManager.bdTheme('#6ee7b7', '#065f46')};
|
border: 1px dashed ${cssManager.bdTheme('#6ee7b7', '#065f46')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metricStack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metricMuted {
|
||||||
|
color: var(--text-muted, #6b7280);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -226,9 +238,13 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|||||||
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
|
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
|
||||||
name: edge.name,
|
name: edge.name,
|
||||||
status: this.getEdgeStatusHtml(edge),
|
status: this.getEdgeStatusHtml(edge),
|
||||||
|
transport: this.getTransportHtml(edge.id),
|
||||||
publicIp: this.getEdgePublicIp(edge.id),
|
publicIp: this.getEdgePublicIp(edge.id),
|
||||||
ports: this.getPortsHtml(edge),
|
ports: this.getPortsHtml(edge),
|
||||||
tunnels: this.getEdgeTunnelCount(edge.id),
|
tunnels: this.getEdgeTunnelCount(edge.id),
|
||||||
|
window: this.getWindowHtml(edge.id),
|
||||||
|
queues: this.getQueuesHtml(edge.id),
|
||||||
|
traffic: this.getTrafficHtml(edge.id),
|
||||||
lastHeartbeat: this.getLastHeartbeat(edge.id),
|
lastHeartbeat: this.getLastHeartbeat(edge.id),
|
||||||
})}
|
})}
|
||||||
.dataActions=${[
|
.dataActions=${[
|
||||||
@@ -459,6 +475,46 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|||||||
return status?.activeTunnels || 0;
|
return status?.activeTunnels || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTransportHtml(edgeId: string): TemplateResult | string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
if (!status?.connected) return '-';
|
||||||
|
const mode = status.transportMode || 'unknown';
|
||||||
|
const label = mode === 'quic' ? 'QUIC' : mode === 'tcpTls' ? 'TCP/TLS' : mode;
|
||||||
|
return html`<div class="metricStack"><strong>${label}</strong><span class="metricMuted">${status.fallbackUsed ? 'fallback' : status.performance?.profile || 'default'}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWindowHtml(edgeId: string): TemplateResult | string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
if (!status?.connected || !status.flowControl) return '-';
|
||||||
|
if (!status.flowControl.applies) {
|
||||||
|
return html`<div class="metricStack"><span>native QUIC</span><span class="metricMuted">max ${status.performance?.maxStreamsPerEdge || '-'} streams</span></div>`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div class="metricStack">
|
||||||
|
<span>${this.formatBytes(status.flowControl.currentWindowBytes)} window</span>
|
||||||
|
<span class="metricMuted">${this.formatBytes(status.flowControl.estimatedInFlightBytes)} est. in-flight</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getQueuesHtml(edgeId: string): TemplateResult | string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
if (!status?.connected || !status.queues) return '-';
|
||||||
|
return html`<div class="metricStack"><span>C ${status.queues.ctrlQueueDepth} / D ${status.queues.dataQueueDepth}</span><span class="metricMuted">S ${status.queues.sustainedQueueDepth}</span></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTrafficHtml(edgeId: string): TemplateResult | string {
|
||||||
|
const status = this.getEdgeStatus(edgeId);
|
||||||
|
if (!status?.connected || !status.traffic) return '-';
|
||||||
|
const drops = (status.traffic.rejectedStreams || 0) + (status.udp?.droppedDatagrams || 0);
|
||||||
|
return html`
|
||||||
|
<div class="metricStack">
|
||||||
|
<span>${this.formatBytes(status.traffic.bytesIn)} in / ${this.formatBytes(status.traffic.bytesOut)} out</span>
|
||||||
|
<span class="metricMuted">${drops} rejected/dropped</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
private getLastHeartbeat(edgeId: string): string {
|
private getLastHeartbeat(edgeId: string): string {
|
||||||
const status = this.getEdgeStatus(edgeId);
|
const status = this.getEdgeStatus(edgeId);
|
||||||
if (!status?.lastHeartbeat) return '-';
|
if (!status?.lastHeartbeat) return '-';
|
||||||
@@ -467,4 +523,16 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|||||||
if (ago < 3600000) return `${Math.floor(ago / 60000)}m ago`;
|
if (ago < 3600000) return `${Math.floor(ago / 60000)}m ago`;
|
||||||
return `${Math.floor(ago / 3600000)}h ago`;
|
return `${Math.floor(ago / 3600000)}h ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private formatBytes(bytes: number): string {
|
||||||
|
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
let value = bytes;
|
||||||
|
let unitIndex = 0;
|
||||||
|
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
value = value / 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
return `${value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unitIndex]}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user