@serve.zone/dcrouter 13.38.4 → 13.39.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 (31) 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/opsserver/handlers/remoteingress.handler.js +3 -2
  9. package/dist_ts/radius/classes.radius.server.d.ts +4 -4
  10. package/dist_ts/radius/classes.radius.server.js +80 -66
  11. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -2
  12. package/dist_ts/remoteingress/classes.remoteingress-manager.js +9 -21
  13. package/dist_ts_interfaces/data/remoteingress.d.ts +10 -0
  14. package/dist_ts_interfaces/requests/remoteingress.d.ts +3 -1
  15. package/dist_ts_web/00_commitinfo_data.js +1 -1
  16. package/dist_ts_web/appstate.d.ts +2 -0
  17. package/dist_ts_web/appstate.js +3 -1
  18. package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -0
  19. package/dist_ts_web/elements/network/ops-view-remoteingress.js +55 -2
  20. package/dist_ts_web/elements/overview/ops-view-config.js +4 -1
  21. package/package.json +3 -3
  22. package/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/db/documents/classes.remote-ingress-edge.doc.ts +4 -0
  24. package/ts/http3/http3-route-augmentation.ts +2 -18
  25. package/ts/opsserver/handlers/remoteingress.handler.ts +2 -0
  26. package/ts/radius/classes.radius.server.ts +90 -66
  27. package/ts/remoteingress/classes.remoteingress-manager.ts +12 -21
  28. package/ts_web/00_commitinfo_data.ts +1 -1
  29. package/ts_web/appstate.ts +4 -0
  30. package/ts_web/elements/network/ops-view-remoteingress.ts +57 -1
  31. package/ts_web/elements/overview/ops-view-config.ts +10 -0
@@ -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.39.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
 
