@serve.zone/dcrouter 13.44.1 → 14.0.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/deno.json +1 -1
- package/dist_serve/bundle.js +1882 -1453
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/acme/manager.acme-config.d.ts +1 -14
- package/dist_ts/acme/manager.acme-config.js +4 -65
- package/dist_ts/classes.dcrouter.d.ts +7 -2
- package/dist_ts/classes.dcrouter.js +105 -27
- package/dist_ts/config/classes.api-token-manager.js +3 -3
- package/dist_ts/db/documents/classes.acme-config.doc.d.ts +1 -3
- package/dist_ts/db/documents/classes.acme-config.doc.js +2 -4
- package/dist_ts/dns/manager.dns.d.ts +0 -13
- package/dist_ts/dns/manager.dns.js +1 -81
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +0 -9
- package/dist_ts/opsserver/handlers/certificate.handler.js +1 -40
- package/dist_ts/opsserver/handlers/config.handler.js +11 -12
- package/dist_ts/opsserver/handlers/email-settings.handler.js +2 -2
- package/dist_ts_interfaces/data/acme-config.d.ts +1 -3
- package/dist_ts_interfaces/requests/certificate.d.ts +0 -12
- package/dist_ts_migrations/index.js +2 -2
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/elements/network/ops-view-routes.js +118 -142
- package/package.json +4 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/acme/manager.acme-config.ts +3 -77
- package/ts/classes.dcrouter.ts +120 -28
- package/ts/config/classes.api-token-manager.ts +2 -2
- package/ts/db/documents/classes.acme-config.doc.ts +1 -3
- package/ts/dns/manager.dns.ts +0 -103
- package/ts/opsserver/handlers/certificate.handler.ts +0 -47
- package/ts/opsserver/handlers/config.handler.ts +10 -11
- package/ts/opsserver/handlers/email-settings.handler.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/network/ops-view-routes.ts +124 -146
|
@@ -2,6 +2,12 @@ import * as appstate from '../../appstate.js';
|
|
|
2
2
|
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
3
3
|
import { viewHostCss } from '../shared/css.js';
|
|
4
4
|
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
5
|
+
import type {
|
|
6
|
+
IRoutePathClassOption as ISzRoutePathClassOption,
|
|
7
|
+
IRouteSourcePolicyPreset as ISzRouteSourcePolicyPreset,
|
|
8
|
+
ISourceProfileOption as ISzSourceProfileOption,
|
|
9
|
+
SzInputRouteSourcePolicy,
|
|
10
|
+
} from '@serve.zone/catalog';
|
|
5
11
|
|
|
6
12
|
import {
|
|
7
13
|
DeesElement,
|
|
@@ -24,8 +30,8 @@ const tlsCertOptions = [
|
|
|
24
30
|
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
|
|
25
31
|
{ key: 'custom', option: 'Custom certificate' },
|
|
26
32
|
];
|
|
27
|
-
const maxSourceBindingRows = 16;
|
|
28
33
|
const giteaSourcePolicyProfileNames = ['TRUSTED NETWORKS', 'AI CRAWLERS', 'PUBLIC'] as const;
|
|
34
|
+
type TSzRouteSecurity = NonNullable<ISzSourceProfileOption['security']>;
|
|
29
35
|
|
|
30
36
|
function rateLimit(maxRequests: number): interfaces.data.IRouteSecurity['rateLimit'] {
|
|
31
37
|
return { enabled: true, maxRequests, window: 60, keyBy: 'ip' };
|
|
@@ -35,36 +41,6 @@ function getDropdownKey(value: any): string {
|
|
|
35
41
|
return typeof value === 'string' ? value : value?.key || '';
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function getSourceBindingRefsFromFormData(formData: Record<string, any>): string[] {
|
|
39
|
-
const refs: string[] = [];
|
|
40
|
-
for (let index = 0; index < maxSourceBindingRows; index++) {
|
|
41
|
-
const ref = getDropdownKey(formData[`sourceBindingProfileRef${index}`]);
|
|
42
|
-
if (ref && !refs.includes(ref)) {
|
|
43
|
-
refs.push(ref);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return refs;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function buildSourceBindingsMetadata(
|
|
50
|
-
profileRefs: string[],
|
|
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
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
44
|
function getGiteaPresetProfileRefs(profiles: interfaces.data.ISourceProfile[]): {
|
|
69
45
|
refs: string[];
|
|
70
46
|
missingNames: string[];
|
|
@@ -116,70 +92,111 @@ function buildGiteaSourceBindingsMetadata(profileRefs: string[]): interfaces.dat
|
|
|
116
92
|
];
|
|
117
93
|
}
|
|
118
94
|
|
|
119
|
-
function
|
|
95
|
+
function getGiteaSourcePolicyPresets(profiles: interfaces.data.ISourceProfile[]): ISzRouteSourcePolicyPreset[] {
|
|
120
96
|
const { refs, missingNames } = getGiteaPresetProfileRefs(profiles);
|
|
121
97
|
if (missingNames.length > 0) {
|
|
122
|
-
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
if (!validateSourceBindingSelection(refs, profiles)) {
|
|
126
|
-
return null;
|
|
98
|
+
return [];
|
|
127
99
|
}
|
|
128
|
-
return
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
key: 'gitea-bot-protection',
|
|
103
|
+
label: 'Gitea bot protection',
|
|
104
|
+
description: 'TRUSTED NETWORKS -> AI CRAWLERS -> PUBLIC with path-class rate limits.',
|
|
105
|
+
bindings: buildGiteaSourceBindingsMetadata(refs),
|
|
106
|
+
},
|
|
107
|
+
];
|
|
129
108
|
}
|
|
130
109
|
|
|
131
|
-
function
|
|
132
|
-
|
|
110
|
+
function normalizeSecurityListEntries(entries: unknown): string[] {
|
|
111
|
+
if (!Array.isArray(entries)) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return entries
|
|
115
|
+
.map((entry) => {
|
|
116
|
+
if (typeof entry === 'string') return entry.trim();
|
|
117
|
+
if (entry && typeof entry === 'object' && 'ip' in entry) {
|
|
118
|
+
const ip = (entry as Record<string, unknown>).ip;
|
|
119
|
+
return typeof ip === 'string' ? ip.trim() : '';
|
|
120
|
+
}
|
|
121
|
+
return '';
|
|
122
|
+
})
|
|
123
|
+
.filter(Boolean);
|
|
133
124
|
}
|
|
134
125
|
|
|
135
126
|
function sourceProfileMatchesAll(profile: interfaces.data.ISourceProfile): boolean {
|
|
136
|
-
return (profile.security?.ipAllowList
|
|
137
|
-
const source = typeof entry === 'string' ? entry : entry.ip;
|
|
127
|
+
return normalizeSecurityListEntries(profile.security?.ipAllowList).some((source) => {
|
|
138
128
|
return ['*', '0.0.0.0/0', '::/0'].includes(source.trim());
|
|
139
129
|
});
|
|
140
130
|
}
|
|
141
131
|
|
|
142
132
|
function sourceProfileHasSourceMatches(profile: interfaces.data.ISourceProfile): boolean {
|
|
143
|
-
return (profile.security?.ipAllowList
|
|
144
|
-
const source = typeof entry === 'string' ? entry : entry.ip;
|
|
145
|
-
return source.trim().length > 0;
|
|
146
|
-
});
|
|
133
|
+
return normalizeSecurityListEntries(profile.security?.ipAllowList).length > 0;
|
|
147
134
|
}
|
|
148
135
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
136
|
+
function normalizeCatalogRateLimit(
|
|
137
|
+
rateLimitValue: interfaces.data.IRouteSecurity['rateLimit'] | undefined,
|
|
138
|
+
): TSzRouteSecurity['rateLimit'] | undefined {
|
|
139
|
+
if (!rateLimitValue) return undefined;
|
|
140
|
+
return {
|
|
141
|
+
enabled: Boolean(rateLimitValue.enabled),
|
|
142
|
+
maxRequests: Number(rateLimitValue.maxRequests) || 0,
|
|
143
|
+
window: Number(rateLimitValue.window) || 0,
|
|
144
|
+
...(rateLimitValue.keyBy ? { keyBy: String(rateLimitValue.keyBy) } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
160
147
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
148
|
+
function getSourceProfileOptions(profiles: interfaces.data.ISourceProfile[]): ISzSourceProfileOption[] {
|
|
149
|
+
return profiles.map((profile) => {
|
|
150
|
+
const ipAllowList = normalizeSecurityListEntries(profile.security?.ipAllowList);
|
|
151
|
+
const ipBlockList = normalizeSecurityListEntries(profile.security?.ipBlockList);
|
|
152
|
+
const rateLimitValue = normalizeCatalogRateLimit(profile.security?.rateLimit);
|
|
153
|
+
const security: TSzRouteSecurity = {
|
|
154
|
+
...(ipAllowList.length ? { ipAllowList } : {}),
|
|
155
|
+
...(ipBlockList.length ? { ipBlockList } : {}),
|
|
156
|
+
...(typeof profile.security?.maxConnections === 'number' ? { maxConnections: profile.security.maxConnections } : {}),
|
|
157
|
+
...(rateLimitValue ? { rateLimit: rateLimitValue } : {}),
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
id: profile.id,
|
|
161
|
+
name: profile.name,
|
|
162
|
+
description: profile.description,
|
|
163
|
+
security,
|
|
164
|
+
hasSourceMatches: sourceProfileHasSourceMatches(profile),
|
|
165
|
+
matchesAllSources: sourceProfileMatchesAll(profile),
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
function getRoutePathClassOptions(): ISzRoutePathClassOption[] {
|
|
171
|
+
return interfaces.data.routePathClasses.map((pathClass) => ({
|
|
172
|
+
key: pathClass,
|
|
173
|
+
label: interfaces.data.giteaRoutePathClassLabels[pathClass],
|
|
174
|
+
defaultPatterns: interfaces.data.giteaRoutePathClassPatterns[pathClass],
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
171
177
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
function getSourcePolicyInfoText(profiles: interfaces.data.ISourceProfile[]): string {
|
|
179
|
+
const { missingNames } = getGiteaPresetProfileRefs(profiles);
|
|
180
|
+
const presetText = missingNames.length > 0
|
|
181
|
+
? `Gitea preset hidden until these source profiles exist: ${missingNames.join(', ')}.`
|
|
182
|
+
: 'Use the Gitea preset as a starting point, then edit the generated bindings before saving.';
|
|
183
|
+
return `First matching source profile wins. Leave empty for no route-level source access control. ${presetText}`;
|
|
184
|
+
}
|
|
176
185
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
186
|
+
function validateSourcePolicyInput(form: Element): boolean {
|
|
187
|
+
const sourcePolicyInput = form.querySelector('sz-input-route-source-policy') as SzInputRouteSourcePolicy | null;
|
|
188
|
+
if (!sourcePolicyInput || sourcePolicyInput.isValid()) {
|
|
189
|
+
return true;
|
|
180
190
|
}
|
|
191
|
+
alert(sourcePolicyInput.getValidationMessages().join('\n'));
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
181
194
|
|
|
182
|
-
|
|
195
|
+
function getSourceBindingsFromFormData(formData: Record<string, unknown>): interfaces.data.IRouteSourceBinding[] {
|
|
196
|
+
const sourceBindings = formData.sourceBindings;
|
|
197
|
+
return Array.isArray(sourceBindings)
|
|
198
|
+
? sourceBindings as interfaces.data.IRouteSourceBinding[]
|
|
199
|
+
: [];
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
function parseTargetPort(value: any): number | undefined {
|
|
@@ -620,13 +637,6 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
620
637
|
const profiles = this.profilesTargetsState.profiles;
|
|
621
638
|
const targets = this.profilesTargetsState.targets;
|
|
622
639
|
|
|
623
|
-
const profileOptions = [
|
|
624
|
-
{ key: '', option: '(none — inline security)' },
|
|
625
|
-
...profiles.map((p) => ({
|
|
626
|
-
key: p.id,
|
|
627
|
-
option: `${p.name}${p.description ? ' — ' + p.description : ''}`,
|
|
628
|
-
})),
|
|
629
|
-
];
|
|
630
640
|
const targetOptions = [
|
|
631
641
|
{ key: '', option: '(none — inline target)' },
|
|
632
642
|
...targets.map((t) => ({
|
|
@@ -651,7 +661,10 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
651
661
|
const currentVpnOnly = route.vpnOnly === true;
|
|
652
662
|
const currentRemoteIngressEnabled = route.remoteIngress?.enabled === true;
|
|
653
663
|
const currentEdgeFilter = route.remoteIngress?.edgeFilter || [];
|
|
654
|
-
const
|
|
664
|
+
const sourceProfileOptions = getSourceProfileOptions(profiles);
|
|
665
|
+
const pathClassOptions = getRoutePathClassOptions();
|
|
666
|
+
const sourcePolicyPresets = getGiteaSourcePolicyPresets(profiles);
|
|
667
|
+
const sourcePolicyInfoText = getSourcePolicyInfoText(profiles);
|
|
655
668
|
|
|
656
669
|
// Compute current TLS state for pre-population
|
|
657
670
|
const currentTls = (route.action as any).tls;
|
|
@@ -672,24 +685,15 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
672
685
|
<dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .value=${currentPorts} .required=${true}></dees-input-text>
|
|
673
686
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
|
|
674
687
|
<dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
|
|
675
|
-
<
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
${Array.from({ length: maxSourceBindingRows }, (_item, index) => html`
|
|
685
|
-
<dees-input-dropdown
|
|
686
|
-
.key=${`sourceBindingProfileRef${index}`}
|
|
687
|
-
.label=${`Binding ${index + 1}`}
|
|
688
|
-
.options=${profileOptions}
|
|
689
|
-
.selectedOption=${profileOptions.find((o) => o.key === (currentSourceBindingRefs[index] || '')) || profileOptions[0]}
|
|
690
|
-
></dees-input-dropdown>
|
|
691
|
-
`)}
|
|
692
|
-
</div>
|
|
688
|
+
<sz-input-route-source-policy
|
|
689
|
+
.key=${'sourceBindings'}
|
|
690
|
+
.label=${'Source Policy'}
|
|
691
|
+
.infoText=${sourcePolicyInfoText}
|
|
692
|
+
.sourceProfiles=${sourceProfileOptions}
|
|
693
|
+
.pathClassOptions=${pathClassOptions}
|
|
694
|
+
.presets=${sourcePolicyPresets}
|
|
695
|
+
.value=${merged.metadata?.sourceBindings || []}
|
|
696
|
+
></sz-input-route-source-policy>
|
|
693
697
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
|
|
694
698
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${currentTargetHost}></dees-input-text>
|
|
695
699
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'} .value=${currentTargetPort}></dees-input-text>
|
|
@@ -723,6 +727,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
723
727
|
if (!form) return;
|
|
724
728
|
const formData = await form.collectFormData();
|
|
725
729
|
if (!formData.name || !formData.ports) return;
|
|
730
|
+
if (!validateSourcePolicyInput(form)) return;
|
|
726
731
|
|
|
727
732
|
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
|
728
733
|
const domains: string[] = Array.isArray(formData.domains)
|
|
@@ -730,11 +735,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
730
735
|
: [];
|
|
731
736
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
732
737
|
|
|
733
|
-
const
|
|
734
|
-
const sourceBindingRefs = useGiteaTemplate
|
|
735
|
-
? []
|
|
736
|
-
: getSourceBindingRefsFromFormData(formData);
|
|
737
|
-
if (!useGiteaTemplate && !validateSourceBindingSelection(sourceBindingRefs, profiles)) return;
|
|
738
|
+
const sourceBindings = getSourceBindingsFromFormData(formData);
|
|
738
739
|
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
739
740
|
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
740
741
|
const targetPort = preserveMatchPort
|
|
@@ -798,12 +799,8 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
798
799
|
}
|
|
799
800
|
|
|
800
801
|
const metadata: any = {};
|
|
801
|
-
if (
|
|
802
|
-
const sourceBindings = getGiteaPresetSourceBindings(profiles);
|
|
803
|
-
if (!sourceBindings) return;
|
|
802
|
+
if (sourceBindings.length > 0) {
|
|
804
803
|
metadata.sourceBindings = sourceBindings;
|
|
805
|
-
} else if (sourceBindingRefs.length > 0) {
|
|
806
|
-
metadata.sourceBindings = buildSourceBindingsMetadata(sourceBindingRefs, merged.metadata?.sourceBindings);
|
|
807
804
|
} else if (merged.metadata?.sourceBindings) {
|
|
808
805
|
metadata.sourceBindings = [];
|
|
809
806
|
}
|
|
@@ -841,14 +838,11 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
841
838
|
const profiles = this.profilesTargetsState.profiles;
|
|
842
839
|
const targets = this.profilesTargetsState.targets;
|
|
843
840
|
|
|
844
|
-
// Build dropdown options for
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
option: `${p.name}${p.description ? ' — ' + p.description : ''}`,
|
|
850
|
-
})),
|
|
851
|
-
];
|
|
841
|
+
// Build dropdown options for targets and source policy metadata
|
|
842
|
+
const sourceProfileOptions = getSourceProfileOptions(profiles);
|
|
843
|
+
const pathClassOptions = getRoutePathClassOptions();
|
|
844
|
+
const sourcePolicyPresets = getGiteaSourcePolicyPresets(profiles);
|
|
845
|
+
const sourcePolicyInfoText = getSourcePolicyInfoText(profiles);
|
|
852
846
|
const targetOptions = [
|
|
853
847
|
{ key: '', option: '(none — inline target)' },
|
|
854
848
|
...targets.map((t) => ({
|
|
@@ -865,24 +859,15 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
865
859
|
<dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .required=${true}></dees-input-text>
|
|
866
860
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
|
|
867
861
|
<dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'}></dees-input-text>
|
|
868
|
-
<
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
${Array.from({ length: maxSourceBindingRows }, (_item, index) => html`
|
|
878
|
-
<dees-input-dropdown
|
|
879
|
-
.key=${`sourceBindingProfileRef${index}`}
|
|
880
|
-
.label=${`Binding ${index + 1}`}
|
|
881
|
-
.options=${profileOptions}
|
|
882
|
-
.selectedOption=${profileOptions[0]}
|
|
883
|
-
></dees-input-dropdown>
|
|
884
|
-
`)}
|
|
885
|
-
</div>
|
|
862
|
+
<sz-input-route-source-policy
|
|
863
|
+
.key=${'sourceBindings'}
|
|
864
|
+
.label=${'Source Policy'}
|
|
865
|
+
.infoText=${sourcePolicyInfoText}
|
|
866
|
+
.sourceProfiles=${sourceProfileOptions}
|
|
867
|
+
.pathClassOptions=${pathClassOptions}
|
|
868
|
+
.presets=${sourcePolicyPresets}
|
|
869
|
+
.value=${[]}
|
|
870
|
+
></sz-input-route-source-policy>
|
|
886
871
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
|
|
887
872
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${'localhost'}></dees-input-text>
|
|
888
873
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'}></dees-input-text>
|
|
@@ -916,6 +901,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
916
901
|
if (!form) return;
|
|
917
902
|
const formData = await form.collectFormData();
|
|
918
903
|
if (!formData.name || !formData.ports) return;
|
|
904
|
+
if (!validateSourcePolicyInput(form)) return;
|
|
919
905
|
|
|
920
906
|
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
|
921
907
|
const domains: string[] = Array.isArray(formData.domains)
|
|
@@ -923,11 +909,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
923
909
|
: [];
|
|
924
910
|
const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
|
|
925
911
|
|
|
926
|
-
const
|
|
927
|
-
const sourceBindingRefs = useGiteaTemplate
|
|
928
|
-
? []
|
|
929
|
-
: getSourceBindingRefsFromFormData(formData);
|
|
930
|
-
if (!useGiteaTemplate && !validateSourceBindingSelection(sourceBindingRefs, profiles)) return;
|
|
912
|
+
const sourceBindings = getSourceBindingsFromFormData(formData);
|
|
931
913
|
const targetKey = getDropdownKey(formData.networkTargetRef);
|
|
932
914
|
const preserveMatchPort = !targetKey && Boolean(formData.preserveMatchPort);
|
|
933
915
|
const targetPort = preserveMatchPort
|
|
@@ -992,12 +974,8 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
992
974
|
|
|
993
975
|
// Build metadata if profile/target selected
|
|
994
976
|
const metadata: any = {};
|
|
995
|
-
if (
|
|
996
|
-
const sourceBindings = getGiteaPresetSourceBindings(profiles);
|
|
997
|
-
if (!sourceBindings) return;
|
|
977
|
+
if (sourceBindings.length > 0) {
|
|
998
978
|
metadata.sourceBindings = sourceBindings;
|
|
999
|
-
} else if (sourceBindingRefs.length > 0) {
|
|
1000
|
-
metadata.sourceBindings = buildSourceBindingsMetadata(sourceBindingRefs);
|
|
1001
979
|
}
|
|
1002
980
|
if (targetKey) {
|
|
1003
981
|
metadata.networkTargetRef = targetKey;
|