@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.
- package/dist_serve/bundle.js +530 -530
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
- package/dist_ts/config/classes.route-config-manager.js +46 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/network/ops-view-routes.js +81 -16
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/config/classes.route-config-manager.ts +51 -8
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/network/ops-view-routes.ts +89 -15
|
@@ -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 (
|
|
138
|
-
const resolved = this.referenceResolver.resolveRoute(route,
|
|
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 = {
|
|
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
|
}
|
|
@@ -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 —
|
|
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:
|
|
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:
|
|
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
|
|