@serve.zone/dcrouter 13.10.0 → 13.12.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 +1075 -967
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +2 -0
- package/dist_ts/classes.dcrouter.js +15 -1
- package/dist_ts/db/documents/classes.email-domain.doc.d.ts +17 -0
- package/dist_ts/db/documents/classes.email-domain.doc.js +124 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +3 -1
- package/dist_ts/email/classes.email-domain.manager.d.ts +46 -0
- package/dist_ts/email/classes.email-domain.manager.js +276 -0
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -0
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/email-domain.handler.d.ts +16 -0
- package/dist_ts/opsserver/handlers/email-domain.handler.js +150 -0
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts_interfaces/data/email-domain.d.ts +70 -0
- package/dist_ts_interfaces/data/email-domain.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/requests/email-domains.d.ts +142 -0
- package/dist_ts_interfaces/requests/email-domains.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_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +21 -0
- package/dist_ts_web/appstate.js +81 -1
- package/dist_ts_web/elements/email/index.d.ts +1 -0
- package/dist_ts_web/elements/email/index.js +2 -1
- package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +19 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +410 -0
- package/dist_ts_web/elements/ops-dashboard.js +3 -1
- package/dist_ts_web/router.js +2 -2
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +17 -0
- package/ts/db/documents/classes.email-domain.doc.ts +56 -0
- package/ts/db/documents/index.ts +3 -0
- package/ts/email/classes.email-domain.manager.ts +321 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/email-domain.handler.ts +195 -0
- package/ts/opsserver/handlers/index.ts +2 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +124 -0
- package/ts_web/elements/email/index.ts +1 -0
- package/ts_web/elements/email/ops-view-email-domains.ts +396 -0
- package/ts_web/elements/ops-dashboard.ts +2 -0
- package/ts_web/router.ts +1 -1
package/ts_web/appstate.ts
CHANGED
|
@@ -2377,6 +2377,130 @@ export const toggleApiTokenAction = routeManagementStatePart.createAction<{
|
|
|
2377
2377
|
}
|
|
2378
2378
|
});
|
|
2379
2379
|
|
|
2380
|
+
// ============================================================================
|
|
2381
|
+
// Email Domains State
|
|
2382
|
+
// ============================================================================
|
|
2383
|
+
|
|
2384
|
+
export interface IEmailDomainsState {
|
|
2385
|
+
domains: interfaces.data.IEmailDomain[];
|
|
2386
|
+
isLoading: boolean;
|
|
2387
|
+
lastUpdated: number;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
export const emailDomainsStatePart = await appState.getStatePart<IEmailDomainsState>(
|
|
2391
|
+
'emailDomains',
|
|
2392
|
+
{
|
|
2393
|
+
domains: [],
|
|
2394
|
+
isLoading: false,
|
|
2395
|
+
lastUpdated: 0,
|
|
2396
|
+
},
|
|
2397
|
+
'soft',
|
|
2398
|
+
);
|
|
2399
|
+
|
|
2400
|
+
export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
|
|
2401
|
+
async (statePartArg): Promise<IEmailDomainsState> => {
|
|
2402
|
+
const context = getActionContext();
|
|
2403
|
+
const currentState = statePartArg.getState()!;
|
|
2404
|
+
if (!context.identity) return currentState;
|
|
2405
|
+
|
|
2406
|
+
try {
|
|
2407
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2408
|
+
interfaces.requests.IReq_GetEmailDomains
|
|
2409
|
+
>('/typedrequest', 'getEmailDomains');
|
|
2410
|
+
const response = await request.fire({ identity: context.identity });
|
|
2411
|
+
return {
|
|
2412
|
+
...currentState,
|
|
2413
|
+
domains: response.domains,
|
|
2414
|
+
isLoading: false,
|
|
2415
|
+
lastUpdated: Date.now(),
|
|
2416
|
+
};
|
|
2417
|
+
} catch {
|
|
2418
|
+
return { ...currentState, isLoading: false };
|
|
2419
|
+
}
|
|
2420
|
+
},
|
|
2421
|
+
);
|
|
2422
|
+
|
|
2423
|
+
export const createEmailDomainAction = emailDomainsStatePart.createAction<{
|
|
2424
|
+
linkedDomainId: string;
|
|
2425
|
+
subdomain?: string;
|
|
2426
|
+
dkimSelector?: string;
|
|
2427
|
+
dkimKeySize?: number;
|
|
2428
|
+
rotateKeys?: boolean;
|
|
2429
|
+
rotationIntervalDays?: number;
|
|
2430
|
+
}>(async (statePartArg, args, actionContext) => {
|
|
2431
|
+
const context = getActionContext();
|
|
2432
|
+
const currentState = statePartArg.getState()!;
|
|
2433
|
+
try {
|
|
2434
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2435
|
+
interfaces.requests.IReq_CreateEmailDomain
|
|
2436
|
+
>('/typedrequest', 'createEmailDomain');
|
|
2437
|
+
await request.fire({ identity: context.identity!, ...args });
|
|
2438
|
+
return await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
2439
|
+
} catch {
|
|
2440
|
+
return currentState;
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
export const deleteEmailDomainAction = emailDomainsStatePart.createAction<string>(
|
|
2445
|
+
async (statePartArg, id, actionContext) => {
|
|
2446
|
+
const context = getActionContext();
|
|
2447
|
+
const currentState = statePartArg.getState()!;
|
|
2448
|
+
try {
|
|
2449
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2450
|
+
interfaces.requests.IReq_DeleteEmailDomain
|
|
2451
|
+
>('/typedrequest', 'deleteEmailDomain');
|
|
2452
|
+
await request.fire({ identity: context.identity!, id });
|
|
2453
|
+
return await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
2454
|
+
} catch {
|
|
2455
|
+
return currentState;
|
|
2456
|
+
}
|
|
2457
|
+
},
|
|
2458
|
+
);
|
|
2459
|
+
|
|
2460
|
+
export const validateEmailDomainAction = emailDomainsStatePart.createAction<string>(
|
|
2461
|
+
async (statePartArg, id, actionContext) => {
|
|
2462
|
+
const context = getActionContext();
|
|
2463
|
+
const currentState = statePartArg.getState()!;
|
|
2464
|
+
try {
|
|
2465
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2466
|
+
interfaces.requests.IReq_ValidateEmailDomain
|
|
2467
|
+
>('/typedrequest', 'validateEmailDomain');
|
|
2468
|
+
await request.fire({ identity: context.identity!, id });
|
|
2469
|
+
return await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
2470
|
+
} catch {
|
|
2471
|
+
return currentState;
|
|
2472
|
+
}
|
|
2473
|
+
},
|
|
2474
|
+
);
|
|
2475
|
+
|
|
2476
|
+
export const provisionEmailDomainDnsAction = emailDomainsStatePart.createAction<string>(
|
|
2477
|
+
async (statePartArg, id, actionContext) => {
|
|
2478
|
+
const context = getActionContext();
|
|
2479
|
+
const currentState = statePartArg.getState()!;
|
|
2480
|
+
try {
|
|
2481
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2482
|
+
interfaces.requests.IReq_ProvisionEmailDomainDns
|
|
2483
|
+
>('/typedrequest', 'provisionEmailDomainDns');
|
|
2484
|
+
await request.fire({ identity: context.identity!, id });
|
|
2485
|
+
return await actionContext!.dispatch(fetchEmailDomainsAction, null);
|
|
2486
|
+
} catch {
|
|
2487
|
+
return currentState;
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
);
|
|
2491
|
+
|
|
2492
|
+
// ============================================================================
|
|
2493
|
+
// Email Domain Standalone Functions
|
|
2494
|
+
// ============================================================================
|
|
2495
|
+
|
|
2496
|
+
export async function fetchEmailDomainDnsRecords(id: string) {
|
|
2497
|
+
const context = getActionContext();
|
|
2498
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
2499
|
+
interfaces.requests.IReq_GetEmailDomainDnsRecords
|
|
2500
|
+
>('/typedrequest', 'getEmailDomainDnsRecords');
|
|
2501
|
+
return request.fire({ identity: context.identity!, id });
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2380
2504
|
// ============================================================================
|
|
2381
2505
|
// TypedSocket Client for Real-time Log Streaming
|
|
2382
2506
|
// ============================================================================
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeesElement,
|
|
3
|
+
html,
|
|
4
|
+
customElement,
|
|
5
|
+
type TemplateResult,
|
|
6
|
+
css,
|
|
7
|
+
state,
|
|
8
|
+
cssManager,
|
|
9
|
+
} from '@design.estate/dees-element';
|
|
10
|
+
import * as appstate from '../../appstate.js';
|
|
11
|
+
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
12
|
+
import { viewHostCss } from '../shared/css.js';
|
|
13
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'ops-view-email-domains': OpsViewEmailDomains;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('ops-view-email-domains')
|
|
22
|
+
export class OpsViewEmailDomains extends DeesElement {
|
|
23
|
+
@state()
|
|
24
|
+
accessor emailDomainsState: appstate.IEmailDomainsState =
|
|
25
|
+
appstate.emailDomainsStatePart.getState()!;
|
|
26
|
+
|
|
27
|
+
@state()
|
|
28
|
+
accessor domainsState: appstate.IDomainsState = appstate.domainsStatePart.getState()!;
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
const sub = appstate.emailDomainsStatePart.select().subscribe((s) => {
|
|
33
|
+
this.emailDomainsState = s;
|
|
34
|
+
});
|
|
35
|
+
this.rxSubscriptions.push(sub);
|
|
36
|
+
const domSub = appstate.domainsStatePart.select().subscribe((s) => {
|
|
37
|
+
this.domainsState = s;
|
|
38
|
+
});
|
|
39
|
+
this.rxSubscriptions.push(domSub);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async connectedCallback() {
|
|
43
|
+
await super.connectedCallback();
|
|
44
|
+
await appstate.emailDomainsStatePart.dispatchAction(appstate.fetchEmailDomainsAction, null);
|
|
45
|
+
await appstate.domainsStatePart.dispatchAction(appstate.fetchDomainsAndProvidersAction, null);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static styles = [
|
|
49
|
+
cssManager.defaultStyles,
|
|
50
|
+
viewHostCss,
|
|
51
|
+
css`
|
|
52
|
+
.emailDomainsContainer {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 24px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.statusBadge {
|
|
59
|
+
display: inline-flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
padding: 3px 10px;
|
|
62
|
+
border-radius: 12px;
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
letter-spacing: 0.02em;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.statusBadge.valid {
|
|
70
|
+
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
|
71
|
+
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.statusBadge.missing {
|
|
75
|
+
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
|
76
|
+
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.statusBadge.invalid {
|
|
80
|
+
background: ${cssManager.bdTheme('#fff7ed', '#431407')};
|
|
81
|
+
color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.statusBadge.unchecked {
|
|
85
|
+
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
|
|
86
|
+
color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.sourceBadge {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 3px 8px;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
font-size: 11px;
|
|
95
|
+
font-weight: 500;
|
|
96
|
+
background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
|
|
97
|
+
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
public render(): TemplateResult {
|
|
103
|
+
const domains = this.emailDomainsState.domains;
|
|
104
|
+
const validCount = domains.filter(
|
|
105
|
+
(d) =>
|
|
106
|
+
d.dnsStatus.mx === 'valid' &&
|
|
107
|
+
d.dnsStatus.spf === 'valid' &&
|
|
108
|
+
d.dnsStatus.dkim === 'valid' &&
|
|
109
|
+
d.dnsStatus.dmarc === 'valid',
|
|
110
|
+
).length;
|
|
111
|
+
const issueCount = domains.length - validCount;
|
|
112
|
+
|
|
113
|
+
const tiles: IStatsTile[] = [
|
|
114
|
+
{
|
|
115
|
+
id: 'total',
|
|
116
|
+
title: 'Total Domains',
|
|
117
|
+
value: domains.length,
|
|
118
|
+
type: 'number',
|
|
119
|
+
icon: 'lucide:globe',
|
|
120
|
+
color: '#3b82f6',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'valid',
|
|
124
|
+
title: 'Valid DNS',
|
|
125
|
+
value: validCount,
|
|
126
|
+
type: 'number',
|
|
127
|
+
icon: 'lucide:Check',
|
|
128
|
+
color: '#22c55e',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'issues',
|
|
132
|
+
title: 'Issues',
|
|
133
|
+
value: issueCount,
|
|
134
|
+
type: 'number',
|
|
135
|
+
icon: 'lucide:TriangleAlert',
|
|
136
|
+
color: issueCount > 0 ? '#ef4444' : '#22c55e',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'dkim',
|
|
140
|
+
title: 'DKIM Active',
|
|
141
|
+
value: domains.filter((d) => d.dkim.publicKey).length,
|
|
142
|
+
type: 'number',
|
|
143
|
+
icon: 'lucide:KeyRound',
|
|
144
|
+
color: '#8b5cf6',
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
return html`
|
|
149
|
+
<dees-heading level="3">Email Domains</dees-heading>
|
|
150
|
+
|
|
151
|
+
<div class="emailDomainsContainer">
|
|
152
|
+
<dees-statsgrid
|
|
153
|
+
.tiles=${tiles}
|
|
154
|
+
.minTileWidth=${200}
|
|
155
|
+
.gridActions=${[
|
|
156
|
+
{
|
|
157
|
+
name: 'Refresh',
|
|
158
|
+
iconName: 'lucide:RefreshCw',
|
|
159
|
+
action: async () => {
|
|
160
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
161
|
+
appstate.fetchEmailDomainsAction,
|
|
162
|
+
null,
|
|
163
|
+
);
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
]}
|
|
167
|
+
></dees-statsgrid>
|
|
168
|
+
|
|
169
|
+
<dees-table
|
|
170
|
+
.heading1=${'Email Domains'}
|
|
171
|
+
.heading2=${'DKIM, SPF, DMARC and MX management'}
|
|
172
|
+
.data=${domains}
|
|
173
|
+
.showColumnFilters=${true}
|
|
174
|
+
.displayFunction=${(d: interfaces.data.IEmailDomain) => ({
|
|
175
|
+
Domain: d.domain,
|
|
176
|
+
Source: this.renderSourceBadge(d.linkedDomainId),
|
|
177
|
+
MX: this.renderDnsStatus(d.dnsStatus.mx),
|
|
178
|
+
SPF: this.renderDnsStatus(d.dnsStatus.spf),
|
|
179
|
+
DKIM: this.renderDnsStatus(d.dnsStatus.dkim),
|
|
180
|
+
DMARC: this.renderDnsStatus(d.dnsStatus.dmarc),
|
|
181
|
+
})}
|
|
182
|
+
.dataActions=${[
|
|
183
|
+
{
|
|
184
|
+
name: 'Add Email Domain',
|
|
185
|
+
iconName: 'lucide:plus',
|
|
186
|
+
type: ['header'] as any,
|
|
187
|
+
actionFunc: async () => {
|
|
188
|
+
await this.showCreateDialog();
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'Validate DNS',
|
|
193
|
+
iconName: 'lucide:search-check',
|
|
194
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
195
|
+
actionFunc: async (actionData: any) => {
|
|
196
|
+
const d = actionData.item as interfaces.data.IEmailDomain;
|
|
197
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
198
|
+
appstate.validateEmailDomainAction,
|
|
199
|
+
d.id,
|
|
200
|
+
);
|
|
201
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
202
|
+
DeesToast.show({ message: `DNS validated for ${d.domain}`, type: 'success', duration: 2500 });
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'Provision DNS',
|
|
207
|
+
iconName: 'lucide:wand-sparkles',
|
|
208
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
209
|
+
actionFunc: async (actionData: any) => {
|
|
210
|
+
const d = actionData.item as interfaces.data.IEmailDomain;
|
|
211
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
212
|
+
appstate.provisionEmailDomainDnsAction,
|
|
213
|
+
d.id,
|
|
214
|
+
);
|
|
215
|
+
const { DeesToast } = await import('@design.estate/dees-catalog');
|
|
216
|
+
DeesToast.show({ message: `DNS records provisioned for ${d.domain}`, type: 'success', duration: 2500 });
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'View DNS Records',
|
|
221
|
+
iconName: 'lucide:list',
|
|
222
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
223
|
+
actionFunc: async (actionData: any) => {
|
|
224
|
+
const d = actionData.item as interfaces.data.IEmailDomain;
|
|
225
|
+
await this.showDnsRecordsDialog(d);
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: 'Delete',
|
|
230
|
+
iconName: 'lucide:trash2',
|
|
231
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
232
|
+
actionFunc: async (actionData: any) => {
|
|
233
|
+
const d = actionData.item as interfaces.data.IEmailDomain;
|
|
234
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
235
|
+
appstate.deleteEmailDomainAction,
|
|
236
|
+
d.id,
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
]}
|
|
241
|
+
dataName="email domain"
|
|
242
|
+
></dees-table>
|
|
243
|
+
</div>
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private renderDnsStatus(status: interfaces.data.TDnsRecordStatus): TemplateResult {
|
|
248
|
+
return html`<span class="statusBadge ${status}">${status}</span>`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private renderSourceBadge(linkedDomainId: string): TemplateResult {
|
|
252
|
+
const domain = this.domainsState.domains.find((d) => d.id === linkedDomainId);
|
|
253
|
+
if (!domain) return html`<span class="sourceBadge">unknown</span>`;
|
|
254
|
+
const label =
|
|
255
|
+
domain.source === 'dcrouter'
|
|
256
|
+
? 'dcrouter'
|
|
257
|
+
: this.domainsState.providers.find((p) => p.id === domain.providerId)?.name || 'provider';
|
|
258
|
+
return html`<span class="sourceBadge">${label}</span>`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async showCreateDialog() {
|
|
262
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
263
|
+
const domainOptions = this.domainsState.domains.map((d) => ({
|
|
264
|
+
option: `${d.name} (${d.source})`,
|
|
265
|
+
key: d.id,
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
DeesModal.createAndShow({
|
|
269
|
+
heading: 'Add Email Domain',
|
|
270
|
+
content: html`
|
|
271
|
+
<dees-form>
|
|
272
|
+
<dees-input-dropdown
|
|
273
|
+
.key=${'linkedDomainId'}
|
|
274
|
+
.label=${'Domain'}
|
|
275
|
+
.description=${'Select an existing DNS domain'}
|
|
276
|
+
.options=${domainOptions}
|
|
277
|
+
.required=${true}
|
|
278
|
+
></dees-input-dropdown>
|
|
279
|
+
<dees-input-text
|
|
280
|
+
.key=${'subdomain'}
|
|
281
|
+
.label=${'Subdomain'}
|
|
282
|
+
.description=${'Leave empty for bare domain, e.g. "mail" for mail.example.com'}
|
|
283
|
+
></dees-input-text>
|
|
284
|
+
<dees-input-text
|
|
285
|
+
.key=${'dkimSelector'}
|
|
286
|
+
.label=${'DKIM Selector'}
|
|
287
|
+
.description=${'Identifier used in DNS record name'}
|
|
288
|
+
.value=${'default'}
|
|
289
|
+
></dees-input-text>
|
|
290
|
+
<dees-input-dropdown
|
|
291
|
+
.key=${'dkimKeySize'}
|
|
292
|
+
.label=${'DKIM Key Size'}
|
|
293
|
+
.options=${[
|
|
294
|
+
{ option: '2048 (recommended)', key: '2048' },
|
|
295
|
+
{ option: '1024', key: '1024' },
|
|
296
|
+
{ option: '4096', key: '4096' },
|
|
297
|
+
]}
|
|
298
|
+
.selectedOption=${{ option: '2048 (recommended)', key: '2048' }}
|
|
299
|
+
></dees-input-dropdown>
|
|
300
|
+
<dees-input-checkbox
|
|
301
|
+
.key=${'rotateKeys'}
|
|
302
|
+
.label=${'Auto-rotate DKIM keys'}
|
|
303
|
+
.value=${false}
|
|
304
|
+
></dees-input-checkbox>
|
|
305
|
+
</dees-form>
|
|
306
|
+
`,
|
|
307
|
+
menuOptions: [
|
|
308
|
+
{ name: 'Cancel', action: async (m: any) => m.destroy() },
|
|
309
|
+
{
|
|
310
|
+
name: 'Create',
|
|
311
|
+
action: async (m: any) => {
|
|
312
|
+
const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
313
|
+
if (!form) return;
|
|
314
|
+
const data = await form.collectFormData();
|
|
315
|
+
const linkedDomainId =
|
|
316
|
+
typeof data.linkedDomainId === 'object'
|
|
317
|
+
? data.linkedDomainId.key
|
|
318
|
+
: data.linkedDomainId;
|
|
319
|
+
const keySize =
|
|
320
|
+
typeof data.dkimKeySize === 'object'
|
|
321
|
+
? parseInt(data.dkimKeySize.key, 10)
|
|
322
|
+
: parseInt(data.dkimKeySize || '2048', 10);
|
|
323
|
+
|
|
324
|
+
const subdomain = data.subdomain?.trim() || undefined;
|
|
325
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
326
|
+
appstate.createEmailDomainAction,
|
|
327
|
+
{
|
|
328
|
+
linkedDomainId,
|
|
329
|
+
subdomain,
|
|
330
|
+
dkimSelector: data.dkimSelector || 'default',
|
|
331
|
+
dkimKeySize: keySize,
|
|
332
|
+
rotateKeys: Boolean(data.rotateKeys),
|
|
333
|
+
},
|
|
334
|
+
);
|
|
335
|
+
m.destroy();
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private async showDnsRecordsDialog(emailDomain: interfaces.data.IEmailDomain) {
|
|
343
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
344
|
+
|
|
345
|
+
// Fetch required DNS records
|
|
346
|
+
let records: interfaces.data.IEmailDnsRecord[] = [];
|
|
347
|
+
try {
|
|
348
|
+
const response = await appstate.fetchEmailDomainDnsRecords(emailDomain.id);
|
|
349
|
+
records = response.records;
|
|
350
|
+
} catch {
|
|
351
|
+
records = [];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
DeesModal.createAndShow({
|
|
355
|
+
heading: `DNS Records: ${emailDomain.domain}`,
|
|
356
|
+
content: html`
|
|
357
|
+
<dees-table
|
|
358
|
+
.data=${records}
|
|
359
|
+
.displayFunction=${(r: interfaces.data.IEmailDnsRecord) => ({
|
|
360
|
+
Type: r.type,
|
|
361
|
+
Name: r.name,
|
|
362
|
+
Value: r.value,
|
|
363
|
+
Status: html`<span class="statusBadge ${r.status}">${r.status}</span>`,
|
|
364
|
+
})}
|
|
365
|
+
.dataActions=${[
|
|
366
|
+
{
|
|
367
|
+
name: 'Copy Value',
|
|
368
|
+
iconName: 'lucide:copy',
|
|
369
|
+
type: ['inRow'] as any,
|
|
370
|
+
actionFunc: async (actionData: any) => {
|
|
371
|
+
const rec = actionData.item as interfaces.data.IEmailDnsRecord;
|
|
372
|
+
await navigator.clipboard.writeText(rec.value);
|
|
373
|
+
DeesToast.show({ message: 'Copied to clipboard', type: 'success', duration: 1500 });
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
]}
|
|
377
|
+
dataName="DNS record"
|
|
378
|
+
></dees-table>
|
|
379
|
+
`,
|
|
380
|
+
menuOptions: [
|
|
381
|
+
{
|
|
382
|
+
name: 'Auto-Provision All',
|
|
383
|
+
action: async (m: any) => {
|
|
384
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
385
|
+
appstate.provisionEmailDomainDnsAction,
|
|
386
|
+
emailDomain.id,
|
|
387
|
+
);
|
|
388
|
+
DeesToast.show({ message: 'DNS records provisioned', type: 'success', duration: 2500 });
|
|
389
|
+
m.destroy();
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{ name: 'Close', action: async (m: any) => m.destroy() },
|
|
393
|
+
],
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -32,6 +32,7 @@ import { OpsViewVpn } from './network/ops-view-vpn.js';
|
|
|
32
32
|
// Email group
|
|
33
33
|
import { OpsViewEmails } from './email/ops-view-emails.js';
|
|
34
34
|
import { OpsViewEmailSecurity } from './email/ops-view-email-security.js';
|
|
35
|
+
import { OpsViewEmailDomains } from './email/ops-view-email-domains.js';
|
|
35
36
|
|
|
36
37
|
// Access group
|
|
37
38
|
import { OpsViewApiTokens } from './access/ops-view-apitokens.js';
|
|
@@ -108,6 +109,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
108
109
|
subViews: [
|
|
109
110
|
{ slug: 'log', name: 'Email Log', iconName: 'lucide:scrollText', element: OpsViewEmails },
|
|
110
111
|
{ slug: 'security', name: 'Email Security', iconName: 'lucide:shieldCheck', element: OpsViewEmailSecurity },
|
|
112
|
+
{ slug: 'domains', name: 'Email Domains', iconName: 'lucide:globe', element: OpsViewEmailDomains },
|
|
111
113
|
],
|
|
112
114
|
},
|
|
113
115
|
{
|
package/ts_web/router.ts
CHANGED
|
@@ -10,7 +10,7 @@ const flatViews = ['logs'] as const;
|
|
|
10
10
|
const subviewMap: Record<string, readonly string[]> = {
|
|
11
11
|
overview: ['stats', 'configuration'] as const,
|
|
12
12
|
network: ['activity', 'routes', 'sourceprofiles', 'networktargets', 'targetprofiles', 'remoteingress', 'vpn'] as const,
|
|
13
|
-
email: ['log', 'security'] as const,
|
|
13
|
+
email: ['log', 'security', 'domains'] as const,
|
|
14
14
|
access: ['apitokens', 'users'] as const,
|
|
15
15
|
security: ['overview', 'blocked', 'authentication'] as const,
|
|
16
16
|
domains: ['providers', 'domains', 'dns', 'certificates'] as const,
|