@serve.zone/dcrouter 13.38.4 → 13.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +350 -343
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/db/documents/classes.remote-ingress-edge.doc.d.ts +2 -0
  5. package/dist_ts/db/documents/classes.remote-ingress-edge.doc.js +8 -2
  6. package/dist_ts/http3/http3-route-augmentation.d.ts +1 -1
  7. package/dist_ts/http3/http3-route-augmentation.js +3 -17
  8. package/dist_ts/monitoring/classes.metricsmanager.d.ts +2 -0
  9. package/dist_ts/monitoring/classes.metricsmanager.js +53 -19
  10. package/dist_ts/opsserver/handlers/remoteingress.handler.js +3 -2
  11. package/dist_ts/opsserver/handlers/security.handler.d.ts +2 -0
  12. package/dist_ts/opsserver/handlers/security.handler.js +50 -73
  13. package/dist_ts/radius/classes.radius.server.d.ts +0 -5
  14. package/dist_ts/radius/classes.radius.server.js +46 -78
  15. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -2
  16. package/dist_ts/remoteingress/classes.remoteingress-manager.js +9 -21
  17. package/dist_ts_interfaces/data/remoteingress.d.ts +10 -0
  18. package/dist_ts_interfaces/requests/remoteingress.d.ts +3 -1
  19. package/dist_ts_web/00_commitinfo_data.js +1 -1
  20. package/dist_ts_web/appstate.d.ts +2 -0
  21. package/dist_ts_web/appstate.js +3 -1
  22. package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -0
  23. package/dist_ts_web/elements/network/ops-view-remoteingress.js +55 -2
  24. package/dist_ts_web/elements/overview/ops-view-config.js +4 -1
  25. package/package.json +3 -3
  26. package/ts/00_commitinfo_data.ts +1 -1
  27. package/ts/db/documents/classes.remote-ingress-edge.doc.ts +4 -0
  28. package/ts/http3/http3-route-augmentation.ts +2 -18
  29. package/ts/monitoring/classes.metricsmanager.ts +59 -21
  30. package/ts/opsserver/handlers/remoteingress.handler.ts +2 -0
  31. package/ts/opsserver/handlers/security.handler.ts +57 -109
  32. package/ts/radius/classes.radius.server.ts +54 -80
  33. package/ts/remoteingress/classes.remoteingress-manager.ts +12 -21
  34. package/ts_web/00_commitinfo_data.ts +1 -1
  35. package/ts_web/appstate.ts +4 -0
  36. package/ts_web/elements/network/ops-view-remoteingress.ts +57 -1
  37. package/ts_web/elements/overview/ops-view-config.ts +10 -0
@@ -1,7 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
- import { MetricsManager } from '../../monitoring/index.js';
5
4
  import { requireOpsAuth } from '../helpers/auth.js';
6
5
 
