@serve.zone/dcrouter 13.19.0 → 13.20.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.
- package/dist_serve/bundle.js +660 -650
- 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 +54 -10
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/network/ops-view-routes.js +142 -17
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/config/classes.route-config-manager.ts +61 -9
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/network/ops-view-routes.ts +153 -16
|
@@ -15,16 +15,90 @@ 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
|
+
const checkboxInputs = Array.from(formEl.querySelectorAll('dees-input-checkbox')) as any[];
|
|
47
|
+
return {
|
|
48
|
+
hostInput: textInputs.find((input) => input.key === 'targetHost'),
|
|
49
|
+
portInput: textInputs.find((input) => input.key === 'targetPort'),
|
|
50
|
+
preservePortInput: checkboxInputs.find((input) => input.key === 'preserveMatchPort'),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function setupTargetInputState(formEl: any) {
|
|
55
|
+
const updateState = async () => {
|
|
56
|
+
const data = await formEl.collectFormData();
|
|
57
|
+
const contentEl = formEl.closest('.content') || formEl.parentElement;
|
|
58
|
+
const usesNetworkTarget = !!getDropdownKey(data.networkTargetRef);
|
|
59
|
+
const preserveMatchPort = !usesNetworkTarget && Boolean(data.preserveMatchPort);
|
|
60
|
+
const { hostInput, portInput, preservePortInput } = getRouteTargetInputs(formEl);
|
|
61
|
+
const hostDescription = usesNetworkTarget
|
|
62
|
+
? 'Controlled by the selected network target'
|
|
63
|
+
: 'Used when no network target is selected';
|
|
64
|
+
const portDescription = usesNetworkTarget
|
|
65
|
+
? 'Controlled by the selected network target'
|
|
66
|
+
: preserveMatchPort
|
|
67
|
+
? 'Forwarded to the backend on the same port the client matched'
|
|
68
|
+
: 'Used when no network target is selected';
|
|
69
|
+
|
|
70
|
+
if (hostInput) {
|
|
71
|
+
hostInput.disabled = usesNetworkTarget;
|
|
72
|
+
hostInput.required = !usesNetworkTarget;
|
|
73
|
+
hostInput.description = hostDescription;
|
|
74
|
+
}
|
|
75
|
+
if (portInput) {
|
|
76
|
+
portInput.disabled = usesNetworkTarget || preserveMatchPort;
|
|
77
|
+
portInput.required = !usesNetworkTarget && !preserveMatchPort;
|
|
78
|
+
portInput.description = portDescription;
|
|
79
|
+
}
|
|
80
|
+
if (preservePortInput) {
|
|
81
|
+
preservePortInput.disabled = usesNetworkTarget;
|
|
82
|
+
preservePortInput.description = usesNetworkTarget
|
|
83
|
+
? 'Unavailable when a network target is selected'
|
|
84
|
+
: 'Forward to the backend using the same port that matched this route';
|
|
85
|
+
if (usesNetworkTarget) {
|
|
86
|
+
preservePortInput.value = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const remoteIngressGroup = contentEl?.querySelector('.remoteIngressGroup') as HTMLElement | null;
|
|
91
|
+
if (remoteIngressGroup) {
|
|
92
|
+
remoteIngressGroup.style.display = Boolean(data.remoteIngressEnabled) ? 'flex' : 'none';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await formEl.updateRequiredStatus?.();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
formEl.changeSubject.subscribe(() => updateState());
|
|
99
|
+
updateState();
|
|
100
|
+
}
|
|
101
|
+
|
|
28
102
|
/**
|
|
29
103
|
* Toggle TLS form field visibility based on selected TLS mode and certificate type.
|
|
30
104
|
*/
|
|
@@ -411,10 +485,13 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
411
485
|
? (Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains])
|
|
412
486
|
: [];
|
|
413
487
|
const firstTarget = route.action.targets?.[0];
|
|
488
|
+
const currentPreserveMatchPort = firstTarget?.port === 'preserve';
|
|
414
489
|
const currentTargetHost = firstTarget
|
|
415
490
|
? (Array.isArray(firstTarget.host) ? firstTarget.host[0] : firstTarget.host)
|
|
416
491
|
: '';
|
|
417
|
-
const currentTargetPort = firstTarget?.port
|
|
492
|
+
const currentTargetPort = typeof firstTarget?.port === 'number' ? String(firstTarget.port) : '';
|
|
493
|
+
const currentRemoteIngressEnabled = route.remoteIngress?.enabled === true;
|
|
494
|
+
const currentEdgeFilter = route.remoteIngress?.edgeFilter || [];
|
|
418
495
|
|
|
419
496
|
// Compute current TLS state for pre-population
|
|
420
497
|
const currentTls = (route.action as any).tls;
|
|
@@ -439,6 +516,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
439
516
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
|
|
440
517
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${currentTargetHost}></dees-input-text>
|
|
441
518
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'} .value=${currentTargetPort}></dees-input-text>
|
|
519
|
+
<dees-input-checkbox .key=${'preserveMatchPort'} .label=${'Preserve incoming port'} .value=${currentPreserveMatchPort}></dees-input-checkbox>
|
|
520
|
+
<dees-input-checkbox .key=${'remoteIngressEnabled'} .label=${'Enable Remote Ingress'} .value=${currentRemoteIngressEnabled}></dees-input-checkbox>
|
|
521
|
+
<div class="remoteIngressGroup" style="display: ${currentRemoteIngressEnabled ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
|
522
|
+
<dees-input-list .key=${'remoteIngressEdgeFilter'} .label=${'Edge Filter'} .description=${'Optional edge IDs or tags. Leave empty to allow all edges.'} .placeholder=${'Add edge ID or tag...'} .value=${currentEdgeFilter}></dees-input-list>
|
|
523
|
+
</div>
|
|
442
524
|
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
|
|
443
525
|
<div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
|
444
526
|
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
|
|
@@ -470,6 +552,24 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
470
552
|
: [];
|
|
471
553
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
472
554
|
|
|
555
|
+
const profileKey = getDropdownKey(formData.sourceProfileRef);
|
|
556
|
+
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
557
|
+
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
558
|
+
const targetPort = preserveMatchPort
|
|
559
|
+
? 'preserve'
|
|
560
|
+
: parseTargetPort(formData.targetPort)
|
|
561
|
+
?? (targetKey ? parseTargetPort(currentTargetPort) ?? ports[0] : undefined);
|
|
562
|
+
|
|
563
|
+
if (targetPort === undefined) {
|
|
564
|
+
alert('Target Port must be a valid port number when no network target is selected.');
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const remoteIngressEnabled = Boolean(formData.remoteIngressEnabled);
|
|
569
|
+
const remoteIngressEdgeFilter: string[] = Array.isArray(formData.remoteIngressEdgeFilter)
|
|
570
|
+
? formData.remoteIngressEdgeFilter.filter(Boolean)
|
|
571
|
+
: [];
|
|
572
|
+
|
|
473
573
|
const updatedRoute: any = {
|
|
474
574
|
name: formData.name,
|
|
475
575
|
match: {
|
|
@@ -480,11 +580,17 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
480
580
|
type: 'forward',
|
|
481
581
|
targets: [
|
|
482
582
|
{
|
|
483
|
-
host: formData.targetHost || 'localhost',
|
|
484
|
-
port:
|
|
583
|
+
host: formData.targetHost || currentTargetHost || 'localhost',
|
|
584
|
+
port: targetPort,
|
|
485
585
|
},
|
|
486
586
|
],
|
|
487
587
|
},
|
|
588
|
+
remoteIngress: remoteIngressEnabled
|
|
589
|
+
? {
|
|
590
|
+
enabled: true,
|
|
591
|
+
...(remoteIngressEdgeFilter.length > 0 ? { edgeFilter: remoteIngressEdgeFilter } : {}),
|
|
592
|
+
}
|
|
593
|
+
: null,
|
|
488
594
|
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
|
489
595
|
};
|
|
490
596
|
|
|
@@ -508,15 +614,17 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
508
614
|
}
|
|
509
615
|
|
|
510
616
|
const metadata: any = {};
|
|
511
|
-
const profileRefValue = formData.sourceProfileRef as any;
|
|
512
|
-
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
|
513
617
|
if (profileKey) {
|
|
514
618
|
metadata.sourceProfileRef = profileKey;
|
|
619
|
+
} else if (merged.metadata?.sourceProfileRef) {
|
|
620
|
+
metadata.sourceProfileRef = '';
|
|
621
|
+
metadata.sourceProfileName = '';
|
|
515
622
|
}
|
|
516
|
-
const targetRefValue = formData.networkTargetRef as any;
|
|
517
|
-
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
|
|
518
623
|
if (targetKey) {
|
|
519
624
|
metadata.networkTargetRef = targetKey;
|
|
625
|
+
} else if (merged.metadata?.networkTargetRef) {
|
|
626
|
+
metadata.networkTargetRef = '';
|
|
627
|
+
metadata.networkTargetName = '';
|
|
520
628
|
}
|
|
521
629
|
|
|
522
630
|
await appstate.routeManagementStatePart.dispatchAction(
|
|
@@ -537,6 +645,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
537
645
|
if (editForm) {
|
|
538
646
|
await editForm.updateComplete;
|
|
539
647
|
setupTlsVisibility(editForm);
|
|
648
|
+
setupTargetInputState(editForm);
|
|
540
649
|
}
|
|
541
650
|
}
|
|
542
651
|
|
|
@@ -573,6 +682,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
573
682
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
|
|
574
683
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${'localhost'}></dees-input-text>
|
|
575
684
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'}></dees-input-text>
|
|
685
|
+
<dees-input-checkbox .key=${'preserveMatchPort'} .label=${'Preserve incoming port'} .value=${false}></dees-input-checkbox>
|
|
686
|
+
<dees-input-checkbox .key=${'remoteIngressEnabled'} .label=${'Enable Remote Ingress'} .value=${false}></dees-input-checkbox>
|
|
687
|
+
<div class="remoteIngressGroup" style="display: none; flex-direction: column; gap: 16px;">
|
|
688
|
+
<dees-input-list .key=${'remoteIngressEdgeFilter'} .label=${'Edge Filter'} .description=${'Optional edge IDs or tags. Leave empty to allow all edges.'} .placeholder=${'Add edge ID or tag...'}></dees-input-list>
|
|
689
|
+
</div>
|
|
576
690
|
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
|
|
577
691
|
<div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
|
|
578
692
|
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
|
|
@@ -604,6 +718,24 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
604
718
|
: [];
|
|
605
719
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
606
720
|
|
|
721
|
+
const profileKey = getDropdownKey(formData.sourceProfileRef);
|
|
722
|
+
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
723
|
+
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
724
|
+
const targetPort = preserveMatchPort
|
|
725
|
+
? 'preserve'
|
|
726
|
+
: parseTargetPort(formData.targetPort)
|
|
727
|
+
?? (targetKey ? ports[0] : undefined);
|
|
728
|
+
|
|
729
|
+
if (targetPort === undefined) {
|
|
730
|
+
alert('Target Port must be a valid port number when no network target is selected.');
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const remoteIngressEnabled = Boolean(formData.remoteIngressEnabled);
|
|
735
|
+
const remoteIngressEdgeFilter: string[] = Array.isArray(formData.remoteIngressEdgeFilter)
|
|
736
|
+
? formData.remoteIngressEdgeFilter.filter(Boolean)
|
|
737
|
+
: [];
|
|
738
|
+
|
|
607
739
|
const route: any = {
|
|
608
740
|
name: formData.name,
|
|
609
741
|
match: {
|
|
@@ -615,10 +747,18 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
615
747
|
targets: [
|
|
616
748
|
{
|
|
617
749
|
host: formData.targetHost || 'localhost',
|
|
618
|
-
port:
|
|
750
|
+
port: targetPort,
|
|
619
751
|
},
|
|
620
752
|
],
|
|
621
753
|
},
|
|
754
|
+
...(remoteIngressEnabled
|
|
755
|
+
? {
|
|
756
|
+
remoteIngress: {
|
|
757
|
+
enabled: true,
|
|
758
|
+
...(remoteIngressEdgeFilter.length > 0 ? { edgeFilter: remoteIngressEdgeFilter } : {}),
|
|
759
|
+
},
|
|
760
|
+
}
|
|
761
|
+
: {}),
|
|
622
762
|
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
|
623
763
|
};
|
|
624
764
|
|
|
@@ -641,13 +781,9 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
641
781
|
|
|
642
782
|
// Build metadata if profile/target selected
|
|
643
783
|
const metadata: any = {};
|
|
644
|
-
const profileRefValue = formData.sourceProfileRef as any;
|
|
645
|
-
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
|
646
784
|
if (profileKey) {
|
|
647
785
|
metadata.sourceProfileRef = profileKey;
|
|
648
786
|
}
|
|
649
|
-
const targetRefValue = formData.networkTargetRef as any;
|
|
650
|
-
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
|
|
651
787
|
if (targetKey) {
|
|
652
788
|
metadata.networkTargetRef = targetKey;
|
|
653
789
|
}
|
|
@@ -669,6 +805,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
669
805
|
if (createForm) {
|
|
670
806
|
await createForm.updateComplete;
|
|
671
807
|
setupTlsVisibility(createForm);
|
|
808
|
+
setupTargetInputState(createForm);
|
|
672
809
|
}
|
|
673
810
|
}
|
|
674
811
|
|