@@ -242,6 +242,7 @@ export class OpsViewRemoteIngress extends DeesElement {
242
242
  publicIp: this.getEdgePublicIp(edge.id),
243
243
  ports: this.getPortsHtml(edge),
244
244
  tunnels: this.getEdgeTunnelCount(edge.id),
245
+ maxConnections: this.getMaxConnectionsHtml(edge),
245
246
  window: this.getWindowHtml(edge.id),
246
247
  queues: this.getQueuesHtml(edge.id),
247
248
  traffic: this.getTrafficHtml(edge.id),
@@ -261,6 +262,7 @@ export class OpsViewRemoteIngress extends DeesElement {
261
262
  <dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
262
263
  <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports'} .description=${'Comma-separated port numbers, optional'}></dees-input-text>
263
264
  <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${true}></dees-input-checkbox>
265
+ <dees-input-text .key=${'maxStreamsPerEdge'} .label=${'Max Connections'} .description=${'Optional maximum concurrent client connections for this edge. Leave empty to use the hub default.'}></dees-input-text>
264
266
  <dees-input-text .key=${'tags'} .label=${'Tags'} .description=${'Comma-separated, optional'}></dees-input-text>
265
267
  </dees-form>
266
268
  `,
@@ -284,12 +286,20 @@ export class OpsViewRemoteIngress extends DeesElement {
284
286
  ? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
285
287
  : undefined;
286
288
  const autoDerivePorts = formData.autoDerivePorts !== false;
289
+ let performance: interfaces.data.IRemoteIngressPerformanceConfig | undefined;
290
+ try {
291
+ performance = this.collectPerformanceOverride(formData);
292
+ } catch (err: unknown) {
293
+ const { DeesToast } = await import('@design.estate/dees-catalog');
294
+ DeesToast.show({ message: (err as Error).message, type: 'error', duration: 4000 });
295
+ return;
296
+ }
287
297
  const tags = formData.tags
288
298
  ? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
289
299
  : undefined;
290
300
  await appstate.remoteIngressStatePart.dispatchAction(
291
301
  appstate.createRemoteIngressAction,
292
- { name, listenPorts, autoDerivePorts, tags },
302
+ { name, listenPorts, autoDerivePorts, performance, tags },
293
303
  );
294
304
  await modalArg.destroy();
295
305
  },
@@ -338,6 +348,7 @@ export class OpsViewRemoteIngress extends DeesElement {
338
348
  <dees-input-text .key=${'name'} .label=${'Name'} .value=${edge.name}></dees-input-text>
339
349
  <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports'} .description=${'Comma-separated port numbers'} .value=${(edge.listenPorts || []).join(', ')}></dees-input-text>
340
350
  <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${edge.autoDerivePorts !== false}></dees-input-checkbox>
351
+ <dees-input-text .key=${'maxStreamsPerEdge'} .label=${'Max Connections'} .description=${'Optional maximum concurrent client connections for this edge. Leave empty to use the hub default.'} .value=${edge.performance?.maxStreamsPerEdge?.toString() || ''}></dees-input-text>
341
352
  <dees-input-text .key=${'tags'} .label=${'Tags'} .description=${'Comma-separated'} .value=${(edge.tags || []).join(', ')}></dees-input-text>
342
353
  </dees-form>
343
354
  `,
@@ -359,6 +370,14 @@ export class OpsViewRemoteIngress extends DeesElement {
359
370
  ? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
360
371
  : [];
361
372
  const autoDerivePorts = formData.autoDerivePorts !== false;
373
+ let performance: interfaces.data.IRemoteIngressPerformanceConfig | undefined;
374
+ try {
375
+ performance = this.collectPerformanceOverride(formData, edge.performance);
376
+ } catch (err: unknown) {
377
+ const { DeesToast } = await import('@design.estate/dees-catalog');
378
+ DeesToast.show({ message: (err as Error).message, type: 'error', duration: 4000 });
379
+ return;
380
+ }
362
381
  const tags = formData.tags
363
382
  ? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
364
383
  : [];
@@ -369,6 +388,7 @@ export class OpsViewRemoteIngress extends DeesElement {
369
388
  name: formData.name || edge.name,
370
389
  listenPorts,
371
390
  autoDerivePorts,
391
+ performance,
372
392
  tags,
373
393
  },
374
394
  );
@@ -475,6 +495,19 @@ export class OpsViewRemoteIngress extends DeesElement {
475
495
  return status?.activeTunnels || 0;
476
496
  }
477
497
 
498
+ private getMaxConnectionsHtml(edge: interfaces.data.IRemoteIngress): TemplateResult | string {
499
+ const status = this.getEdgeStatus(edge.id);
500
+ const override = edge.performance?.maxStreamsPerEdge;
501
+ const effective = status?.performance?.maxStreamsPerEdge;
502
+ if (!override && !effective) return '-';
503
+ return html`
504
+ <div class="metricStack">
505
+ <span>${override || effective}</span>
506
+ <span class="metricMuted">${override ? 'edge override' : 'hub default'}</span>
507
+ </div>
508
+ `;
509
+ }
510
+
478
511
  private getTransportHtml(edgeId: string): TemplateResult | string {
479
512
  const status = this.getEdgeStatus(edgeId);
480
513
  if (!status?.connected) return '-';
@@ -535,4 +568,27 @@ export class OpsViewRemoteIngress extends DeesElement {
535
568
  }
536
569
  return `${value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[unitIndex]}`;
537
570
  }
571
+
572
+ private collectPerformanceOverride(
573
+ formData: Record<string, any>,
574
+ base?: interfaces.data.IRemoteIngressPerformanceConfig,
575
+ ): interfaces.data.IRemoteIngressPerformanceConfig | undefined {
576
+ const next: interfaces.data.IRemoteIngressPerformanceConfig = { ...(base || {}) };
577
+ const maxStreamsText = `${formData.maxStreamsPerEdge || ''}`.trim();
578
+ if (maxStreamsText) {
579
+ const maxStreamsPerEdge = Number.parseInt(maxStreamsText, 10);
580
+ if (!Number.isInteger(maxStreamsPerEdge) || maxStreamsPerEdge < 1) {
581
+ throw new Error('Max Connections must be a positive integer');
582
+ }
583
+ next.maxStreamsPerEdge = maxStreamsPerEdge;
584
+ } else {
585
+ delete next.maxStreamsPerEdge;
586
+ }
587
+
588
+ if (Object.keys(next).length > 0) {
589
+ return next;
590
+ }
591
+
592
+ return base ? {} : undefined;
593
+ }
538
594
  }
@@ -304,6 +304,16 @@ export class OpsViewConfig extends DeesElement {
304
304
  { key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
305
305
  ];
306
306
 
307
+ if (ri.performance) {
308
+ fields.push(
309
+ { key: 'Performance Profile', value: ri.performance.profile || null, type: 'badge' },
310
+ { key: 'Max Connections / Edge', value: ri.performance.maxStreamsPerEdge ?? null },
311
+ { key: 'Client Write Timeout', value: ri.performance.clientWriteTimeoutMs ? `${ri.performance.clientWriteTimeoutMs} ms` : null },
312
+ { key: 'First Data Timeout', value: ri.performance.firstDataConnectTimeoutMs ? `${ri.performance.firstDataConnectTimeoutMs} ms` : null },
313
+ { key: 'Server-first Ports', value: ri.performance.serverFirstPorts?.length ? ri.performance.serverFirstPorts.map(String) : null, type: 'pills' },
314
+ );
315
+ }
316
+
307
317
  const actions: IConfigSectionAction[] = [
308
318
  { label: 'View Remote Ingress', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'network', subview: 'remoteingress' } },
309
319
  ];