7
6
  export class SecurityHandler {
@@ -46,18 +45,7 @@ export class SecurityHandler {
46
45
  'getActiveConnections',
47
46
  async (dataArg, toolsArg) => {
48
47
  await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
49
- const connections = await this.getActiveConnections(dataArg.protocol, dataArg.state);
50
- const connectionInfos: interfaces.data.IConnectionInfo[] = connections.map(conn => ({
51
- id: conn.id,
52
- remoteAddress: conn.source.ip,
53
- localAddress: conn.destination.ip,
54
- startTime: conn.startTime,
55
- protocol: conn.type === 'http' ? 'https' : conn.type as any,
56
- state: conn.status === 'active' ? 'connected' : conn.status as any,
57
- bytesReceived: (conn as any)._throughputIn || 0,
58
- bytesSent: (conn as any)._throughputOut || 0,
59
- connectionCount: conn.bytesTransferred || 1,
60
- }));
48
+ const connectionInfos = await this.getActiveConnections(dataArg.protocol, dataArg.state);
61
49
  const totalConnections = connectionInfos.reduce((sum, conn) => sum + (conn.connectionCount || 1), 0);
62
50
 
63
51
  const summary = {
@@ -362,106 +350,66 @@ export class SecurityHandler {
362
350
  private async getActiveConnections(
363
351
  protocol?: 'http' | 'https' | 'smtp' | 'smtps',
364
352
  state?: string
365
- ): Promise<Array<{
366
- id: string;
367
- type: 'http' | 'smtp' | 'dns';
368
- source: {
369
- ip: string;
370
- port: number;
371
- country?: string;
372
- };
373
- destination: {
374
- ip: string;
375
- port: number;
376
- service?: string;
377
- };
378
- startTime: number;
379
- bytesTransferred: number;
380
- status: 'active' | 'idle' | 'closing';
381
- }>> {
382
- const connections: Array<{
383
- id: string;
384
- type: 'http' | 'smtp' | 'dns';
385
- source: {
386
- ip: string;
387
- port: number;
388
- country?: string;
389
- };
390
- destination: {
391
- ip: string;
392
- port: number;
393
- service?: string;
394
- };
395
- startTime: number;
396
- bytesTransferred: number;
397
- status: 'active' | 'idle' | 'closing';
398
- }> = [];
399
-
400
- // Get connection info and network stats from MetricsManager if available
401
- if (this.opsServerRef.dcRouterRef.metricsManager) {
402
- const connectionInfo = await this.opsServerRef.dcRouterRef.metricsManager.getConnectionInfo();
403
- const networkStats = await this.opsServerRef.dcRouterRef.metricsManager.getNetworkStats();
404
-
405
- // One aggregate row per IP with real throughput data
406
- if (networkStats.connectionsByIP && networkStats.connectionsByIP.size > 0) {
407
- let connIndex = 0;
408
- const publicIp = this.opsServerRef.dcRouterRef.options.publicIp || 'server';
353
+ ): Promise<interfaces.data.IConnectionInfo[]> {
354
+ const metricsManager = this.opsServerRef.dcRouterRef.metricsManager;
355
+ if (!metricsManager) {
356
+ return [];
357
+ }
409
358
 
410
- for (const [ip, count] of networkStats.connectionsByIP) {
411
- const tp = networkStats.throughputByIP?.get(ip);
412
- connections.push({
413
- id: `ip-${connIndex++}`,
414
- type: 'http',
415
- source: {
416
- ip: ip,
417
- port: 0,
418
- },
419
- destination: {
420
- ip: publicIp,
421
- port: 443,
422
- service: 'proxy',
423
- },
424
- startTime: 0,
425
- bytesTransferred: count, // Store connection count here
426
- status: 'active',
427
- // Attach real throughput for the handler mapping
428
- ...(tp ? { _throughputIn: tp.in, _throughputOut: tp.out } : {}),
429
- } as any);
430
- }
431
- } else if (connectionInfo.length > 0) {
432
- // Fallback to route-based connection info if no IP data available
433
- connectionInfo.forEach((info, index) => {
434
- connections.push({
435
- id: `conn-${index}`,
436
- type: 'http',
437
- source: {
438
- ip: 'unknown',
439
- port: 0,
440
- },
441
- destination: {
442
- ip: this.opsServerRef.dcRouterRef.options.publicIp || 'server',
443
- port: 443,
444
- service: info.source,
445
- },
446
- startTime: info.lastActivity.getTime(),
447
- bytesTransferred: 0,
448
- status: 'active',
449
- });
450
- });
359
+ const snapshots = await metricsManager.getActiveConnectionSnapshots({ limit: 10000 });
360
+ const connections = snapshots.map((snapshot): interfaces.data.IConnectionInfo => ({
361
+ id: String(snapshot.id),
362
+ remoteAddress: snapshot.sourcePort === null
363
+ ? snapshot.sourceIp
364
+ : `${snapshot.sourceIp}:${snapshot.sourcePort}`,
365
+ localAddress: snapshot.targetHost
366
+ ? `${snapshot.targetHost}:${snapshot.targetPort ?? snapshot.localPort}`
367
+ : `${this.opsServerRef.dcRouterRef.options.publicIp || 'server'}:${snapshot.localPort}`,
368
+ startTime: snapshot.startedAtMs,
369
+ protocol: this.mapSnapshotProtocol(snapshot),
370
+ state: this.mapSnapshotState(snapshot.state),
371
+ bytesReceived: snapshot.bytesIn,
372
+ bytesSent: snapshot.bytesOut,
373
+ }));
374
+
375
+ return connections.filter((connection) => {
376
+ if (protocol && connection.protocol !== protocol) {
377
+ return false;
378
+ }
379
+ if (state && connection.state !== state) {
380
+ return false;
451
381
  }
382
+ return true;
383
+ });
384
+ }
385
+
386
+ private mapSnapshotProtocol(
387
+ snapshot: plugins.smartproxy.IActiveConnectionSnapshot,
388
+ ): interfaces.data.IConnectionInfo['protocol'] {
389
+ if (snapshot.localPort === 465) {
390
+ return 'smtps';
452
391
  }
453
-
454
- // Filter by protocol if specified
455
- if (protocol) {
456
- return connections.filter(conn => {
457
- if (protocol === 'https' || protocol === 'http') {
458
- return conn.type === 'http';
459
- }
460
- return conn.type === protocol.replace('s', ''); // smtp/smtps -> smtp
461
- });
392
+ if ([25, 587, 2525].includes(snapshot.localPort)) {
393
+ return 'smtp';
462
394
  }
463
-
464
- return connections;
395
+
396
+ switch (snapshot.protocol) {
397
+ case 'http':
398
+ return 'http';
399
+ case 'https':
400
+ case 'tls':
401
+ case 'tls-passthrough':
402
+ case 'tls-reencrypt':
403
+ case 'tls-socket-handler':
404
+ case 'quic':
405
+ return 'https';
406
+ default:
407
+ return snapshot.localPort === 80 ? 'http' : 'https';
408
+ }
409
+ }
410
+
411
+ private mapSnapshotState(state: string): interfaces.data.IConnectionInfo['state'] {
412
+ return state === 'closing' ? 'closing' : 'connected';
465
413
  }
466
414
 
467
415
  private async getRateLimitStatus(
@@ -91,7 +91,6 @@ export class RadiusServer {
91
91
  private vlanManager: VlanManager;
92
92
  private accountingManager: AccountingManager;
93
93
  private config: IRadiusServerConfig;
94
- private clientSecrets: Map<string, string> = new Map();
95
94
  private running: boolean = false;
96
95
 
97
96
  // Statistics
@@ -138,24 +137,18 @@ export class RadiusServer {
138
137
  await this.vlanManager.importMappings(this.config.vlanAssignment.mappings);
139
138
  }
140
139
 
141
- // Build client secrets map
142
- this.buildClientSecretsMap();
140
+ const cidrSecrets = this.buildClientSecretsMap();
143
141
 
144
142
  // Create the RADIUS server
145
143
  this.radiusServer = new plugins.smartradius.RadiusServer({
146
144
  authPort: this.config.authPort,
147
145
  acctPort: this.config.acctPort,
148
146
  bindAddress: this.config.bindAddress,
149
- defaultSecret: this.getDefaultSecret(),
147
+ cidrSecrets,
150
148
  authenticationHandler: this.handleAuthentication.bind(this),
151
149
  accountingHandler: this.handleAccounting.bind(this),
152
150
  });
153
151
 
154
- // Configure per-client secrets
155
- for (const [ip, secret] of this.clientSecrets) {
156
- this.radiusServer.setClientSecret(ip, secret);
157
- }
158
-
159
152
  // Start the server
160
153
  await this.radiusServer.start();
161
154
 
@@ -189,19 +182,22 @@ export class RadiusServer {
189
182
  /**
190
183
  * Handle authentication request
191
184
  */
192
- private async handleAuthentication(request: any): Promise<any> {
185
+ private async handleAuthentication(
186
+ request: plugins.smartradius.IAuthenticationRequest,
187
+ ): Promise<plugins.smartradius.IAuthenticationResponse> {
193
188
  this.stats.authRequests++;
194
189
 
195
190
  const authData: IAuthRequestData = {
196
- username: request.attributes?.UserName || '',
197
- password: request.attributes?.UserPassword,
198
- nasIpAddress: request.attributes?.NasIpAddress || request.source?.address || '',
199
- nasPort: request.attributes?.NasPort,
200
- nasPortType: request.attributes?.NasPortType,
201
- nasIdentifier: request.attributes?.NasIdentifier,
202
- calledStationId: request.attributes?.CalledStationId,
203
- callingStationId: request.attributes?.CallingStationId,
204
- serviceType: request.attributes?.ServiceType,
191
+ username: request.username || '',
192
+ password: request.password,
193
+ nasIpAddress: request.nasIpAddress || request.clientAddress || '',
194
+ nasPort: request.nasPort,
195
+ nasPortType: request.nasPortType !== undefined ? String(request.nasPortType) : undefined,
196
+ nasIdentifier: request.nasIdentifier,
197
+ calledStationId: request.calledStationId,
198
+ callingStationId: request.callingStationId,
199
+ serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
200
+ framedMtu: request.framedMtu,
205
201
  };
206
202
 
207
203
  logger.log('debug', `RADIUS Auth Request: user=${authData.username}, NAS=${authData.nasIpAddress}`);
@@ -215,15 +211,15 @@ export class RadiusServer {
215
211
  logger.log('info', `RADIUS Auth Accept: user=${authData.username}, VLAN=${result.vlanId}`);
216
212
 
217
213
  // Build response with VLAN attributes
218
- const response: any = {
214
+ const response: plugins.smartradius.IAuthenticationResponse = {
219
215
  code: plugins.smartradius.ERadiusCode.AccessAccept,
220
216
  replyMessage: result.replyMessage,
221
217
  };
222
218
 
223
219
  // Add VLAN attributes if assigned
224
220
  if (result.vlanId !== undefined) {
225
- response.tunnelType = 13; // VLAN
226
- response.tunnelMediumType = 6; // IEEE 802
221
+ response.tunnelType = plugins.smartradius.ETunnelType.Vlan;
222
+ response.tunnelMediumType = plugins.smartradius.ETunnelMediumType.Ieee802;
227
223
  response.tunnelPrivateGroupId = String(result.vlanId);
228
224
  }
229
225
 
@@ -257,34 +253,37 @@ export class RadiusServer {
257
253
  /**
258
254
  * Handle accounting request
259
255
  */
260
- private async handleAccounting(request: any): Promise<any> {
256
+ private async handleAccounting(
257
+ request: plugins.smartradius.IAccountingRequest,
258
+ ): Promise<plugins.smartradius.IAccountingResponse> {
261
259
  this.stats.accountingRequests++;
262
260
 
263
261
  if (!this.config.accounting?.enabled) {
264
262
  // Still respond even if not tracking
265
- return { code: plugins.smartradius.ERadiusCode.AccountingResponse };
263
+ return { success: true };
266
264
  }
267
265
 
268
- const statusType = request.attributes?.AcctStatusType;
269
- const sessionId = request.attributes?.AcctSessionId || '';
266
+ const statusType = request.statusType;
267
+ const sessionId = request.sessionId || '';
270
268
 
271
269
  const accountingData = {
272
270
  sessionId,
273
- username: request.attributes?.UserName || '',
274
- macAddress: request.attributes?.CallingStationId,
275
- nasIpAddress: request.attributes?.NasIpAddress || request.source?.address || '',
276
- nasPort: request.attributes?.NasPort,
277
- nasPortType: request.attributes?.NasPortType,
278
- nasIdentifier: request.attributes?.NasIdentifier,
279
- calledStationId: request.attributes?.CalledStationId,
280
- callingStationId: request.attributes?.CallingStationId,
281
- inputOctets: request.attributes?.AcctInputOctets,
282
- outputOctets: request.attributes?.AcctOutputOctets,
283
- inputPackets: request.attributes?.AcctInputPackets,
284
- outputPackets: request.attributes?.AcctOutputPackets,
285
- sessionTime: request.attributes?.AcctSessionTime,
286
- terminateCause: request.attributes?.AcctTerminateCause,
287
- serviceType: request.attributes?.ServiceType,
271
+ username: request.username || '',
272
+ macAddress: request.callingStationId,
273
+ nasIpAddress: request.nasIpAddress || request.clientAddress || '',
274
+ nasPort: request.nasPort,
275
+ nasPortType: request.nasPortType !== undefined ? String(request.nasPortType) : undefined,
276
+ nasIdentifier: request.nasIdentifier,
277
+ calledStationId: request.calledStationId,
278
+ callingStationId: request.callingStationId,
279
+ inputOctets: request.inputOctets,
280
+ outputOctets: request.outputOctets,
281
+ inputPackets: request.inputPackets,
282
+ outputPackets: request.outputPackets,
283
+ sessionTime: request.sessionTime,
284
+ terminateCause: request.terminateCause !== undefined ? String(request.terminateCause) : undefined,
285
+ framedIpAddress: request.framedIpAddress,
286
+ serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
288
287
  };
289
288
 
290
289
  try {
@@ -311,7 +310,7 @@ export class RadiusServer {
311
310
  logger.log('error', `RADIUS accounting error: ${(error as Error).message}`);
312
311
  }
313
312
 
314
- return { code: plugins.smartradius.ERadiusCode.AccountingResponse };
313
+ return { success: true };
315
314
  }
316
315
 
317
316
  /**
@@ -391,37 +390,18 @@ export class RadiusServer {
391
390
  /**
392
391
  * Build client secrets map from configuration
393
392
  */
394
- private buildClientSecretsMap(): void {
395
- this.clientSecrets.clear();
393
+ private buildClientSecretsMap(): Record<string, string> {
394
+ const cidrSecrets: Record<string, string> = {};
396
395
 
397
396
  for (const client of this.config.clients) {
398
397
  if (!client.enabled) {
399
398
  continue;
400
399
  }
401
400
 
402
- // Handle CIDR ranges
403
- if (client.ipRange.includes('/')) {
404
- // For CIDR ranges, we'll use the network address as key
405
- // In practice, smartradius may handle this differently
406
- const [network] = client.ipRange.split('/');
407
- this.clientSecrets.set(network, client.secret);
408
- } else {
409
- this.clientSecrets.set(client.ipRange, client.secret);
410
- }
401
+ cidrSecrets[client.ipRange] = client.secret;
411
402
  }
412
- }
413
403
 
414
- /**
415
- * Get default secret for unknown clients
416
- */
417
- private getDefaultSecret(): string {
418
- // Use first enabled client's secret as default, or a random one
419
- for (const client of this.config.clients) {
420
- if (client.enabled) {
421
- return client.secret;
422
- }
423
- }
424
- return plugins.crypto.randomBytes(16).toString('hex');
404
+ return cidrSecrets;
425
405
  }
426
406
 
427
407
  /**
@@ -430,21 +410,19 @@ export class RadiusServer {
430
410
  async addClient(client: IRadiusClient): Promise<void> {
431
411
  // Check if client already exists
432
412
  const existingIndex = this.config.clients.findIndex(c => c.name === client.name);
413
+ const previousClient = existingIndex >= 0 ? this.config.clients[existingIndex] : undefined;
433
414
  if (existingIndex >= 0) {
434
415
  this.config.clients[existingIndex] = client;
435
416
  } else {
436
417
  this.config.clients.push(client);
437
418
  }
438
419
 
439
- // Update client secrets if running
440
- if (this.running && this.radiusServer && client.enabled) {
441
- if (client.ipRange.includes('/')) {
442
- const [network] = client.ipRange.split('/');
443
- this.radiusServer.setClientSecret(network, client.secret);
444
- this.clientSecrets.set(network, client.secret);
445
- } else {
446
- this.radiusServer.setClientSecret(client.ipRange, client.secret);
447
- this.clientSecrets.set(client.ipRange, client.secret);
420
+ if (this.running && this.radiusServer) {
421
+ if (previousClient) {
422
+ this.radiusServer.removeNetworkSecret(previousClient.ipRange);
423
+ }
424
+ if (client.enabled) {
425
+ this.radiusServer.setNetworkSecret(client.ipRange, client.secret);
448
426
  }
449
427
  }
450
428
 
@@ -460,12 +438,8 @@ export class RadiusServer {
460
438
  const client = this.config.clients[index];
461
439
  this.config.clients.splice(index, 1);
462
440
 
463
- // Remove from secrets map
464
- if (client.ipRange.includes('/')) {
465
- const [network] = client.ipRange.split('/');
466
- this.clientSecrets.delete(network);
467
- } else {
468
- this.clientSecrets.delete(client.ipRange);
441
+ if (this.radiusServer) {
442
+ this.radiusServer.removeNetworkSecret(client.ipRange);
469
443
  }
470
444
 
471
445
  logger.log('info', `RADIUS client removed: ${name}`);
@@ -1,29 +1,13 @@
1
1
  import * as plugins from '../plugins.js';
2
- import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
2
+ import type { IRemoteIngress, IRemoteIngressPerformanceConfig, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
3
3
  import { RemoteIngressEdgeDoc } from '../db/index.js';
4
4
 
5
5
  interface IRemoteIngressFirewallConfig {
6
6
  blockedIps?: string[];
7
7
  }
8
8
 
9
- /**
10
- * Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
11
- */
12
- function extractPorts(portRange: number | Array<number | { from: number; to: number }>): number[] {
13
- const ports = new Set<number>();
14
- if (typeof portRange === 'number') {
15
- ports.add(portRange);
16
- } else if (Array.isArray(portRange)) {
17
- for (const entry of portRange) {
18
- if (typeof entry === 'number') {
19
- ports.add(entry);
20
- } else if (typeof entry === 'object' && 'from' in entry && 'to' in entry) {
21
- for (let p = entry.from; p <= entry.to; p++) {
22
- ports.add(p);
23
- }
24
- }
25
- }
26
- }
9
+ function extractPorts(portRange: plugins.smartproxy.IRouteConfig['match']['ports']): number[] {
10
+ const ports = new Set<number>(plugins.smartproxy.expandPortRange(portRange) as number[]);
27
11
  return [...ports].sort((a, b) => a - b);
28
12
  }
29
13
 
@@ -59,6 +43,7 @@ export class RemoteIngressManager {
59
43
  listenPortsUdp: doc.listenPortsUdp,
60
44
  enabled: doc.enabled,
61
45
  autoDerivePorts: doc.autoDerivePorts,
46
+ performance: doc.performance,
62
47
  tags: doc.tags,
63
48
  createdAt: doc.createdAt,
64
49
  updatedAt: doc.updatedAt,
@@ -189,6 +174,7 @@ export class RemoteIngressManager {
189
174
  listenPorts: number[] = [],
190
175
  tags?: string[],
191
176
  autoDerivePorts: boolean = true,
177
+ performance?: IRemoteIngressPerformanceConfig,
192
178
  ): Promise<IRemoteIngress> {
193
179
  const id = plugins.uuid.v4();
194
180
  const secret = plugins.crypto.randomBytes(32).toString('hex');
@@ -201,6 +187,7 @@ export class RemoteIngressManager {
201
187
  listenPorts,
202
188
  enabled: true,
203
189
  autoDerivePorts,
190
+ performance,
204
191
  tags: tags || [],
205
192
  createdAt: now,
206
193
  updatedAt: now,
@@ -237,6 +224,7 @@ export class RemoteIngressManager {
237
224
  listenPorts?: number[];
238
225
  autoDerivePorts?: boolean;
239
226
  enabled?: boolean;
227
+ performance?: IRemoteIngressPerformanceConfig;
240
228
  tags?: string[];
241
229
  },
242
230
  ): Promise<IRemoteIngress | null> {
@@ -249,6 +237,7 @@ export class RemoteIngressManager {
249
237
  if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
250
238
  if (updates.autoDerivePorts !== undefined) edge.autoDerivePorts = updates.autoDerivePorts;
251
239
  if (updates.enabled !== undefined) edge.enabled = updates.enabled;
240
+ if (updates.performance !== undefined) edge.performance = updates.performance;
252
241
  if (updates.tags !== undefined) edge.tags = updates.tags;
253
242
  edge.updatedAt = Date.now();
254
243
 
@@ -317,17 +306,19 @@ export class RemoteIngressManager {
317
306
  * Get the list of allowed edges (enabled only) for the Rust hub.
318
307
  * Includes listenPortsUdp when routes with transport 'udp' or 'all' are present.
319
308
  */
320
- public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig }> {
321
- const result: Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig }> = [];
309
+ public getAllowedEdges(): Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig; performance?: IRemoteIngressPerformanceConfig }> {
310
+ const result: Array<{ id: string; secret: string; listenPorts: number[]; listenPortsUdp?: number[]; firewallConfig?: IRemoteIngressFirewallConfig; performance?: IRemoteIngressPerformanceConfig }> = [];
322
311
  for (const edge of this.edges.values()) {
323
312
  if (edge.enabled) {
324
313
  const listenPortsUdp = this.getEffectiveListenPortsUdp(edge);
314
+ const performance = edge.performance && Object.keys(edge.performance).length > 0 ? edge.performance : undefined;
325
315
  result.push({
326
316
  id: edge.id,
327
317
  secret: edge.secret,
328
318
  listenPorts: this.getEffectiveListenPorts(edge),
329
319
  ...(listenPortsUdp.length > 0 ? { listenPortsUdp } : {}),
330
320
  ...(this.firewallConfig ? { firewallConfig: this.firewallConfig } : {}),
321
+ ...(performance ? { performance } : {}),
331
322
  });
332
323
  }
333
324
  }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.38.4',
6
+ version: '13.40.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -1120,6 +1120,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
1120
1120
  name: string;
1121
1121
  listenPorts?: number[];
1122
1122
  autoDerivePorts?: boolean;
1123
+ performance?: interfaces.data.IRemoteIngressPerformanceConfig;
1123
1124
  tags?: string[];
1124
1125
  }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1125
1126
  const context = getActionContext();
@@ -1135,6 +1136,7 @@ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
1135
1136
  name: dataArg.name,
1136
1137
  listenPorts: dataArg.listenPorts,
1137
1138
  autoDerivePorts: dataArg.autoDerivePorts,
1139
+ performance: dataArg.performance,
1138
1140
  tags: dataArg.tags,
1139
1141
  });
1140
1142
 
@@ -1187,6 +1189,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
1187
1189
  name?: string;
1188
1190
  listenPorts?: number[];
1189
1191
  autoDerivePorts?: boolean;
1192
+ performance?: interfaces.data.IRemoteIngressPerformanceConfig;
1190
1193
  tags?: string[];
1191
1194
  }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1192
1195
  const context = getActionContext();
@@ -1203,6 +1206,7 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
1203
1206
  name: dataArg.name,
1204
1207
  listenPorts: dataArg.listenPorts,
1205
1208
  autoDerivePorts: dataArg.autoDerivePorts,
1209
+ performance: dataArg.performance,
1206
1210
  tags: dataArg.tags,
1207
1211
  });
1208
1212