@serve.zone/dcrouter 13.43.1 → 13.43.3
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/deno.json +2 -2
- package/dist_serve/bundle.js +894 -896
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/config/classes.reference-resolver.d.ts +3 -3
- package/dist_ts/config/classes.reference-resolver.js +16 -40
- package/dist_ts/config/classes.route-config-manager.d.ts +3 -2
- package/dist_ts/config/classes.route-config-manager.js +38 -36
- package/dist_ts/config/classes.source-policy-compiler.d.ts +9 -4
- package/dist_ts/config/classes.source-policy-compiler.js +92 -26
- package/dist_ts/opsserver/handlers/workhoster.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/workhoster.handler.js +25 -3
- package/dist_ts_interfaces/data/route-management.d.ts +7 -8
- package/dist_ts_migrations/index.js +102 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/network/ops-view-routes.d.ts +1 -1
- package/dist_ts_web/elements/network/ops-view-routes.js +111 -134
- package/package.json +2 -2
- package/readme.md +44 -45
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/config/classes.reference-resolver.ts +16 -40
- package/ts/config/classes.route-config-manager.ts +41 -40
- package/ts/config/classes.source-policy-compiler.ts +115 -30
- package/ts/opsserver/handlers/workhoster.handler.ts +26 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/network/ops-view-routes.ts +112 -136
|
@@ -24,10 +24,7 @@ const tlsCertOptions = [
|
|
|
24
24
|
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
|
|
25
25
|
{ key: 'custom', option: 'Custom certificate' },
|
|
26
26
|
];
|
|
27
|
-
const
|
|
28
|
-
{ key: 'manual', option: 'Manual source policy' },
|
|
29
|
-
{ key: 'gitea', option: 'Gitea bot protection' },
|
|
30
|
-
];
|
|
27
|
+
const maxSourceBindingRows = 16;
|
|
31
28
|
const giteaSourcePolicyProfileNames = ['TRUSTED NETWORKS', 'AI CRAWLERS', 'PUBLIC'] as const;
|
|
32
29
|
|
|
33
30
|
function rateLimit(maxRequests: number): interfaces.data.IRouteSecurity['rateLimit'] {
|
|
@@ -38,10 +35,10 @@ function getDropdownKey(value: any): string {
|
|
|
38
35
|
return typeof value === 'string' ? value : value?.key || '';
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
function
|
|
38
|
+
function getSourceBindingRefsFromFormData(formData: Record<string, any>): string[] {
|
|
42
39
|
const refs: string[] = [];
|
|
43
|
-
for (let index = 0; index <
|
|
44
|
-
const ref = getDropdownKey(formData[`
|
|
40
|
+
for (let index = 0; index < maxSourceBindingRows; index++) {
|
|
41
|
+
const ref = getDropdownKey(formData[`sourceBindingProfileRef${index}`]);
|
|
45
42
|
if (ref && !refs.includes(ref)) {
|
|
46
43
|
refs.push(ref);
|
|
47
44
|
}
|
|
@@ -49,25 +46,23 @@ function getSourcePolicyRefsFromFormData(formData: Record<string, any>): string[
|
|
|
49
46
|
return refs;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
function
|
|
49
|
+
function buildSourceBindingsMetadata(
|
|
53
50
|
profileRefs: string[],
|
|
54
|
-
|
|
55
|
-
): interfaces.data.
|
|
56
|
-
return {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}),
|
|
70
|
-
};
|
|
51
|
+
existingSourceBindings?: interfaces.data.IRouteSourceBinding[],
|
|
52
|
+
): interfaces.data.IRouteSourceBinding[] {
|
|
53
|
+
return profileRefs.map((sourceProfileRef) => {
|
|
54
|
+
const existingBinding = existingSourceBindings?.find((binding) => binding.sourceProfileRef === sourceProfileRef);
|
|
55
|
+
return existingBinding
|
|
56
|
+
? {
|
|
57
|
+
...existingBinding,
|
|
58
|
+
sourceProfileRef,
|
|
59
|
+
onExceeded: existingBinding.onExceeded || { type: '429' as const },
|
|
60
|
+
}
|
|
61
|
+
: {
|
|
62
|
+
sourceProfileRef,
|
|
63
|
+
onExceeded: { type: '429' as const },
|
|
64
|
+
};
|
|
65
|
+
});
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
function getGiteaPresetProfileRefs(profiles: interfaces.data.ISourceProfile[]): {
|
|
@@ -87,56 +82,54 @@ function getGiteaPresetProfileRefs(profiles: interfaces.data.ISourceProfile[]):
|
|
|
87
82
|
return { refs, missingNames };
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
function
|
|
85
|
+
function buildGiteaSourceBindingsMetadata(profileRefs: string[]): interfaces.data.IRouteSourceBinding[] {
|
|
91
86
|
const [trustedRef, aiRef, publicRef] = profileRefs;
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
],
|
|
123
|
-
};
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
sourceProfileRef: trustedRef,
|
|
90
|
+
onExceeded: { type: '429' as const },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
sourceProfileRef: aiRef,
|
|
94
|
+
onExceeded: { type: '429' as const },
|
|
95
|
+
pathPolicies: [
|
|
96
|
+
{ pathClass: 'git-smart-http', rateLimit: rateLimit(1200) },
|
|
97
|
+
{ pathClass: 'static', rateLimit: rateLimit(240) },
|
|
98
|
+
{ pathClass: 'raw', rateLimit: rateLimit(20) },
|
|
99
|
+
{ pathClass: 'archive', rateLimit: rateLimit(6) },
|
|
100
|
+
{ pathClass: 'expensive-html', rateLimit: rateLimit(6) },
|
|
101
|
+
{ pathClass: 'normal-html', rateLimit: rateLimit(20) },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
sourceProfileRef: publicRef,
|
|
106
|
+
onExceeded: { type: '429' as const },
|
|
107
|
+
pathPolicies: [
|
|
108
|
+
{ pathClass: 'git-smart-http', rateLimit: rateLimit(1200) },
|
|
109
|
+
{ pathClass: 'static', rateLimit: rateLimit(600) },
|
|
110
|
+
{ pathClass: 'raw', rateLimit: rateLimit(120) },
|
|
111
|
+
{ pathClass: 'archive', rateLimit: rateLimit(30) },
|
|
112
|
+
{ pathClass: 'expensive-html', rateLimit: rateLimit(30) },
|
|
113
|
+
{ pathClass: 'normal-html', rateLimit: rateLimit(120) },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
124
117
|
}
|
|
125
118
|
|
|
126
|
-
function
|
|
119
|
+
function getGiteaPresetSourceBindings(profiles: interfaces.data.ISourceProfile[]): interfaces.data.IRouteSourceBinding[] | null {
|
|
127
120
|
const { refs, missingNames } = getGiteaPresetProfileRefs(profiles);
|
|
128
121
|
if (missingNames.length > 0) {
|
|
129
122
|
alert(`Gitea source-policy preset needs these seeded profiles: ${missingNames.join(', ')}`);
|
|
130
123
|
return null;
|
|
131
124
|
}
|
|
132
|
-
if (!
|
|
125
|
+
if (!validateSourceBindingSelection(refs, profiles)) {
|
|
133
126
|
return null;
|
|
134
127
|
}
|
|
135
|
-
return
|
|
128
|
+
return buildGiteaSourceBindingsMetadata(refs);
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
function metadataUsesPathPolicies(metadata?: interfaces.data.IRouteMetadata): boolean {
|
|
139
|
-
return Boolean(metadata?.
|
|
132
|
+
return Boolean(metadata?.sourceBindings?.some((binding) => binding.pathPolicies?.length));
|
|
140
133
|
}
|
|
141
134
|
|
|
142
135
|
function sourceProfileMatchesAll(profile: interfaces.data.ISourceProfile): boolean {
|
|
@@ -153,7 +146,7 @@ function sourceProfileHasSourceMatches(profile: interfaces.data.ISourceProfile):
|
|
|
153
146
|
});
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
function
|
|
149
|
+
function validateSourceBindingSelection(
|
|
157
150
|
profileRefs: string[],
|
|
158
151
|
profiles: interfaces.data.ISourceProfile[],
|
|
159
152
|
): boolean {
|
|
@@ -176,19 +169,14 @@ function validateSourcePolicySelection(
|
|
|
176
169
|
return false;
|
|
177
170
|
}
|
|
178
171
|
|
|
179
|
-
const fallbackProfile = selectedProfiles[selectedProfiles.length - 1];
|
|
180
|
-
if (!sourceProfileMatchesAll(fallbackProfile)) {
|
|
181
|
-
alert('Source policy needs an explicit public/wildcard fallback profile as the last binding. Add a profile with IP Allow List "*".');
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
172
|
if (selectedProfiles.slice(0, -1).some((profile) => sourceProfileMatchesAll(profile))) {
|
|
186
173
|
alert('Wildcard source profiles must be last. Earlier wildcard profiles would shadow all following profiles.');
|
|
187
174
|
return false;
|
|
188
175
|
}
|
|
189
176
|
|
|
190
|
-
|
|
191
|
-
|
|
177
|
+
const fallbackProfile = selectedProfiles[selectedProfiles.length - 1];
|
|
178
|
+
if (sourceProfileMatchesAll(fallbackProfile) && fallbackProfile.security?.rateLimit?.enabled !== true) {
|
|
179
|
+
return confirm(`The wildcard profile "${fallbackProfile.name}" has no enabled rate limit. Save anyway?`);
|
|
192
180
|
}
|
|
193
181
|
|
|
194
182
|
return true;
|
|
@@ -521,7 +509,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
521
509
|
|
|
522
510
|
const meta = merged.metadata;
|
|
523
511
|
const isSystemManaged = this.isSystemManagedRoute(merged);
|
|
524
|
-
const
|
|
512
|
+
const sourceBindingSummary = this.describeSourcePolicy(meta);
|
|
525
513
|
await DeesModal.createAndShow({
|
|
526
514
|
heading: `Route: ${merged.route.name}`,
|
|
527
515
|
content: html`
|
|
@@ -531,7 +519,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
531
519
|
${merged.route.vpnOnly ? html`<p>Access: <strong style="color: #22c55e;">VPN only</strong></p>` : ''}
|
|
532
520
|
<p>ID: <code style="color: #888;">${merged.id}</code></p>
|
|
533
521
|
${isSystemManaged ? html`<p>This route is system-managed. Change its source config to modify it directly.</p>` : ''}
|
|
534
|
-
${
|
|
522
|
+
${sourceBindingSummary ? html`<p>Source Bindings: <strong style="color: #a78bfa;">${sourceBindingSummary}</strong></p>` : ''}
|
|
535
523
|
${meta?.networkTargetName ? html`<p>Network Target: <strong style="color: #a78bfa;">${meta.networkTargetName}</strong></p>` : ''}
|
|
536
524
|
</div>
|
|
537
525
|
`,
|
|
@@ -663,8 +651,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
663
651
|
const currentVpnOnly = route.vpnOnly === true;
|
|
664
652
|
const currentRemoteIngressEnabled = route.remoteIngress?.enabled === true;
|
|
665
653
|
const currentEdgeFilter = route.remoteIngress?.edgeFilter || [];
|
|
666
|
-
const
|
|
667
|
-
const currentSourcePolicyPreset = metadataUsesPathPolicies(merged.metadata) ? 'gitea' : 'manual';
|
|
654
|
+
const currentSourceBindingRefs = this.getSourceBindingRefs(merged.metadata);
|
|
668
655
|
|
|
669
656
|
// Compute current TLS state for pre-population
|
|
670
657
|
const currentTls = (route.action as any).tls;
|
|
@@ -686,21 +673,20 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
686
673
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
|
|
687
674
|
<dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
|
|
688
675
|
<div class="sourcePolicyGroup" style="display: flex; flex-direction: column; gap: 12px; padding: 12px; border: 1px solid rgba(255,255,255,0.12); border-radius: 8px;">
|
|
689
|
-
<strong>Source
|
|
690
|
-
<small>First matching profile wins.
|
|
691
|
-
<dees-input-
|
|
692
|
-
.key=${'
|
|
693
|
-
.label=${'
|
|
694
|
-
.
|
|
695
|
-
.
|
|
696
|
-
></dees-input-
|
|
697
|
-
|
|
698
|
-
${[0, 1, 2, 3].map((index) => html`
|
|
676
|
+
<strong>Source Bindings</strong>
|
|
677
|
+
<small>First matching source profile wins. Leave all rows empty to remove route-level source access control.</small>
|
|
678
|
+
<dees-input-checkbox
|
|
679
|
+
.key=${'useGiteaTemplate'}
|
|
680
|
+
.label=${'Apply Gitea bot protection template on save'}
|
|
681
|
+
.description=${'Replaces these rows with TRUSTED NETWORKS -> AI CRAWLERS -> PUBLIC and path-class limits.'}
|
|
682
|
+
.value=${false}
|
|
683
|
+
></dees-input-checkbox>
|
|
684
|
+
${Array.from({ length: maxSourceBindingRows }, (_item, index) => html`
|
|
699
685
|
<dees-input-dropdown
|
|
700
|
-
.key=${`
|
|
701
|
-
.label=${`
|
|
686
|
+
.key=${`sourceBindingProfileRef${index}`}
|
|
687
|
+
.label=${`Binding ${index + 1}`}
|
|
702
688
|
.options=${profileOptions}
|
|
703
|
-
.selectedOption=${profileOptions.find((o) => o.key === (
|
|
689
|
+
.selectedOption=${profileOptions.find((o) => o.key === (currentSourceBindingRefs[index] || '')) || profileOptions[0]}
|
|
704
690
|
></dees-input-dropdown>
|
|
705
691
|
`)}
|
|
706
692
|
</div>
|
|
@@ -744,11 +730,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
744
730
|
: [];
|
|
745
731
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
746
732
|
|
|
747
|
-
const
|
|
748
|
-
const
|
|
733
|
+
const useGiteaTemplate = Boolean(formData.useGiteaTemplate);
|
|
734
|
+
const sourceBindingRefs = useGiteaTemplate
|
|
749
735
|
? []
|
|
750
|
-
:
|
|
751
|
-
if (
|
|
736
|
+
: getSourceBindingRefsFromFormData(formData);
|
|
737
|
+
if (!useGiteaTemplate && !validateSourceBindingSelection(sourceBindingRefs, profiles)) return;
|
|
752
738
|
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
753
739
|
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
754
740
|
const targetPort = preserveMatchPort
|
|
@@ -812,20 +798,14 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
812
798
|
}
|
|
813
799
|
|
|
814
800
|
const metadata: any = {};
|
|
815
|
-
if (
|
|
816
|
-
const
|
|
817
|
-
if (!
|
|
818
|
-
metadata.
|
|
819
|
-
|
|
820
|
-
metadata.
|
|
821
|
-
} else if (
|
|
822
|
-
metadata.
|
|
823
|
-
metadata.sourceProfileRef = '';
|
|
824
|
-
metadata.sourceProfileName = '';
|
|
825
|
-
} else if (merged.metadata?.sourcePolicy || merged.metadata?.sourceProfileRef) {
|
|
826
|
-
metadata.sourcePolicy = { bindings: [] };
|
|
827
|
-
metadata.sourceProfileRef = '';
|
|
828
|
-
metadata.sourceProfileName = '';
|
|
801
|
+
if (useGiteaTemplate) {
|
|
802
|
+
const sourceBindings = getGiteaPresetSourceBindings(profiles);
|
|
803
|
+
if (!sourceBindings) return;
|
|
804
|
+
metadata.sourceBindings = sourceBindings;
|
|
805
|
+
} else if (sourceBindingRefs.length > 0) {
|
|
806
|
+
metadata.sourceBindings = buildSourceBindingsMetadata(sourceBindingRefs, merged.metadata?.sourceBindings);
|
|
807
|
+
} else if (merged.metadata?.sourceBindings) {
|
|
808
|
+
metadata.sourceBindings = [];
|
|
829
809
|
}
|
|
830
810
|
if (targetKey) {
|
|
831
811
|
metadata.networkTargetRef = targetKey;
|
|
@@ -886,19 +866,18 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
886
866
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
|
|
887
867
|
<dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'}></dees-input-text>
|
|
888
868
|
<div class="sourcePolicyGroup" style="display: flex; flex-direction: column; gap: 12px; padding: 12px; border: 1px solid rgba(255,255,255,0.12); border-radius: 8px;">
|
|
889
|
-
<strong>Source
|
|
890
|
-
<small>First matching profile wins.
|
|
891
|
-
<dees-input-
|
|
892
|
-
.key=${'
|
|
893
|
-
.label=${'
|
|
894
|
-
.
|
|
895
|
-
.
|
|
896
|
-
></dees-input-
|
|
897
|
-
|
|
898
|
-
${[0, 1, 2, 3].map((index) => html`
|
|
869
|
+
<strong>Source Bindings</strong>
|
|
870
|
+
<small>First matching source profile wins. Leave all rows empty for no route-level source access control.</small>
|
|
871
|
+
<dees-input-checkbox
|
|
872
|
+
.key=${'useGiteaTemplate'}
|
|
873
|
+
.label=${'Apply Gitea bot protection template on save'}
|
|
874
|
+
.description=${'Writes TRUSTED NETWORKS -> AI CRAWLERS -> PUBLIC and path-class limits.'}
|
|
875
|
+
.value=${false}
|
|
876
|
+
></dees-input-checkbox>
|
|
877
|
+
${Array.from({ length: maxSourceBindingRows }, (_item, index) => html`
|
|
899
878
|
<dees-input-dropdown
|
|
900
|
-
.key=${`
|
|
901
|
-
.label=${`
|
|
879
|
+
.key=${`sourceBindingProfileRef${index}`}
|
|
880
|
+
.label=${`Binding ${index + 1}`}
|
|
902
881
|
.options=${profileOptions}
|
|
903
882
|
.selectedOption=${profileOptions[0]}
|
|
904
883
|
></dees-input-dropdown>
|
|
@@ -944,11 +923,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
944
923
|
: [];
|
|
945
924
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
946
925
|
|
|
947
|
-
const
|
|
948
|
-
const
|
|
926
|
+
const useGiteaTemplate = Boolean(formData.useGiteaTemplate);
|
|
927
|
+
const sourceBindingRefs = useGiteaTemplate
|
|
949
928
|
? []
|
|
950
|
-
:
|
|
951
|
-
if (
|
|
929
|
+
: getSourceBindingRefsFromFormData(formData);
|
|
930
|
+
if (!useGiteaTemplate && !validateSourceBindingSelection(sourceBindingRefs, profiles)) return;
|
|
952
931
|
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
953
932
|
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
954
933
|
const targetPort = preserveMatchPort
|
|
@@ -1013,12 +992,12 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
1013
992
|
|
|
1014
993
|
// Build metadata if profile/target selected
|
|
1015
994
|
const metadata: any = {};
|
|
1016
|
-
if (
|
|
1017
|
-
const
|
|
1018
|
-
if (!
|
|
1019
|
-
metadata.
|
|
1020
|
-
} else if (
|
|
1021
|
-
metadata.
|
|
995
|
+
if (useGiteaTemplate) {
|
|
996
|
+
const sourceBindings = getGiteaPresetSourceBindings(profiles);
|
|
997
|
+
if (!sourceBindings) return;
|
|
998
|
+
metadata.sourceBindings = sourceBindings;
|
|
999
|
+
} else if (sourceBindingRefs.length > 0) {
|
|
1000
|
+
metadata.sourceBindings = buildSourceBindingsMetadata(sourceBindingRefs);
|
|
1022
1001
|
}
|
|
1023
1002
|
if (targetKey) {
|
|
1024
1003
|
metadata.networkTargetRef = targetKey;
|
|
@@ -1049,23 +1028,20 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
1049
1028
|
appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
|
|
1050
1029
|
}
|
|
1051
1030
|
|
|
1052
|
-
private
|
|
1053
|
-
const
|
|
1031
|
+
private getSourceBindingRefs(metadata?: interfaces.data.IRouteMetadata): string[] {
|
|
1032
|
+
const bindingRefs = metadata?.sourceBindings
|
|
1054
1033
|
?.map((binding) => binding.sourceProfileRef)
|
|
1055
1034
|
.filter(Boolean) || [];
|
|
1056
|
-
|
|
1057
|
-
return policyRefs;
|
|
1058
|
-
}
|
|
1059
|
-
return metadata?.sourceProfileRef ? [metadata.sourceProfileRef] : [];
|
|
1035
|
+
return bindingRefs;
|
|
1060
1036
|
}
|
|
1061
1037
|
|
|
1062
1038
|
private describeSourcePolicy(metadata?: interfaces.data.IRouteMetadata): string {
|
|
1063
|
-
const refs = this.
|
|
1039
|
+
const refs = this.getSourceBindingRefs(metadata);
|
|
1064
1040
|
if (refs.length === 0) {
|
|
1065
1041
|
return '';
|
|
1066
1042
|
}
|
|
1067
1043
|
return refs.map((ref) => {
|
|
1068
|
-
const binding = metadata?.
|
|
1044
|
+
const binding = metadata?.sourceBindings?.find((item) => item.sourceProfileRef === ref);
|
|
1069
1045
|
const profile = this.profilesTargetsState.profiles.find((item) => item.id === ref);
|
|
1070
1046
|
return binding?.sourceProfileName || profile?.name || ref.slice(0, 8);
|
|
1071
1047
|
}).join(' → ');
|