@serve.zone/dcrouter 13.9.2 → 13.11.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 +1306 -1180
- package/dist_ts/00_commitinfo_data.js +2 -2
- 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 +16 -0
- package/dist_ts/db/documents/classes.email-domain.doc.js +118 -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 +45 -0
- package/dist_ts/email/classes.email-domain.manager.js +272 -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 +149 -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 +68 -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 +140 -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 +2 -2
- package/dist_ts_web/appstate.d.ts +20 -0
- package/dist_ts_web/appstate.js +81 -1
- package/dist_ts_web/elements/domains/ops-view-certificates.js +17 -96
- 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 +403 -0
- package/dist_ts_web/elements/email/ops-view-email-security.d.ts +1 -1
- package/dist_ts_web/elements/email/ops-view-email-security.js +39 -58
- 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 +53 -0
- package/ts/db/documents/index.ts +3 -0
- package/ts/email/classes.email-domain.manager.ts +316 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/email-domain.handler.ts +194 -0
- package/ts/opsserver/handlers/index.ts +2 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +123 -0
- package/ts_web/elements/domains/ops-view-certificates.ts +16 -95
- package/ts_web/elements/email/index.ts +1 -0
- package/ts_web/elements/email/ops-view-email-domains.ts +389 -0
- package/ts_web/elements/email/ops-view-email-security.ts +38 -57
- package/ts_web/elements/ops-dashboard.ts +2 -0
- package/ts_web/router.ts +1 -1
|
@@ -0,0 +1,389 @@
|
|
|
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=${'dkimSelector'}
|
|
281
|
+
.label=${'DKIM Selector'}
|
|
282
|
+
.description=${'Identifier used in DNS record name'}
|
|
283
|
+
.value=${'default'}
|
|
284
|
+
></dees-input-text>
|
|
285
|
+
<dees-input-dropdown
|
|
286
|
+
.key=${'dkimKeySize'}
|
|
287
|
+
.label=${'DKIM Key Size'}
|
|
288
|
+
.options=${[
|
|
289
|
+
{ option: '2048 (recommended)', key: '2048' },
|
|
290
|
+
{ option: '1024', key: '1024' },
|
|
291
|
+
{ option: '4096', key: '4096' },
|
|
292
|
+
]}
|
|
293
|
+
.selectedOption=${{ option: '2048 (recommended)', key: '2048' }}
|
|
294
|
+
></dees-input-dropdown>
|
|
295
|
+
<dees-input-checkbox
|
|
296
|
+
.key=${'rotateKeys'}
|
|
297
|
+
.label=${'Auto-rotate DKIM keys'}
|
|
298
|
+
.value=${false}
|
|
299
|
+
></dees-input-checkbox>
|
|
300
|
+
</dees-form>
|
|
301
|
+
`,
|
|
302
|
+
menuOptions: [
|
|
303
|
+
{ name: 'Cancel', action: async (m: any) => m.destroy() },
|
|
304
|
+
{
|
|
305
|
+
name: 'Create',
|
|
306
|
+
action: async (m: any) => {
|
|
307
|
+
const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
308
|
+
if (!form) return;
|
|
309
|
+
const data = await form.collectFormData();
|
|
310
|
+
const linkedDomainId =
|
|
311
|
+
typeof data.linkedDomainId === 'object'
|
|
312
|
+
? data.linkedDomainId.key
|
|
313
|
+
: data.linkedDomainId;
|
|
314
|
+
const keySize =
|
|
315
|
+
typeof data.dkimKeySize === 'object'
|
|
316
|
+
? parseInt(data.dkimKeySize.key, 10)
|
|
317
|
+
: parseInt(data.dkimKeySize || '2048', 10);
|
|
318
|
+
|
|
319
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
320
|
+
appstate.createEmailDomainAction,
|
|
321
|
+
{
|
|
322
|
+
linkedDomainId,
|
|
323
|
+
dkimSelector: data.dkimSelector || 'default',
|
|
324
|
+
dkimKeySize: keySize,
|
|
325
|
+
rotateKeys: Boolean(data.rotateKeys),
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
m.destroy();
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private async showDnsRecordsDialog(emailDomain: interfaces.data.IEmailDomain) {
|
|
336
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
337
|
+
|
|
338
|
+
// Fetch required DNS records
|
|
339
|
+
let records: interfaces.data.IEmailDnsRecord[] = [];
|
|
340
|
+
try {
|
|
341
|
+
const response = await appstate.fetchEmailDomainDnsRecords(emailDomain.id);
|
|
342
|
+
records = response.records;
|
|
343
|
+
} catch {
|
|
344
|
+
records = [];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
DeesModal.createAndShow({
|
|
348
|
+
heading: `DNS Records: ${emailDomain.domain}`,
|
|
349
|
+
content: html`
|
|
350
|
+
<dees-table
|
|
351
|
+
.data=${records}
|
|
352
|
+
.displayFunction=${(r: interfaces.data.IEmailDnsRecord) => ({
|
|
353
|
+
Type: r.type,
|
|
354
|
+
Name: r.name,
|
|
355
|
+
Value: r.value,
|
|
356
|
+
Status: html`<span class="statusBadge ${r.status}">${r.status}</span>`,
|
|
357
|
+
})}
|
|
358
|
+
.dataActions=${[
|
|
359
|
+
{
|
|
360
|
+
name: 'Copy Value',
|
|
361
|
+
iconName: 'lucide:copy',
|
|
362
|
+
type: ['inRow'] as any,
|
|
363
|
+
actionFunc: async (actionData: any) => {
|
|
364
|
+
const rec = actionData.item as interfaces.data.IEmailDnsRecord;
|
|
365
|
+
await navigator.clipboard.writeText(rec.value);
|
|
366
|
+
DeesToast.show({ message: 'Copied to clipboard', type: 'success', duration: 1500 });
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
]}
|
|
370
|
+
dataName="DNS record"
|
|
371
|
+
></dees-table>
|
|
372
|
+
`,
|
|
373
|
+
menuOptions: [
|
|
374
|
+
{
|
|
375
|
+
name: 'Auto-Provision All',
|
|
376
|
+
action: async (m: any) => {
|
|
377
|
+
await appstate.emailDomainsStatePart.dispatchAction(
|
|
378
|
+
appstate.provisionEmailDomainDnsAction,
|
|
379
|
+
emailDomain.id,
|
|
380
|
+
);
|
|
381
|
+
DeesToast.show({ message: 'DNS records provisioned', type: 'success', duration: 2500 });
|
|
382
|
+
m.destroy();
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{ name: 'Close', action: async (m: any) => m.destroy() },
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -37,25 +37,10 @@ export class OpsViewEmailSecurity extends DeesElement {
|
|
|
37
37
|
cssManager.defaultStyles,
|
|
38
38
|
viewHostCss,
|
|
39
39
|
css`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
45
|
-
}
|
|
46
|
-
dees-statsgrid {
|
|
47
|
-
margin-bottom: 32px;
|
|
48
|
-
}
|
|
49
|
-
.securityCard {
|
|
50
|
-
background: ${cssManager.bdTheme('#fff', '#222')};
|
|
51
|
-
border: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
52
|
-
border-radius: 8px;
|
|
53
|
-
padding: 24px;
|
|
54
|
-
position: relative;
|
|
55
|
-
overflow: hidden;
|
|
56
|
-
}
|
|
57
|
-
.actionButton {
|
|
58
|
-
margin-top: 16px;
|
|
40
|
+
.securityContainer {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 24px;
|
|
59
44
|
}
|
|
60
45
|
`,
|
|
61
46
|
];
|
|
@@ -113,48 +98,44 @@ export class OpsViewEmailSecurity extends DeesElement {
|
|
|
113
98
|
return html`
|
|
114
99
|
<dees-heading level="3">Email Security</dees-heading>
|
|
115
100
|
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
101
|
+
<div class="securityContainer">
|
|
102
|
+
<dees-statsgrid
|
|
103
|
+
.tiles=${tiles}
|
|
104
|
+
.minTileWidth=${200}
|
|
105
|
+
></dees-statsgrid>
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.label=${'Enable DKIM validation'}
|
|
132
|
-
.value=${true}
|
|
133
|
-
></dees-input-checkbox>
|
|
134
|
-
<dees-input-checkbox
|
|
135
|
-
.key=${'enableDMARC'}
|
|
136
|
-
.label=${'Enable DMARC policy enforcement'}
|
|
137
|
-
.value=${true}
|
|
138
|
-
></dees-input-checkbox>
|
|
139
|
-
<dees-input-checkbox
|
|
140
|
-
.key=${'enableSpamFilter'}
|
|
141
|
-
.label=${'Enable spam filtering'}
|
|
142
|
-
.value=${true}
|
|
143
|
-
></dees-input-checkbox>
|
|
144
|
-
</dees-form>
|
|
145
|
-
<dees-button
|
|
146
|
-
class="actionButton"
|
|
147
|
-
type="highlighted"
|
|
148
|
-
@click=${() => this.saveEmailSecuritySettings()}
|
|
149
|
-
>
|
|
150
|
-
Save Settings
|
|
151
|
-
</dees-button>
|
|
107
|
+
<dees-settings
|
|
108
|
+
.heading=${'Security Configuration'}
|
|
109
|
+
.settingsFields=${[
|
|
110
|
+
{ key: 'spf', label: 'SPF checking', value: 'enabled' },
|
|
111
|
+
{ key: 'dkim', label: 'DKIM validation', value: 'enabled' },
|
|
112
|
+
{ key: 'dmarc', label: 'DMARC policy', value: 'enabled' },
|
|
113
|
+
{ key: 'spam', label: 'Spam filtering', value: 'enabled' },
|
|
114
|
+
]}
|
|
115
|
+
.actions=${[{ name: 'Edit', action: () => this.showEditSecurityDialog() }]}
|
|
116
|
+
></dees-settings>
|
|
152
117
|
</div>
|
|
153
118
|
`;
|
|
154
119
|
}
|
|
155
120
|
|
|
156
|
-
private async
|
|
157
|
-
|
|
158
|
-
|
|
121
|
+
private async showEditSecurityDialog() {
|
|
122
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
123
|
+
DeesModal.createAndShow({
|
|
124
|
+
heading: 'Edit Security Configuration',
|
|
125
|
+
content: html`
|
|
126
|
+
<dees-form>
|
|
127
|
+
<dees-input-checkbox .key=${'enableSPF'} .label=${'SPF checking'} .value=${true}></dees-input-checkbox>
|
|
128
|
+
<dees-input-checkbox .key=${'enableDKIM'} .label=${'DKIM validation'} .value=${true}></dees-input-checkbox>
|
|
129
|
+
<dees-input-checkbox .key=${'enableDMARC'} .label=${'DMARC policy enforcement'} .value=${true}></dees-input-checkbox>
|
|
130
|
+
<dees-input-checkbox .key=${'enableSpamFilter'} .label=${'Spam filtering'} .value=${true}></dees-input-checkbox>
|
|
131
|
+
</dees-form>
|
|
132
|
+
<p style="margin-top: 12px; font-size: 12px; opacity: 0.7;">
|
|
133
|
+
These settings are read-only for now. Update the dcrouter configuration to change them.
|
|
134
|
+
</p>
|
|
135
|
+
`,
|
|
136
|
+
menuOptions: [
|
|
137
|
+
{ name: 'Close', action: async (modalArg: any) => modalArg.destroy() },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
159
140
|
}
|
|
160
141
|
}
|
|
@@ -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,
|