@serve.zone/dcrouter 13.19.0 → 13.19.1

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.
@@ -133,11 +133,11 @@ export class RouteConfigManager {
133
133
  }
134
134
 
135
135
  // Resolve references if metadata has refs and resolver is available
136
- let resolvedMetadata = metadata;
137
- if (metadata && this.referenceResolver) {
138
- const resolved = this.referenceResolver.resolveRoute(route, metadata);
136
+ let resolvedMetadata = this.normalizeRouteMetadata(metadata);
137
+ if (resolvedMetadata && this.referenceResolver) {
138
+ const resolved = this.referenceResolver.resolveRoute(route, resolvedMetadata);
139
139
  route = resolved.route;
140
- resolvedMetadata = resolved.metadata;
140
+ resolvedMetadata = this.normalizeRouteMetadata(resolved.metadata);
141
141
  }
142
142
 
143
143
  const stored: IRoute = {
@@ -198,14 +198,17 @@ export class RouteConfigManager {
198
198
  stored.enabled = patch.enabled;
199
199
  }
200
200
  if (patch.metadata !== undefined) {
201
- stored.metadata = { ...stored.metadata, ...patch.metadata };
201
+ stored.metadata = this.normalizeRouteMetadata({
202
+ ...stored.metadata,
203
+ ...patch.metadata,
204
+ });
202
205
  }
203
206
 
204
207
  // Re-resolve if metadata refs exist and resolver is available
205
208
  if (stored.metadata && this.referenceResolver) {
206
209
  const resolved = this.referenceResolver.resolveRoute(stored.route, stored.metadata);
207
210
  stored.route = resolved.route;
208
- stored.metadata = resolved.metadata;
211
+ stored.metadata = this.normalizeRouteMetadata(resolved.metadata);
209
212
  }
210
213
 
211
214
  stored.updatedAt = Date.now();
@@ -368,7 +371,7 @@ export class RouteConfigManager {
368
371
  createdBy: doc.createdBy,
369
372
  origin: doc.origin || 'api',
370
373
  systemKey: doc.systemKey,
371
- metadata: doc.metadata,
374
+ metadata: this.normalizeRouteMetadata(doc.metadata),
372
375
  };
373
376
 
374
377
  this.routes.set(doc.id, storedRoute);
@@ -404,6 +407,46 @@ export class RouteConfigManager {
404
407
  }
405
408
  }
406
409
 
410
+ private normalizeRouteMetadata(metadata?: Partial<IRouteMetadata>): IRouteMetadata | undefined {
411
+ if (!metadata) {
412
+ return undefined;
413
+ }
414
+
415
+ const normalizeString = (value?: string): string | undefined => {
416
+ if (typeof value !== 'string') {
417
+ return undefined;
418
+ }
419
+ const trimmed = value.trim();
420
+ return trimmed.length > 0 ? trimmed : undefined;
421
+ };
422
+
423
+ const normalized: IRouteMetadata = {
424
+ sourceProfileRef: normalizeString(metadata.sourceProfileRef),
425
+ networkTargetRef: normalizeString(metadata.networkTargetRef),
426
+ sourceProfileName: normalizeString(metadata.sourceProfileName),
427
+ networkTargetName: normalizeString(metadata.networkTargetName),
428
+ lastResolvedAt: typeof metadata.lastResolvedAt === 'number' && Number.isFinite(metadata.lastResolvedAt)
429
+ ? metadata.lastResolvedAt
430
+ : undefined,
431
+ };
432
+
433
+ if (!normalized.sourceProfileRef) {
434
+ normalized.sourceProfileName = undefined;
435
+ }
436
+ if (!normalized.networkTargetRef) {
437
+ normalized.networkTargetName = undefined;
438
+ }
439
+ if (!normalized.sourceProfileRef && !normalized.networkTargetRef) {
440
+ normalized.lastResolvedAt = undefined;
441
+ }
442
+
443
+ if (Object.values(normalized).every((value) => value === undefined)) {
444
+ return undefined;
445
+ }
446
+
447
+ return normalized;
448
+ }
449
+
407
450
  // =========================================================================
408
451
  // Private: warnings
409
452
  // =========================================================================
@@ -446,7 +489,7 @@ export class RouteConfigManager {
446
489
 
447
490
  const resolved = this.referenceResolver.resolveRoute(stored.route, stored.metadata);
448
491
  stored.route = resolved.route;
449
- stored.metadata = resolved.metadata;
492
+ stored.metadata = this.normalizeRouteMetadata(resolved.metadata);
450
493
  stored.updatedAt = Date.now();
451
494
  await this.persistRoute(stored);
452
495
  }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.19.0',
6
+ version: '13.19.1',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -15,16 +15,70 @@ import {
15
15
 
16
16
  // TLS dropdown options shared by create and edit dialogs
17
17
  const tlsModeOptions = [
18
- { key: 'none', option: '(none — no TLS)' },
19
- { key: 'passthrough', option: 'Passthrough' },
20
- { key: 'terminate', option: 'Terminate' },
21
- { key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt' },
18
+ { key: 'none', option: '(none — plain TCP/HTTP, use for SSH)' },
19
+ { key: 'passthrough', option: 'Passthrough (TLS only)' },
20
+ { key: 'terminate', option: 'Terminate TLS' },
21
+ { key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt TLS' },
22
22
  ];
23
23
  const tlsCertOptions = [
24
24
  { key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
25
25
  { key: 'custom', option: 'Custom certificate' },
26
26
  ];
27
27
 
28
+ function getDropdownKey(value: any): string {
29
+ return typeof value === 'string' ? value : value?.key || '';
30
+ }
31
+
32
+ function parseTargetPort(value: any): number | undefined {
33
+ const parsed = typeof value === 'number'
34
+ ? value
35
+ : typeof value === 'string'
36
+ ? parseInt(value.trim(), 10)
37
+ : Number.NaN;
38
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
39
+ return undefined;
40
+ }
41
+ return parsed;
42
+ }
43
+
44
+ function getRouteTargetInputs(formEl: any) {
45
+ const textInputs = Array.from(formEl.querySelectorAll('dees-input-text')) as any[];
46
+ return {
47
+ hostInput: textInputs.find((input) => input.key === 'targetHost'),
48
+ portInput: textInputs.find((input) => input.key === 'targetPort'),
49
+ };
50
+ }
51
+
52
+ function setupTargetInputState(formEl: any) {
53
+ const updateState = async () => {
54
+ const data = await formEl.collectFormData();
55
+ const usesNetworkTarget = !!getDropdownKey(data.networkTargetRef);
56
+ const { hostInput, portInput } = getRouteTargetInputs(formEl);
57
+ const hostDescription = usesNetworkTarget
58
+ ? 'Controlled by the selected network target'
59
+ : 'Used when no network target is selected';
60
+ const portDescription = usesNetworkTarget
61
+ ? 'Controlled by the selected network target'
62
+ : 'Used when no network target is selected';
63
+
64
+ if (hostInput) {
65
+ hostInput.disabled = usesNetworkTarget;
66
+ hostInput.required = !usesNetworkTarget;
67
+ hostInput.description = hostDescription;
68
+ }
69
+ if (portInput) {
70
+ portInput.disabled = usesNetworkTarget;
71
+ portInput.required = !usesNetworkTarget;
72
+ portInput.description = portDescription;
73
+ }
74
+
75
+ await formEl.updateRequiredStatus?.();
76
+ };
77
+
78
+ formEl.changeSubject.subscribe(() => updateState());
79
+ updateState();
80
+ }
81
+
28
82
  /**
29
83
  * Toggle TLS form field visibility based on selected TLS mode and certificate type.
30
84
  */
@@ -470,6 +524,16 @@ export class OpsViewRoutes extends DeesElement {
470
524
  : [];
471
525
  const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
472
526
 
527
+ const profileKey = getDropdownKey(formData.sourceProfileRef);
528
+ const targetKey = getDropdownKey(formData.networkTargetRef);
529
+ const targetPort = parseTargetPort(formData.targetPort)
530
+ ?? (targetKey ? parseTargetPort(currentTargetPort) ?? ports[0] : undefined);
531
+
532
+ if (targetPort === undefined) {
533
+ alert('Target Port must be a valid port number when no network target is selected.');
534
+ return;
535
+ }
536
+
473
537
  const updatedRoute: any = {
474
538
  name: formData.name,
475
539
  match: {
@@ -480,8 +544,8 @@ export class OpsViewRoutes extends DeesElement {
480
544
  type: 'forward',
481
545
  targets: [
482
546
  {
483
- host: formData.targetHost || 'localhost',
484
- port: parseInt(formData.targetPort, 10) || 443,
547
+ host: formData.targetHost || currentTargetHost || 'localhost',
548
+ port: targetPort,
485
549
  },
486
550
  ],
487
551
  },
@@ -508,15 +572,17 @@ export class OpsViewRoutes extends DeesElement {
508
572
  }
509
573
 
510
574
  const metadata: any = {};
511
- const profileRefValue = formData.sourceProfileRef as any;
512
- const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
513
575
  if (profileKey) {
514
576
  metadata.sourceProfileRef = profileKey;
577
+ } else if (merged.metadata?.sourceProfileRef) {
578
+ metadata.sourceProfileRef = '';
579
+ metadata.sourceProfileName = '';
515
580
  }
516
- const targetRefValue = formData.networkTargetRef as any;
517
- const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
518
581
  if (targetKey) {
519
582
  metadata.networkTargetRef = targetKey;
583
+ } else if (merged.metadata?.networkTargetRef) {
584
+ metadata.networkTargetRef = '';
585
+ metadata.networkTargetName = '';
520
586
  }
521
587
 
522
588
  await appstate.routeManagementStatePart.dispatchAction(
@@ -537,6 +603,7 @@ export class OpsViewRoutes extends DeesElement {
537
603
  if (editForm) {
538
604
  await editForm.updateComplete;
539
605
  setupTlsVisibility(editForm);
606
+ setupTargetInputState(editForm);
540
607
  }
541
608
  }
542
609
 
@@ -604,6 +671,16 @@ export class OpsViewRoutes extends DeesElement {
604
671
  : [];
605
672
  const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
606
673
 
674
+ const profileKey = getDropdownKey(formData.sourceProfileRef);
675
+ const targetKey = getDropdownKey(formData.networkTargetRef);
676
+ const targetPort = parseTargetPort(formData.targetPort)
677
+ ?? (targetKey ? ports[0] : undefined);
678
+
679
+ if (targetPort === undefined) {
680
+ alert('Target Port must be a valid port number when no network target is selected.');
681
+ return;
682
+ }
683
+
607
684
  const route: any = {
608
685
  name: formData.name,
609
686
  match: {
@@ -615,7 +692,7 @@ export class OpsViewRoutes extends DeesElement {
615
692
  targets: [
616
693
  {
617
694
  host: formData.targetHost || 'localhost',
618
- port: parseInt(formData.targetPort, 10) || 443,
695
+ port: targetPort,
619
696
  },
620
697
  ],
621
698
  },
@@ -641,13 +718,9 @@ export class OpsViewRoutes extends DeesElement {
641
718
 
642
719
  // Build metadata if profile/target selected
643
720
  const metadata: any = {};
644
- const profileRefValue = formData.sourceProfileRef as any;
645
- const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
646
721
  if (profileKey) {
647
722
  metadata.sourceProfileRef = profileKey;
648
723
  }
649
- const targetRefValue = formData.networkTargetRef as any;
650
- const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
651
724
  if (targetKey) {
652
725
  metadata.networkTargetRef = targetKey;
653
726
  }
@@ -669,6 +742,7 @@ export class OpsViewRoutes extends DeesElement {
669
742
  if (createForm) {
670
743
  await createForm.updateComplete;
671
744
  setupTlsVisibility(createForm);
745
+ setupTargetInputState(createForm);
672
746
  }
673
747
  }
674
748