@serve.zone/dcrouter 16.3.0 → 16.4.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 +997 -971
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/network/ops-view-redirects.d.ts +1 -0
- package/dist_ts_web/elements/network/ops-view-redirects.js +25 -19
- package/dist_ts_web/elements/network/ops-view-routes.d.ts +2 -0
- package/dist_ts_web/elements/network/ops-view-routes.js +204 -80
- package/dist_ts_web/elements/network/ops-view-special-forwards.d.ts +2 -1
- package/dist_ts_web/elements/network/ops-view-special-forwards.js +92 -170
- package/dist_ts_web/elements/network/special-forward-dialog.d.ts +11 -0
- package/dist_ts_web/elements/network/special-forward-dialog.js +154 -0
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/network/ops-view-redirects.ts +25 -18
- package/ts_web/elements/network/ops-view-routes.ts +222 -81
- package/ts_web/elements/network/ops-view-special-forwards.ts +90 -181
- package/ts_web/elements/network/special-forward-dialog.ts +174 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
state,
|
|
9
9
|
type TemplateResult,
|
|
10
10
|
} from '@design.estate/dees-element';
|
|
11
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
11
12
|
import * as appstate from '../../appstate.js';
|
|
12
13
|
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
13
14
|
import { viewHostCss } from '../shared/css.js';
|
|
@@ -16,36 +17,18 @@ import {
|
|
|
16
17
|
routeMatchesOwner,
|
|
17
18
|
type TRouteOwnerFilter,
|
|
18
19
|
} from './route-view-filters.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
: value && typeof value === 'object' && 'key' in value
|
|
26
|
-
? String((value as { key: unknown }).key)
|
|
27
|
-
: '';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getDomains(route: interfaces.data.IMergedRoute): string[] {
|
|
31
|
-
const domains = route.route.match.domains;
|
|
32
|
-
if (!domains) return [];
|
|
33
|
-
return Array.isArray(domains) ? domains : [domains];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getTarget(route: interfaces.data.IMergedRoute): { host: string; port: number } | undefined {
|
|
37
|
-
const target = route.route.action.targets?.[0];
|
|
38
|
-
if (!target || typeof target.port !== 'number') return undefined;
|
|
39
|
-
const host = typeof target.host === 'string'
|
|
40
|
-
? target.host
|
|
41
|
-
: Array.isArray(target.host)
|
|
42
|
-
? target.host[0]
|
|
43
|
-
: undefined;
|
|
44
|
-
return host ? { host, port: target.port } : undefined;
|
|
45
|
-
}
|
|
20
|
+
import {
|
|
21
|
+
getSpecialForwardDomains,
|
|
22
|
+
getSpecialForwardTarget,
|
|
23
|
+
letsEncryptHttp01ManagedRouteKind,
|
|
24
|
+
showLetsEncryptHttp01ForwardDialog,
|
|
25
|
+
} from './special-forward-dialog.js';
|
|
46
26
|
|
|
47
27
|
@customElement('ops-view-special-forwards')
|
|
48
28
|
export class OpsViewSpecialForwards extends DeesElement {
|
|
29
|
+
@property({ type: Boolean, reflect: true })
|
|
30
|
+
accessor embedded = false;
|
|
31
|
+
|
|
49
32
|
@property({ type: String })
|
|
50
33
|
accessor ownerFilter: TRouteOwnerFilter = 'all';
|
|
51
34
|
|
|
@@ -76,6 +59,12 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
76
59
|
display: block;
|
|
77
60
|
}
|
|
78
61
|
|
|
62
|
+
:host([embedded]) {
|
|
63
|
+
margin: 0;
|
|
64
|
+
max-width: none;
|
|
65
|
+
padding: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
79
68
|
.specialForwardsContainer {
|
|
80
69
|
display: flex;
|
|
81
70
|
flex-direction: column;
|
|
@@ -106,12 +95,62 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
106
95
|
|
|
107
96
|
public render(): TemplateResult {
|
|
108
97
|
const forwards = this.routeState.mergedRoutes.filter((route) => {
|
|
109
|
-
return route.metadata?.managedRouteKind ===
|
|
98
|
+
return route.metadata?.managedRouteKind === letsEncryptHttp01ManagedRouteKind
|
|
110
99
|
&& routeMatchesOwner(route, this.ownerFilter);
|
|
111
100
|
});
|
|
101
|
+
const enabledCount = forwards.filter((route) => route.enabled).length;
|
|
102
|
+
const disabledCount = forwards.length - enabledCount;
|
|
103
|
+
const readOnlyCount = forwards.filter((route) => getRouteOwner(route) !== 'operator').length;
|
|
104
|
+
const statsTiles: IStatsTile[] = [
|
|
105
|
+
{
|
|
106
|
+
id: 'matchingSpecialForwards',
|
|
107
|
+
title: 'Matching',
|
|
108
|
+
type: 'number',
|
|
109
|
+
value: forwards.length,
|
|
110
|
+
icon: 'lucide:route',
|
|
111
|
+
description: 'HTTP-01 forwards in this owner scope',
|
|
112
|
+
color: '#3b82f6',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'enabledSpecialForwards',
|
|
116
|
+
title: 'Enabled',
|
|
117
|
+
type: 'number',
|
|
118
|
+
value: enabledCount,
|
|
119
|
+
icon: 'lucide:circleCheck',
|
|
120
|
+
description: 'Currently forwarding challenges',
|
|
121
|
+
color: '#22c55e',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'disabledSpecialForwards',
|
|
125
|
+
title: 'Disabled',
|
|
126
|
+
type: 'number',
|
|
127
|
+
value: disabledCount,
|
|
128
|
+
icon: 'lucide:circlePause',
|
|
129
|
+
description: 'Configured but inactive',
|
|
130
|
+
color: disabledCount > 0 ? '#f59e0b' : '#6b7280',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'readOnlySpecialForwards',
|
|
134
|
+
title: 'Read-only',
|
|
135
|
+
type: 'number',
|
|
136
|
+
value: readOnlyCount,
|
|
137
|
+
icon: 'lucide:lockKeyhole',
|
|
138
|
+
description: 'Managed by gateway clients or system',
|
|
139
|
+
color: '#8b5cf6',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
112
142
|
|
|
113
143
|
return html`
|
|
144
|
+
${this.embedded ? '' : html`<dees-heading level="3">Special Forwards</dees-heading>`}
|
|
114
145
|
<div class="specialForwardsContainer">
|
|
146
|
+
${this.embedded
|
|
147
|
+
? ''
|
|
148
|
+
: html`
|
|
149
|
+
<dees-statsgrid
|
|
150
|
+
.tiles=${statsTiles}
|
|
151
|
+
.gridActions=${this.getHeaderActions()}
|
|
152
|
+
></dees-statsgrid>
|
|
153
|
+
`}
|
|
115
154
|
<div class="explanation">
|
|
116
155
|
<strong>Let's Encrypt HTTP-01 Forward</strong> exposes only
|
|
117
156
|
<code>/.well-known/acme-challenge/*</code> on public port 80. DCRouter manages the
|
|
@@ -128,7 +167,7 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
128
167
|
.data=${forwards}
|
|
129
168
|
.showColumnFilters=${true}
|
|
130
169
|
.displayFunction=${(route: interfaces.data.IMergedRoute) => {
|
|
131
|
-
const target =
|
|
170
|
+
const target = getSpecialForwardTarget(route);
|
|
132
171
|
const owner = getRouteOwner(route);
|
|
133
172
|
return {
|
|
134
173
|
Status: route.enabled ? 'Enabled' : 'Disabled',
|
|
@@ -139,7 +178,7 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
139
178
|
? 'Gateway Client'
|
|
140
179
|
: 'System',
|
|
141
180
|
Name: route.route.name || route.id,
|
|
142
|
-
Domains:
|
|
181
|
+
Domains: getSpecialForwardDomains(route).join(', '),
|
|
143
182
|
Path: route.route.match.path || '-',
|
|
144
183
|
Target: route.metadata?.networkTargetName
|
|
145
184
|
|| (target ? `${target.host}:${target.port}` : '-'),
|
|
@@ -147,22 +186,6 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
147
186
|
};
|
|
148
187
|
}}
|
|
149
188
|
.dataActions=${[
|
|
150
|
-
...(this.ownerFilter === 'all' || this.ownerFilter === 'operator'
|
|
151
|
-
? [
|
|
152
|
-
{
|
|
153
|
-
name: 'Add HTTP-01 Forward',
|
|
154
|
-
iconName: 'lucide:plus',
|
|
155
|
-
type: ['header' as const],
|
|
156
|
-
actionFunc: async () => this.showForwardDialog(),
|
|
157
|
-
},
|
|
158
|
-
]
|
|
159
|
-
: []),
|
|
160
|
-
{
|
|
161
|
-
name: 'Refresh',
|
|
162
|
-
iconName: 'lucide:refreshCw',
|
|
163
|
-
type: ['header' as const],
|
|
164
|
-
actionFunc: async () => this.refreshData(),
|
|
165
|
-
},
|
|
166
189
|
{
|
|
167
190
|
name: 'Edit',
|
|
168
191
|
iconName: 'lucide:pencil',
|
|
@@ -171,7 +194,10 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
171
194
|
return getRouteOwner(route) === 'operator';
|
|
172
195
|
},
|
|
173
196
|
actionFunc: async (actionData: any) => {
|
|
174
|
-
await
|
|
197
|
+
await showLetsEncryptHttp01ForwardDialog({
|
|
198
|
+
existing: actionData.item as interfaces.data.IMergedRoute,
|
|
199
|
+
targets: this.profilesTargetsState.targets,
|
|
200
|
+
});
|
|
175
201
|
},
|
|
176
202
|
},
|
|
177
203
|
{
|
|
@@ -216,147 +242,30 @@ export class OpsViewSpecialForwards extends DeesElement {
|
|
|
216
242
|
]);
|
|
217
243
|
}
|
|
218
244
|
|
|
219
|
-
private
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
245
|
+
private getHeaderActions() {
|
|
246
|
+
return [
|
|
247
|
+
...(this.ownerFilter === 'all' || this.ownerFilter === 'operator'
|
|
248
|
+
? [{
|
|
249
|
+
name: 'Add User Special Forward',
|
|
250
|
+
iconName: 'lucide:plus',
|
|
251
|
+
action: async () => showLetsEncryptHttp01ForwardDialog({
|
|
252
|
+
targets: this.profilesTargetsState.targets,
|
|
253
|
+
}),
|
|
254
|
+
}]
|
|
255
|
+
: []),
|
|
256
|
+
{
|
|
257
|
+
name: 'Refresh',
|
|
258
|
+
iconName: 'lucide:refreshCw',
|
|
259
|
+
action: async () => this.refreshData(),
|
|
260
|
+
},
|
|
230
261
|
];
|
|
231
|
-
const selectedTarget = targetOptions.find((option) => {
|
|
232
|
-
return option.key === (existing?.metadata?.networkTargetRef || '');
|
|
233
|
-
}) || targetOptions[0];
|
|
234
|
-
|
|
235
|
-
await DeesModal.createAndShow({
|
|
236
|
-
heading: existing
|
|
237
|
-
? `Edit HTTP-01 Forward: ${existing.route.name || existing.id}`
|
|
238
|
-
: 'Add Let\'s Encrypt HTTP-01 Forward',
|
|
239
|
-
content: html`
|
|
240
|
-
<dees-form>
|
|
241
|
-
<dees-input-text
|
|
242
|
-
.key=${'name'}
|
|
243
|
-
.label=${'Name'}
|
|
244
|
-
.value=${existing?.route.name || ''}
|
|
245
|
-
.required=${true}
|
|
246
|
-
></dees-input-text>
|
|
247
|
-
<dees-input-list
|
|
248
|
-
.key=${'domains'}
|
|
249
|
-
.label=${'Domains'}
|
|
250
|
-
.description=${'Exact public hostnames; HTTP-01 does not support wildcards'}
|
|
251
|
-
.placeholder=${'Add domain...'}
|
|
252
|
-
.value=${existing ? getDomains(existing) : []}
|
|
253
|
-
></dees-input-list>
|
|
254
|
-
<dees-input-dropdown
|
|
255
|
-
.key=${'networkTargetRef'}
|
|
256
|
-
.label=${'Network Target'}
|
|
257
|
-
.options=${targetOptions}
|
|
258
|
-
.selectedOption=${selectedTarget}
|
|
259
|
-
></dees-input-dropdown>
|
|
260
|
-
<dees-input-text
|
|
261
|
-
.key=${'targetHost'}
|
|
262
|
-
.label=${'Inline Target Host'}
|
|
263
|
-
.description=${'Used only when no Network Target is selected'}
|
|
264
|
-
.value=${target?.host || ''}
|
|
265
|
-
></dees-input-text>
|
|
266
|
-
<dees-input-text
|
|
267
|
-
.key=${'targetPort'}
|
|
268
|
-
.label=${'Inline Target Port'}
|
|
269
|
-
.description=${'Usually port 80 on the ACME responder'}
|
|
270
|
-
.value=${target ? String(target.port) : '80'}
|
|
271
|
-
></dees-input-text>
|
|
272
|
-
<dees-input-checkbox
|
|
273
|
-
.key=${'remoteIngressEnabled'}
|
|
274
|
-
.label=${'Enable Remote Ingress'}
|
|
275
|
-
.value=${existing?.route.remoteIngress?.enabled === true}
|
|
276
|
-
></dees-input-checkbox>
|
|
277
|
-
<dees-input-list
|
|
278
|
-
.key=${'remoteIngressEdgeFilter'}
|
|
279
|
-
.label=${'Remote Ingress Edge Filter'}
|
|
280
|
-
.description=${'Optional edge IDs or tags; leave empty for all edges'}
|
|
281
|
-
.placeholder=${'Add edge ID or tag...'}
|
|
282
|
-
.value=${existing?.route.remoteIngress?.edgeFilter || []}
|
|
283
|
-
></dees-input-list>
|
|
284
|
-
</dees-form>
|
|
285
|
-
`,
|
|
286
|
-
menuOptions: [
|
|
287
|
-
{
|
|
288
|
-
name: 'Cancel',
|
|
289
|
-
iconName: 'lucide:x',
|
|
290
|
-
action: async (modalArg: any) => modalArg.destroy(),
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
name: existing ? 'Save' : 'Create',
|
|
294
|
-
iconName: existing ? 'lucide:check' : 'lucide:plus',
|
|
295
|
-
action: async (modalArg: any) => {
|
|
296
|
-
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
297
|
-
if (!form) return;
|
|
298
|
-
const data = await form.collectFormData();
|
|
299
|
-
const name = String(data.name || '').trim();
|
|
300
|
-
const domains = Array.isArray(data.domains)
|
|
301
|
-
? data.domains.map((domain: unknown) => String(domain).trim()).filter(Boolean)
|
|
302
|
-
: [];
|
|
303
|
-
const networkTargetRef = getDropdownKey(data.networkTargetRef).trim();
|
|
304
|
-
const targetHost = String(data.targetHost || '').trim();
|
|
305
|
-
const targetPort = Number.parseInt(String(data.targetPort || ''), 10);
|
|
306
|
-
if (!name || domains.length === 0) {
|
|
307
|
-
alert('Name and at least one domain are required.');
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (
|
|
311
|
-
!networkTargetRef
|
|
312
|
-
&& (!targetHost || !Number.isInteger(targetPort) || targetPort < 1 || targetPort > 65535)
|
|
313
|
-
) {
|
|
314
|
-
alert('A valid inline target host and port are required.');
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const edgeFilter = Array.isArray(data.remoteIngressEdgeFilter)
|
|
319
|
-
? data.remoteIngressEdgeFilter.map((entry: unknown) => String(entry).trim()).filter(Boolean)
|
|
320
|
-
: [];
|
|
321
|
-
const config: interfaces.data.ILetsEncryptHttp01ForwardConfig = {
|
|
322
|
-
name,
|
|
323
|
-
domains,
|
|
324
|
-
...(networkTargetRef
|
|
325
|
-
? { networkTargetRef }
|
|
326
|
-
: { target: { host: targetHost, port: targetPort } }),
|
|
327
|
-
...(data.remoteIngressEnabled
|
|
328
|
-
? {
|
|
329
|
-
remoteIngress: {
|
|
330
|
-
enabled: true,
|
|
331
|
-
...(edgeFilter.length ? { edgeFilter } : {}),
|
|
332
|
-
},
|
|
333
|
-
}
|
|
334
|
-
: {}),
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
if (existing) {
|
|
338
|
-
await appstate.routeManagementStatePart.dispatchAction(
|
|
339
|
-
appstate.updateLetsEncryptHttp01ForwardAction,
|
|
340
|
-
{ id: existing.id, config, enabled: existing.enabled },
|
|
341
|
-
);
|
|
342
|
-
} else {
|
|
343
|
-
await appstate.routeManagementStatePart.dispatchAction(
|
|
344
|
-
appstate.createLetsEncryptHttp01ForwardAction,
|
|
345
|
-
{ config },
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
await modalArg.destroy();
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
});
|
|
353
262
|
}
|
|
354
263
|
|
|
355
264
|
private async confirmDelete(route: interfaces.data.IMergedRoute): Promise<void> {
|
|
356
265
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
357
266
|
await DeesModal.createAndShow({
|
|
358
267
|
heading: `Delete Special Forward: ${route.route.name || route.id}`,
|
|
359
|
-
content: html`<p>This removes the managed HTTP-01 route for ${
|
|
268
|
+
content: html`<p>This removes the managed HTTP-01 route for ${getSpecialForwardDomains(route).join(', ')}.</p>`,
|
|
360
269
|
menuOptions: [
|
|
361
270
|
{
|
|
362
271
|
name: 'Cancel',
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { html } from '@design.estate/dees-element';
|
|
2
|
+
import * as appstate from '../../appstate.js';
|
|
3
|
+
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
4
|
+
|
|
5
|
+
export const letsEncryptHttp01ManagedRouteKind = 'letsencrypt-http01-forward' as const;
|
|
6
|
+
|
|
7
|
+
export function getSpecialForwardDomains(
|
|
8
|
+
route: interfaces.data.IMergedRoute,
|
|
9
|
+
): string[] {
|
|
10
|
+
const domains = route.route.match.domains;
|
|
11
|
+
if (!domains) return [];
|
|
12
|
+
return Array.isArray(domains) ? domains : [domains];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getSpecialForwardTarget(
|
|
16
|
+
route: interfaces.data.IMergedRoute,
|
|
17
|
+
): { host: string; port: number } | undefined {
|
|
18
|
+
const target = route.route.action.targets?.[0];
|
|
19
|
+
if (!target || typeof target.port !== 'number') return undefined;
|
|
20
|
+
const host = typeof target.host === 'string'
|
|
21
|
+
? target.host
|
|
22
|
+
: Array.isArray(target.host)
|
|
23
|
+
? target.host[0]
|
|
24
|
+
: undefined;
|
|
25
|
+
return host ? { host, port: target.port } : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getDropdownKey(value: unknown): string {
|
|
29
|
+
return typeof value === 'string'
|
|
30
|
+
? value
|
|
31
|
+
: value && typeof value === 'object' && 'key' in value
|
|
32
|
+
? String((value as { key: unknown }).key)
|
|
33
|
+
: '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function showLetsEncryptHttp01ForwardDialog(options: {
|
|
37
|
+
existing?: interfaces.data.IMergedRoute;
|
|
38
|
+
targets: interfaces.data.INetworkTarget[];
|
|
39
|
+
}): Promise<void> {
|
|
40
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
41
|
+
const { existing, targets } = options;
|
|
42
|
+
const target = existing ? getSpecialForwardTarget(existing) : undefined;
|
|
43
|
+
const targetOptions = [
|
|
44
|
+
{ key: '', option: '(none — inline target)' },
|
|
45
|
+
...targets.map((networkTarget) => ({
|
|
46
|
+
key: networkTarget.id,
|
|
47
|
+
option: `${networkTarget.name} (${
|
|
48
|
+
Array.isArray(networkTarget.host) ? networkTarget.host.join(', ') : networkTarget.host
|
|
49
|
+
}:${networkTarget.port})`,
|
|
50
|
+
})),
|
|
51
|
+
];
|
|
52
|
+
const selectedTarget = targetOptions.find((option) => {
|
|
53
|
+
return option.key === (existing?.metadata?.networkTargetRef || '');
|
|
54
|
+
}) || targetOptions[0];
|
|
55
|
+
|
|
56
|
+
await DeesModal.createAndShow({
|
|
57
|
+
heading: existing
|
|
58
|
+
? `Edit User HTTP-01 Forward: ${existing.route.name || existing.id}`
|
|
59
|
+
: 'Add User Let\'s Encrypt HTTP-01 Forward',
|
|
60
|
+
content: html`
|
|
61
|
+
<dees-form>
|
|
62
|
+
<dees-input-text
|
|
63
|
+
.key=${'name'}
|
|
64
|
+
.label=${'Name'}
|
|
65
|
+
.value=${existing?.route.name || ''}
|
|
66
|
+
.required=${true}
|
|
67
|
+
></dees-input-text>
|
|
68
|
+
<dees-input-list
|
|
69
|
+
.key=${'domains'}
|
|
70
|
+
.label=${'Domains'}
|
|
71
|
+
.description=${'Exact public hostnames; HTTP-01 does not support wildcards'}
|
|
72
|
+
.placeholder=${'Add domain...'}
|
|
73
|
+
.value=${existing ? getSpecialForwardDomains(existing) : []}
|
|
74
|
+
></dees-input-list>
|
|
75
|
+
<dees-input-dropdown
|
|
76
|
+
.key=${'networkTargetRef'}
|
|
77
|
+
.label=${'Network Target'}
|
|
78
|
+
.options=${targetOptions}
|
|
79
|
+
.selectedOption=${selectedTarget}
|
|
80
|
+
></dees-input-dropdown>
|
|
81
|
+
<dees-input-text
|
|
82
|
+
.key=${'targetHost'}
|
|
83
|
+
.label=${'Inline Target Host'}
|
|
84
|
+
.description=${'Used only when no Network Target is selected'}
|
|
85
|
+
.value=${target?.host || ''}
|
|
86
|
+
></dees-input-text>
|
|
87
|
+
<dees-input-text
|
|
88
|
+
.key=${'targetPort'}
|
|
89
|
+
.label=${'Inline Target Port'}
|
|
90
|
+
.description=${'Usually port 80 on the ACME responder'}
|
|
91
|
+
.value=${target ? String(target.port) : '80'}
|
|
92
|
+
></dees-input-text>
|
|
93
|
+
<dees-input-checkbox
|
|
94
|
+
.key=${'remoteIngressEnabled'}
|
|
95
|
+
.label=${'Enable Remote Ingress'}
|
|
96
|
+
.value=${existing?.route.remoteIngress?.enabled === true}
|
|
97
|
+
></dees-input-checkbox>
|
|
98
|
+
<dees-input-list
|
|
99
|
+
.key=${'remoteIngressEdgeFilter'}
|
|
100
|
+
.label=${'Remote Ingress Edge Filter'}
|
|
101
|
+
.description=${'Optional edge IDs or tags; leave empty for all edges'}
|
|
102
|
+
.placeholder=${'Add edge ID or tag...'}
|
|
103
|
+
.value=${existing?.route.remoteIngress?.edgeFilter || []}
|
|
104
|
+
></dees-input-list>
|
|
105
|
+
</dees-form>
|
|
106
|
+
`,
|
|
107
|
+
menuOptions: [
|
|
108
|
+
{
|
|
109
|
+
name: 'Cancel',
|
|
110
|
+
iconName: 'lucide:x',
|
|
111
|
+
action: async (modalArg: any) => modalArg.destroy(),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: existing ? 'Save' : 'Create',
|
|
115
|
+
iconName: existing ? 'lucide:check' : 'lucide:plus',
|
|
116
|
+
action: async (modalArg: any) => {
|
|
117
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
118
|
+
if (!form) return;
|
|
119
|
+
const data = await form.collectFormData();
|
|
120
|
+
const name = String(data.name || '').trim();
|
|
121
|
+
const domains = Array.isArray(data.domains)
|
|
122
|
+
? data.domains.map((domain: unknown) => String(domain).trim()).filter(Boolean)
|
|
123
|
+
: [];
|
|
124
|
+
const networkTargetRef = getDropdownKey(data.networkTargetRef).trim();
|
|
125
|
+
const targetHost = String(data.targetHost || '').trim();
|
|
126
|
+
const targetPort = Number.parseInt(String(data.targetPort || ''), 10);
|
|
127
|
+
if (!name || domains.length === 0) {
|
|
128
|
+
alert('Name and at least one domain are required.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (
|
|
132
|
+
!networkTargetRef
|
|
133
|
+
&& (!targetHost || !Number.isInteger(targetPort) || targetPort < 1 || targetPort > 65535)
|
|
134
|
+
) {
|
|
135
|
+
alert('A valid inline target host and port are required.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const edgeFilter = Array.isArray(data.remoteIngressEdgeFilter)
|
|
140
|
+
? data.remoteIngressEdgeFilter.map((entry: unknown) => String(entry).trim()).filter(Boolean)
|
|
141
|
+
: [];
|
|
142
|
+
const config: interfaces.data.ILetsEncryptHttp01ForwardConfig = {
|
|
143
|
+
name,
|
|
144
|
+
domains,
|
|
145
|
+
...(networkTargetRef
|
|
146
|
+
? { networkTargetRef }
|
|
147
|
+
: { target: { host: targetHost, port: targetPort } }),
|
|
148
|
+
...(data.remoteIngressEnabled
|
|
149
|
+
? {
|
|
150
|
+
remoteIngress: {
|
|
151
|
+
enabled: true,
|
|
152
|
+
...(edgeFilter.length ? { edgeFilter } : {}),
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
: {}),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (existing) {
|
|
159
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
160
|
+
appstate.updateLetsEncryptHttp01ForwardAction,
|
|
161
|
+
{ id: existing.id, config, enabled: existing.enabled },
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
165
|
+
appstate.createLetsEncryptHttp01ForwardAction,
|
|
166
|
+
{ config },
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
await modalArg.destroy();
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
}
|