@serve.zone/dcrouter 13.43.5 → 13.44.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 +491 -436
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +16 -13
- package/dist_ts/classes.dcrouter.js +320 -82
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
- package/dist_ts/config/classes.route-config-manager.js +4 -1
- package/dist_ts/db/documents/classes.email-server-settings.doc.d.ts +14 -0
- package/dist_ts/db/documents/classes.email-server-settings.doc.js +103 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.d.ts +3 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.js +20 -2
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/email/classes.email-domain.manager.d.ts +3 -1
- package/dist_ts/email/classes.email-domain.manager.js +6 -3
- package/dist_ts/email/classes.email-settings.manager.d.ts +25 -0
- package/dist_ts/email/classes.email-settings.manager.js +184 -0
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/config.handler.js +17 -14
- package/dist_ts/opsserver/handlers/email-settings.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/email-settings.handler.js +57 -0
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +24 -6
- package/dist_ts/opsserver/handlers/workhoster.handler.js +2 -2
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -6
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +58 -19
- package/dist_ts_interfaces/data/email-settings.d.ts +39 -0
- package/dist_ts_interfaces/data/email-settings.js +2 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/remoteingress.d.ts +7 -0
- package/dist_ts_interfaces/requests/email-settings.d.ts +26 -0
- package/dist_ts_interfaces/requests/email-settings.js +2 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/remoteingress.d.ts +4 -1
- package/dist_ts_migrations/index.d.ts +14 -1
- package/dist_ts_migrations/index.js +107 -2
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +6 -1
- package/dist_ts_web/appstate.js +23 -1
- package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +4 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +122 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.js +57 -17
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +361 -100
- package/ts/config/classes.route-config-manager.ts +4 -0
- package/ts/db/documents/classes.email-server-settings.doc.ts +40 -0
- package/ts/db/documents/classes.remote-ingress-hub-settings.doc.ts +9 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/email/classes.email-domain.manager.ts +6 -2
- package/ts/email/classes.email-settings.manager.ts +221 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/config.handler.ts +16 -13
- package/ts/opsserver/handlers/email-settings.handler.ts +72 -0
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +25 -5
- package/ts/opsserver/handlers/workhoster.handler.ts +1 -1
- package/ts/remoteingress/classes.remoteingress-manager.ts +65 -18
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +33 -1
- package/ts_web/elements/email/ops-view-email-domains.ts +126 -0
- package/ts_web/elements/network/ops-view-remoteingress.ts +59 -17
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import type { IDcRouterRouteConfig, IRemoteIngress, IRemoteIngressHubSettings, IRemoteIngressPerformanceConfig, TRemoteIngressPerformanceProfile } from '../../ts_interfaces/data/remoteingress.js';
|
|
2
|
+
import type { IDcRouterRouteConfig, IRemoteIngress, IRemoteIngressHubSettings, IRemoteIngressPerformanceConfig, TRemoteIngressHubSettingsUpdate, TRemoteIngressPerformanceProfile } from '../../ts_interfaces/data/remoteingress.js';
|
|
3
3
|
import { RemoteIngressEdgeDoc, RemoteIngressHubSettingsDoc } from '../db/index.js';
|
|
4
4
|
|
|
5
5
|
interface IRemoteIngressFirewallConfig {
|
|
@@ -30,6 +30,11 @@ const performanceIntegerMaxByField: Record<TPerformanceIntegerField, number> = {
|
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const maxServerFirstPorts = 128;
|
|
33
|
+
const defaultTunnelPort = 8443;
|
|
34
|
+
|
|
35
|
+
function hasOwn(objectArg: object, keyArg: string): boolean {
|
|
36
|
+
return Object.prototype.hasOwnProperty.call(objectArg, keyArg);
|
|
37
|
+
}
|
|
33
38
|
|
|
34
39
|
function extractPorts(portRange: plugins.smartproxy.IRouteConfig['match']['ports']): number[] {
|
|
35
40
|
const ports = new Set<number>(plugins.smartproxy.expandPortRange(portRange) as number[]);
|
|
@@ -46,12 +51,13 @@ export class RemoteIngressManager {
|
|
|
46
51
|
private routes: IDcRouterRouteConfig[] = [];
|
|
47
52
|
private firewallConfig?: IRemoteIngressFirewallConfig;
|
|
48
53
|
private hubSettings: IRemoteIngressHubSettings = {
|
|
54
|
+
enabled: false,
|
|
55
|
+
tunnelPort: defaultTunnelPort,
|
|
49
56
|
updatedAt: 0,
|
|
50
57
|
updatedBy: 'default',
|
|
51
58
|
};
|
|
52
59
|
|
|
53
|
-
constructor(
|
|
54
|
-
}
|
|
60
|
+
constructor() {}
|
|
55
61
|
|
|
56
62
|
/**
|
|
57
63
|
* Load all edge registrations from the database into memory.
|
|
@@ -86,21 +92,17 @@ export class RemoteIngressManager {
|
|
|
86
92
|
private async initializeHubSettings(): Promise<void> {
|
|
87
93
|
let doc = await RemoteIngressHubSettingsDoc.load();
|
|
88
94
|
if (!doc) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
95
|
+
doc = new RemoteIngressHubSettingsDoc();
|
|
96
|
+
doc.settingsId = 'remote-ingress-hub-settings';
|
|
97
|
+
doc.enabled = false;
|
|
98
|
+
doc.tunnelPort = defaultTunnelPort;
|
|
99
|
+
doc.hubDomain = '';
|
|
100
|
+
doc.updatedAt = Date.now();
|
|
101
|
+
doc.updatedBy = 'default';
|
|
102
|
+
await doc.save();
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
this.hubSettings =
|
|
101
|
-
updatedAt: 0,
|
|
102
|
-
updatedBy: 'default',
|
|
103
|
-
};
|
|
105
|
+
this.hubSettings = this.toHubSettings(doc);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
/**
|
|
@@ -131,16 +133,30 @@ export class RemoteIngressManager {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
public async updateHubSettings(
|
|
134
|
-
updates:
|
|
136
|
+
updates: TRemoteIngressHubSettingsUpdate,
|
|
135
137
|
updatedBy: string,
|
|
136
138
|
): Promise<IRemoteIngressHubSettings> {
|
|
137
139
|
let doc = await RemoteIngressHubSettingsDoc.load();
|
|
138
140
|
if (!doc) {
|
|
139
141
|
doc = new RemoteIngressHubSettingsDoc();
|
|
140
142
|
doc.settingsId = 'remote-ingress-hub-settings';
|
|
143
|
+
doc.enabled = false;
|
|
144
|
+
doc.tunnelPort = defaultTunnelPort;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
const normalized = this.normalizeHubSettingsUpdate(updates);
|
|
148
|
+
if (hasOwn(normalized, 'enabled')) {
|
|
149
|
+
doc.enabled = normalized.enabled;
|
|
150
|
+
}
|
|
151
|
+
if (hasOwn(normalized, 'tunnelPort')) {
|
|
152
|
+
doc.tunnelPort = normalized.tunnelPort;
|
|
153
|
+
}
|
|
154
|
+
if (hasOwn(updates, 'hubDomain')) {
|
|
155
|
+
doc.hubDomain = normalized.hubDomain || '';
|
|
156
|
+
}
|
|
157
|
+
if (hasOwn(updates, 'performance')) {
|
|
158
|
+
doc.performance = normalized.performance || undefined;
|
|
159
|
+
}
|
|
144
160
|
doc.updatedAt = Date.now();
|
|
145
161
|
doc.updatedBy = updatedBy;
|
|
146
162
|
await doc.save();
|
|
@@ -408,6 +424,34 @@ export class RemoteIngressManager {
|
|
|
408
424
|
return result;
|
|
409
425
|
}
|
|
410
426
|
|
|
427
|
+
private normalizeHubSettingsUpdate(
|
|
428
|
+
updates: TRemoteIngressHubSettingsUpdate,
|
|
429
|
+
): TRemoteIngressHubSettingsUpdate {
|
|
430
|
+
const next: TRemoteIngressHubSettingsUpdate = {};
|
|
431
|
+
|
|
432
|
+
if (hasOwn(updates, 'enabled') && updates.enabled !== undefined) {
|
|
433
|
+
next.enabled = Boolean(updates.enabled);
|
|
434
|
+
}
|
|
435
|
+
if (hasOwn(updates, 'tunnelPort') && updates.tunnelPort !== undefined) {
|
|
436
|
+
const tunnelPort = Number(updates.tunnelPort);
|
|
437
|
+
if (!Number.isInteger(tunnelPort) || tunnelPort < 1 || tunnelPort > 65535) {
|
|
438
|
+
throw new Error('tunnelPort must be a valid TCP port');
|
|
439
|
+
}
|
|
440
|
+
next.tunnelPort = tunnelPort;
|
|
441
|
+
}
|
|
442
|
+
if (hasOwn(updates, 'hubDomain')) {
|
|
443
|
+
const hubDomain = `${updates.hubDomain || ''}`.trim();
|
|
444
|
+
next.hubDomain = hubDomain || undefined;
|
|
445
|
+
}
|
|
446
|
+
if (hasOwn(updates, 'performance')) {
|
|
447
|
+
next.performance = updates.performance === null
|
|
448
|
+
? undefined
|
|
449
|
+
: this.normalizePerformanceConfig(updates.performance || undefined);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return next;
|
|
453
|
+
}
|
|
454
|
+
|
|
411
455
|
private normalizePerformanceConfig(
|
|
412
456
|
performance?: IRemoteIngressPerformanceConfig,
|
|
413
457
|
): IRemoteIngressPerformanceConfig | undefined {
|
|
@@ -488,6 +532,9 @@ export class RemoteIngressManager {
|
|
|
488
532
|
|
|
489
533
|
private toHubSettings(doc: RemoteIngressHubSettingsDoc): IRemoteIngressHubSettings {
|
|
490
534
|
return {
|
|
535
|
+
enabled: doc.enabled ?? false,
|
|
536
|
+
tunnelPort: doc.tunnelPort ?? defaultTunnelPort,
|
|
537
|
+
hubDomain: doc.hubDomain || undefined,
|
|
491
538
|
performance: doc.performance,
|
|
492
539
|
updatedAt: doc.updatedAt,
|
|
493
540
|
updatedBy: doc.updatedBy,
|
package/ts_web/appstate.ts
CHANGED
|
@@ -1230,7 +1230,10 @@ export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
|
|
|
1230
1230
|
});
|
|
1231
1231
|
|
|
1232
1232
|
export const updateRemoteIngressHubSettingsAction = remoteIngressStatePart.createAction<{
|
|
1233
|
-
|
|
1233
|
+
enabled?: boolean;
|
|
1234
|
+
tunnelPort?: number;
|
|
1235
|
+
hubDomain?: string | null;
|
|
1236
|
+
performance?: interfaces.data.IRemoteIngressPerformanceConfig | null;
|
|
1234
1237
|
}>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
|
|
1235
1238
|
const context = getActionContext();
|
|
1236
1239
|
const currentState = statePartArg.getState()!;
|
|
@@ -1242,6 +1245,9 @@ export const updateRemoteIngressHubSettingsAction = remoteIngressStatePart.creat
|
|
|
1242
1245
|
|
|
1243
1246
|
const response = await request.fire({
|
|
1244
1247
|
identity: context.identity!,
|
|
1248
|
+
enabled: dataArg.enabled,
|
|
1249
|
+
tunnelPort: dataArg.tunnelPort,
|
|
1250
|
+
hubDomain: dataArg.hubDomain,
|
|
1245
1251
|
performance: dataArg.performance,
|
|
1246
1252
|
});
|
|
1247
1253
|
|
|
@@ -2956,6 +2962,7 @@ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
|
|
|
2956
2962
|
|
|
2957
2963
|
export interface IEmailDomainsState {
|
|
2958
2964
|
domains: interfaces.data.IEmailDomain[];
|
|
2965
|
+
settings: interfaces.data.IEmailServerSettings | null;
|
|
2959
2966
|
isLoading: boolean;
|
|
2960
2967
|
lastUpdated: number;
|
|
2961
2968
|
}
|
|
@@ -2964,6 +2971,7 @@ export const emailDomainsStatePart = await appState.getStatePart<IEmailDomainsSt
|
|
|
2964
2971
|
'emailDomains',
|
|
2965
2972
|
{
|
|
2966
2973
|
domains: [],
|
|
2974
|
+
settings: null,
|
|
2967
2975
|
isLoading: false,
|
|
2968
2976
|
lastUpdated: 0,
|
|
2969
2977
|
},
|
|
@@ -2980,10 +2988,15 @@ export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
|
|
|
2980
2988
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2981
2989
|
interfaces.requests.IReq_GetEmailDomains
|
|
2982
2990
|
>('/typedrequest', 'getEmailDomains');
|
|
2991
|
+
const settingsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2992
|
+
interfaces.requests.IReq_GetEmailServerSettings
|
|
2993
|
+
>('/typedrequest', 'getEmailServerSettings');
|
|
2983
2994
|
const response = await request.fire({ identity: context.identity });
|
|
2995
|
+
const settingsResponse = await settingsRequest.fire({ identity: context.identity });
|
|
2984
2996
|
return {
|
|
2985
2997
|
...currentState,
|
|
2986
2998
|
domains: response.domains,
|
|
2999
|
+
settings: settingsResponse.settings,
|
|
2987
3000
|
isLoading: false,
|
|
2988
3001
|
lastUpdated: Date.now(),
|
|
2989
3002
|
};
|
|
@@ -3014,6 +3027,25 @@ export const createEmailDomainAction = emailDomainsStatePart.createAction<{
|
|
|
3014
3027
|
}
|
|
3015
3028
|
});
|
|
3016
3029
|
|
|
3030
|
+
export const updateEmailServerSettingsAction = emailDomainsStatePart.createAction<
|
|
3031
|
+
interfaces.data.TEmailServerSettingsUpdate
|
|
3032
|
+
>(async (statePartArg, settings, actionContext) => {
|
|
3033
|
+
const context = getActionContext();
|
|
3034
|
+
const currentState = statePartArg.getState()!;
|
|
3035
|
+
try {
|
|
3036
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
3037
|
+
interfaces.requests.IReq_UpdateEmailServerSettings
|
|
3038
|
+
>('/typedrequest', 'updateEmailServerSettings');
|
|
3039
|
+
const response = await request.fire({ identity: context.identity!, settings });
|
|
3040
|
+
if (!response.success) {
|
|
3041
|
+
return currentState;
|
|
3042
|
+
}
|
|
3043
|
+
return await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
3044
|
+
} catch {
|
|
3045
|
+
return currentState;
|
|
3046
|
+
}
|
|
3047
|
+
});
|
|
3048
|
+
|
|
3017
3049
|
export const deleteEmailDomainAction = emailDomainsStatePart.createAction<string>(
|
|
3018
3050
|
async (statePartArg, id, actionContext) => {
|
|
3019
3051
|
const context = getActionContext();
|
|
@@ -101,6 +101,7 @@ export class OpsViewEmailDomains extends DeesElement {
|
|
|
101
101
|
|
|
102
102
|
public render(): TemplateResult {
|
|
103
103
|
const domains = this.emailDomainsState.domains;
|
|
104
|
+
const settings = this.emailDomainsState.settings;
|
|
104
105
|
const validCount = domains.filter(
|
|
105
106
|
(d) =>
|
|
106
107
|
d.dnsStatus.mx === 'valid' &&
|
|
@@ -127,6 +128,22 @@ export class OpsViewEmailDomains extends DeesElement {
|
|
|
127
128
|
icon: 'lucide:Check',
|
|
128
129
|
color: '#22c55e',
|
|
129
130
|
},
|
|
131
|
+
{
|
|
132
|
+
id: 'server',
|
|
133
|
+
title: 'Server',
|
|
134
|
+
value: settings?.enabled ? 'enabled' : 'disabled',
|
|
135
|
+
type: 'text',
|
|
136
|
+
icon: 'lucide:mail-check',
|
|
137
|
+
color: settings?.enabled ? '#22c55e' : '#6b7280',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'ports',
|
|
141
|
+
title: 'SMTP Ports',
|
|
142
|
+
value: settings?.ports?.join(', ') || 'none',
|
|
143
|
+
type: 'text',
|
|
144
|
+
icon: 'lucide:plug',
|
|
145
|
+
color: '#0ea5e9',
|
|
146
|
+
},
|
|
130
147
|
{
|
|
131
148
|
id: 'issues',
|
|
132
149
|
title: 'Issues',
|
|
@@ -163,6 +180,13 @@ export class OpsViewEmailDomains extends DeesElement {
|
|
|
163
180
|
);
|
|
164
181
|
},
|
|
165
182
|
},
|
|
183
|
+
{
|
|
184
|
+
name: 'Settings',
|
|
185
|
+
iconName: 'lucide:settings',
|
|
186
|
+
action: async () => {
|
|
187
|
+
await this.showSettingsDialog();
|
|
188
|
+
},
|
|
189
|
+
},
|
|
166
190
|
]}
|
|
167
191
|
></dees-statsgrid>
|
|
168
192
|
|
|
@@ -258,6 +282,108 @@ export class OpsViewEmailDomains extends DeesElement {
|
|
|
258
282
|
return html`<span class="sourceBadge">${label}</span>`;
|
|
259
283
|
}
|
|
260
284
|
|
|
285
|
+
private parsePortList(value: string): number[] {
|
|
286
|
+
return value
|
|
287
|
+
.split(',')
|
|
288
|
+
.map((part) => Number.parseInt(part.trim(), 10))
|
|
289
|
+
.filter((port) => Number.isInteger(port));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private parsePortMapping(value: string): Record<number, number> | null {
|
|
293
|
+
const trimmed = value.trim();
|
|
294
|
+
if (!trimmed) return null;
|
|
295
|
+
const mapping: Record<number, number> = {};
|
|
296
|
+
for (const pair of trimmed.split(',')) {
|
|
297
|
+
const [externalPort, internalPort] = pair
|
|
298
|
+
.split(':')
|
|
299
|
+
.map((part) => Number.parseInt(part.trim(), 10));
|
|
300
|
+
if (Number.isInteger(externalPort) && Number.isInteger(internalPort)) {
|
|
301
|
+
mapping[externalPort] = internalPort;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return Object.keys(mapping).length > 0 ? mapping : null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private formatPortMapping(mapping: Record<number, number> | null | undefined): string {
|
|
308
|
+
if (!mapping) return '';
|
|
309
|
+
return Object.entries(mapping)
|
|
310
|
+
.map(([externalPort, internalPort]) => `${externalPort}:${internalPort}`)
|
|
311
|
+
.join(', ');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private async showSettingsDialog() {
|
|
315
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
316
|
+
const settings = this.emailDomainsState.settings;
|
|
317
|
+
|
|
318
|
+
DeesModal.createAndShow({
|
|
319
|
+
heading: 'Email Server Settings',
|
|
320
|
+
content: html`
|
|
321
|
+
<dees-form>
|
|
322
|
+
<dees-input-checkbox
|
|
323
|
+
.key=${'enabled'}
|
|
324
|
+
.label=${'Enable email server'}
|
|
325
|
+
.value=${settings?.enabled ?? false}
|
|
326
|
+
></dees-input-checkbox>
|
|
327
|
+
<dees-input-text
|
|
328
|
+
.key=${'hostname'}
|
|
329
|
+
.label=${'SMTP hostname'}
|
|
330
|
+
.description=${'Public hostname used in SMTP banners and DNS records'}
|
|
331
|
+
.value=${settings?.hostname || ''}
|
|
332
|
+
></dees-input-text>
|
|
333
|
+
<dees-input-text
|
|
334
|
+
.key=${'ports'}
|
|
335
|
+
.label=${'Public ports'}
|
|
336
|
+
.description=${'Comma-separated SMTP ingress ports, e.g. 25, 587, 465'}
|
|
337
|
+
.value=${settings?.ports?.join(', ') || '25, 587, 465'}
|
|
338
|
+
></dees-input-text>
|
|
339
|
+
<dees-input-text
|
|
340
|
+
.key=${'portMapping'}
|
|
341
|
+
.label=${'Port mapping'}
|
|
342
|
+
.description=${'Optional external:internal pairs, e.g. 25:10025, 587:10587'}
|
|
343
|
+
.value=${this.formatPortMapping(settings?.portMapping)}
|
|
344
|
+
></dees-input-text>
|
|
345
|
+
<dees-input-text
|
|
346
|
+
.key=${'maxMessageSize'}
|
|
347
|
+
.label=${'Max message size'}
|
|
348
|
+
.description=${'Bytes; leave empty for smartmta default'}
|
|
349
|
+
.value=${settings?.maxMessageSize ? String(settings.maxMessageSize) : ''}
|
|
350
|
+
></dees-input-text>
|
|
351
|
+
<dees-input-text
|
|
352
|
+
.key=${'receivedEmailsPath'}
|
|
353
|
+
.label=${'Received emails path'}
|
|
354
|
+
.description=${'Optional storage path for received email artifacts'}
|
|
355
|
+
.value=${settings?.receivedEmailsPath || ''}
|
|
356
|
+
></dees-input-text>
|
|
357
|
+
</dees-form>
|
|
358
|
+
`,
|
|
359
|
+
menuOptions: [
|
|
360
|
+
{ name: 'Cancel', action: async (m: any) => m.destroy() },
|
|
361
|
+
{
|
|
362
|
+
name: 'Save',
|
|
363
|
+
action: async (m: any) => {
|
|
364
|
+
const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
365
|
+
if (!form) return;
|
|
366
|
+
const data = await form.collectFormData();
|
|
367
|
+
const maxMessageSizeRaw = String(data.maxMessageSize || '').trim();
|
|
368
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
369
|
+
appstate.updateEmailServerSettingsAction,
|
|
370
|
+
{
|
|
371
|
+
enabled: Boolean(data.enabled),
|
|
372
|
+
hostname: String(data.hostname || '').trim() || null,
|
|
373
|
+
ports: this.parsePortList(String(data.ports || '')),
|
|
374
|
+
portMapping: this.parsePortMapping(String(data.portMapping || '')),
|
|
375
|
+
maxMessageSize: maxMessageSizeRaw ? Number.parseInt(maxMessageSizeRaw, 10) : null,
|
|
376
|
+
receivedEmailsPath: String(data.receivedEmailsPath || '').trim() || null,
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
DeesToast.show({ message: 'Email settings saved', type: 'success', duration: 2500 });
|
|
380
|
+
m.destroy();
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
261
387
|
private async showCreateDialog() {
|
|
262
388
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
263
389
|
const domainOptions = this.domainsState.domains.map((d) => ({
|
|
@@ -620,16 +620,34 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
620
620
|
|
|
621
621
|
private async showHubSettingsDialog(): Promise<void> {
|
|
622
622
|
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
623
|
-
const
|
|
623
|
+
const hubSettings = this.riState.hubSettings;
|
|
624
|
+
const performance = hubSettings?.performance || {};
|
|
624
625
|
const selectedProfile = performanceProfileOptions.find((option) => option.key === (performance.profile || '')) || performanceProfileOptions[0];
|
|
625
|
-
const updatedAt =
|
|
626
|
-
? new Date(
|
|
626
|
+
const updatedAt = hubSettings?.updatedAt
|
|
627
|
+
? new Date(hubSettings.updatedAt).toLocaleString()
|
|
627
628
|
: 'not persisted yet';
|
|
628
629
|
|
|
629
630
|
await DeesModal.createAndShow({
|
|
630
631
|
heading: 'RemoteIngress Hub Settings',
|
|
631
632
|
content: html`
|
|
632
633
|
<dees-form>
|
|
634
|
+
<dees-input-checkbox
|
|
635
|
+
.key=${'enabled'}
|
|
636
|
+
.label=${'Enable RemoteIngress Hub'}
|
|
637
|
+
.value=${hubSettings?.enabled ?? false}
|
|
638
|
+
></dees-input-checkbox>
|
|
639
|
+
<dees-input-text
|
|
640
|
+
.key=${'tunnelPort'}
|
|
641
|
+
.label=${'Tunnel Port'}
|
|
642
|
+
.description=${'TCP/UDP port edges connect to on the hub.'}
|
|
643
|
+
.value=${(hubSettings?.tunnelPort || 8443).toString()}
|
|
644
|
+
></dees-input-text>
|
|
645
|
+
<dees-input-text
|
|
646
|
+
.key=${'hubDomain'}
|
|
647
|
+
.label=${'Hub Domain / Address'}
|
|
648
|
+
.description=${'Public host or IP embedded in edge connection tokens.'}
|
|
649
|
+
.value=${hubSettings?.hubDomain || ''}
|
|
650
|
+
></dees-input-text>
|
|
633
651
|
<dees-input-dropdown
|
|
634
652
|
.key=${'profile'}
|
|
635
653
|
.label=${'Performance Profile'}
|
|
@@ -662,8 +680,8 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
662
680
|
></dees-input-text>
|
|
663
681
|
</dees-form>
|
|
664
682
|
<p class="settingsNote">
|
|
665
|
-
Saving
|
|
666
|
-
Last updated: ${updatedAt} by ${
|
|
683
|
+
Saving applies DB-backed hub settings. Enabling or disabling the hub restarts SmartProxy so tunneled traffic and route metadata stay consistent.
|
|
684
|
+
Last updated: ${updatedAt} by ${hubSettings?.updatedBy || 'default'}.
|
|
667
685
|
</p>
|
|
668
686
|
`,
|
|
669
687
|
menuOptions: [
|
|
@@ -679,9 +697,11 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
679
697
|
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
680
698
|
if (!form) return;
|
|
681
699
|
const formData = await form.collectFormData();
|
|
682
|
-
let performanceSettings: interfaces.data.IRemoteIngressPerformanceConfig |
|
|
700
|
+
let performanceSettings: interfaces.data.IRemoteIngressPerformanceConfig | null;
|
|
701
|
+
let tunnelPort: number;
|
|
683
702
|
try {
|
|
684
|
-
|
|
703
|
+
tunnelPort = this.parseRequiredPort(formData.tunnelPort, 'Tunnel Port');
|
|
704
|
+
performanceSettings = this.collectHubPerformanceSettings(formData, performance);
|
|
685
705
|
} catch (err: unknown) {
|
|
686
706
|
DeesToast.show({ message: (err as Error).message, type: 'error', duration: 4000 });
|
|
687
707
|
return;
|
|
@@ -689,7 +709,12 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
689
709
|
|
|
690
710
|
const nextState = await appstate.remoteIngressStatePart.dispatchAction(
|
|
691
711
|
appstate.updateRemoteIngressHubSettingsAction,
|
|
692
|
-
{
|
|
712
|
+
{
|
|
713
|
+
enabled: formData.enabled !== false,
|
|
714
|
+
tunnelPort,
|
|
715
|
+
hubDomain: `${formData.hubDomain || ''}`.trim() || null,
|
|
716
|
+
performance: performanceSettings,
|
|
717
|
+
},
|
|
693
718
|
);
|
|
694
719
|
if (nextState.error) {
|
|
695
720
|
DeesToast.show({ message: nextState.error, type: 'error', duration: 4000 });
|
|
@@ -703,29 +728,37 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
703
728
|
});
|
|
704
729
|
}
|
|
705
730
|
|
|
706
|
-
private collectHubPerformanceSettings(
|
|
707
|
-
|
|
731
|
+
private collectHubPerformanceSettings(
|
|
732
|
+
formData: Record<string, any>,
|
|
733
|
+
currentPerformance: interfaces.data.IRemoteIngressPerformanceConfig,
|
|
734
|
+
): interfaces.data.IRemoteIngressPerformanceConfig | null {
|
|
735
|
+
const next: interfaces.data.IRemoteIngressPerformanceConfig = { ...currentPerformance };
|
|
708
736
|
const profile = getDropdownKey(formData.profile) as interfaces.data.TRemoteIngressPerformanceProfile | '';
|
|
709
737
|
if (profile) {
|
|
710
738
|
next.profile = profile;
|
|
739
|
+
} else {
|
|
740
|
+
delete next.profile;
|
|
711
741
|
}
|
|
712
742
|
|
|
713
|
-
this.
|
|
714
|
-
this.
|
|
715
|
-
this.
|
|
743
|
+
this.assignOptionalPositiveIntegerSetting(next, 'maxStreamsPerEdge', formData.maxStreamsPerEdge, 'Max Connections / Edge');
|
|
744
|
+
this.assignOptionalPositiveIntegerSetting(next, 'clientWriteTimeoutMs', formData.clientWriteTimeoutMs, 'Client Write Timeout');
|
|
745
|
+
this.assignOptionalPositiveIntegerSetting(next, 'firstDataConnectTimeoutMs', formData.firstDataConnectTimeoutMs, 'First Data Timeout');
|
|
716
746
|
|
|
717
|
-
const
|
|
718
|
-
if (
|
|
747
|
+
const serverFirstPortsText = `${formData.serverFirstPorts || ''}`.trim();
|
|
748
|
+
if (serverFirstPortsText) {
|
|
749
|
+
const serverFirstPorts = this.parsePortList(serverFirstPortsText, 'Server-first Ports');
|
|
719
750
|
if (serverFirstPorts.includes(443)) {
|
|
720
751
|
throw new Error('Port 443 is client-first TLS and must not be listed as server-first');
|
|
721
752
|
}
|
|
722
753
|
next.serverFirstPorts = serverFirstPorts;
|
|
754
|
+
} else {
|
|
755
|
+
delete next.serverFirstPorts;
|
|
723
756
|
}
|
|
724
757
|
|
|
725
|
-
return Object.keys(next).length > 0 ? next :
|
|
758
|
+
return Object.keys(next).length > 0 ? next : null;
|
|
726
759
|
}
|
|
727
760
|
|
|
728
|
-
private
|
|
761
|
+
private assignOptionalPositiveIntegerSetting(
|
|
729
762
|
target: interfaces.data.IRemoteIngressPerformanceConfig,
|
|
730
763
|
key: 'maxStreamsPerEdge' | 'clientWriteTimeoutMs' | 'firstDataConnectTimeoutMs',
|
|
731
764
|
value: any,
|
|
@@ -733,6 +766,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
733
766
|
): void {
|
|
734
767
|
const text = `${value || ''}`.trim();
|
|
735
768
|
if (!text) {
|
|
769
|
+
delete target[key];
|
|
736
770
|
return;
|
|
737
771
|
}
|
|
738
772
|
const parsed = Number.parseInt(text, 10);
|
|
@@ -755,4 +789,12 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
755
789
|
}
|
|
756
790
|
return [...new Set(ports)].sort((a, b) => a - b);
|
|
757
791
|
}
|
|
792
|
+
|
|
793
|
+
private parseRequiredPort(value: any, label: string): number {
|
|
794
|
+
const port = Number.parseInt(`${value || ''}`.trim(), 10);
|
|
795
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
796
|
+
throw new Error(`${label} must be a valid port number`);
|
|
797
|
+
}
|
|
798
|
+
return port;
|
|
799
|
+
}
|
|
758
800
|
}
|