@serve.zone/dcrouter 7.4.3 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +10752 -3469
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +22 -2
- package/dist_ts/opsserver/handlers/email-ops.handler.js +166 -159
- package/dist_ts_interfaces/requests/email-ops.d.ts +51 -108
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +2 -16
- package/dist_ts_web/appstate.js +7 -176
- package/dist_ts_web/elements/ops-view-emails.d.ts +8 -31
- package/dist_ts_web/elements/ops-view-emails.js +54 -769
- package/dist_ts_web/elements/ops-view-logs.d.ts +2 -8
- package/dist_ts_web/elements/ops-view-logs.js +4 -101
- package/dist_ts_web/plugins.d.ts +2 -1
- package/dist_ts_web/plugins.js +4 -2
- package/dist_ts_web/router.d.ts +0 -6
- package/dist_ts_web/router.js +7 -81
- package/package.json +2 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/opsserver/handlers/email-ops.handler.ts +177 -225
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +8 -221
- package/ts_web/elements/ops-view-emails.ts +40 -749
- package/ts_web/elements/ops-view-logs.ts +2 -87
- package/ts_web/plugins.ts +4 -0
- package/ts_web/router.ts +6 -81
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
|
|
2
|
+
import * as plugins from '../plugins.js';
|
|
2
3
|
import * as appstate from '../appstate.js';
|
|
3
4
|
import * as shared from './shared/index.js';
|
|
4
5
|
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
|
5
|
-
import { appRouter } from '../router.js';
|
|
6
6
|
|
|
7
7
|
declare global {
|
|
8
8
|
interface HTMLElementTagNameMap {
|
|
@@ -10,67 +10,30 @@ declare global {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
type TEmailFolder = 'queued' | 'sent' | 'failed' | 'received' | 'security';
|
|
14
|
-
|
|
15
13
|
@customElement('ops-view-emails')
|
|
16
14
|
export class OpsViewEmails extends DeesElement {
|
|
17
15
|
@state()
|
|
18
|
-
accessor
|
|
19
|
-
|
|
20
|
-
@state()
|
|
21
|
-
accessor queuedEmails: interfaces.requests.IEmailQueueItem[] = [];
|
|
22
|
-
|
|
23
|
-
@state()
|
|
24
|
-
accessor sentEmails: interfaces.requests.IEmailQueueItem[] = [];
|
|
25
|
-
|
|
26
|
-
@state()
|
|
27
|
-
accessor failedEmails: interfaces.requests.IEmailQueueItem[] = [];
|
|
28
|
-
|
|
29
|
-
@state()
|
|
30
|
-
accessor securityIncidents: interfaces.requests.ISecurityIncident[] = [];
|
|
31
|
-
|
|
32
|
-
@state()
|
|
33
|
-
accessor selectedEmail: interfaces.requests.IEmailQueueItem | null = null;
|
|
16
|
+
accessor emails: interfaces.requests.IEmail[] = [];
|
|
34
17
|
|
|
35
18
|
@state()
|
|
36
|
-
accessor
|
|
19
|
+
accessor selectedEmail: interfaces.requests.IEmailDetail | null = null;
|
|
37
20
|
|
|
38
21
|
@state()
|
|
39
|
-
accessor
|
|
22
|
+
accessor currentView: 'list' | 'detail' = 'list';
|
|
40
23
|
|
|
41
24
|
@state()
|
|
42
25
|
accessor isLoading = false;
|
|
43
26
|
|
|
44
|
-
@state()
|
|
45
|
-
accessor searchTerm = '';
|
|
46
|
-
|
|
47
|
-
@state()
|
|
48
|
-
accessor emailDomains: string[] = [];
|
|
49
|
-
|
|
50
27
|
private stateSubscription: any;
|
|
51
28
|
|
|
52
|
-
constructor() {
|
|
53
|
-
super();
|
|
54
|
-
this.loadData();
|
|
55
|
-
this.loadEmailDomains();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
29
|
async connectedCallback() {
|
|
59
30
|
await super.connectedCallback();
|
|
60
|
-
// Subscribe to state changes
|
|
61
31
|
this.stateSubscription = appstate.emailOpsStatePart.state.subscribe((state) => {
|
|
62
|
-
this.
|
|
63
|
-
this.sentEmails = state.sentEmails;
|
|
64
|
-
this.failedEmails = state.failedEmails;
|
|
65
|
-
this.securityIncidents = state.securityIncidents;
|
|
32
|
+
this.emails = state.emails;
|
|
66
33
|
this.isLoading = state.isLoading;
|
|
67
|
-
|
|
68
|
-
// Sync folder from state (e.g., when URL changes)
|
|
69
|
-
if (state.currentView !== this.selectedFolder) {
|
|
70
|
-
this.selectedFolder = state.currentView as TEmailFolder;
|
|
71
|
-
this.loadFolderData(state.currentView as TEmailFolder);
|
|
72
|
-
}
|
|
73
34
|
});
|
|
35
|
+
// Initial fetch
|
|
36
|
+
await appstate.emailOpsStatePart.dispatchAction(appstate.fetchAllEmailsAction, null);
|
|
74
37
|
}
|
|
75
38
|
|
|
76
39
|
async disconnectedCallback() {
|
|
@@ -89,730 +52,58 @@ export class OpsViewEmails extends DeesElement {
|
|
|
89
52
|
height: 100%;
|
|
90
53
|
}
|
|
91
54
|
|
|
92
|
-
.
|
|
93
|
-
display: flex;
|
|
94
|
-
gap: 16px;
|
|
55
|
+
.viewContainer {
|
|
95
56
|
height: 100%;
|
|
96
|
-
min-height: 600px;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.sidebar {
|
|
100
|
-
flex-shrink: 0;
|
|
101
|
-
width: 280px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.mainArea {
|
|
105
|
-
flex: 1;
|
|
106
|
-
display: flex;
|
|
107
|
-
flex-direction: column;
|
|
108
|
-
gap: 16px;
|
|
109
|
-
overflow: hidden;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.emailToolbar {
|
|
113
|
-
display: flex;
|
|
114
|
-
align-items: center;
|
|
115
|
-
gap: 12px;
|
|
116
|
-
flex-wrap: wrap;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.searchBox {
|
|
120
|
-
flex: 1;
|
|
121
|
-
min-width: 200px;
|
|
122
|
-
max-width: 400px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.emailList {
|
|
126
|
-
flex: 1;
|
|
127
|
-
overflow: hidden;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.emailPreview {
|
|
131
|
-
flex: 1;
|
|
132
|
-
display: flex;
|
|
133
|
-
flex-direction: column;
|
|
134
|
-
background: ${cssManager.bdTheme('#fff', '#222')};
|
|
135
|
-
border: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
136
|
-
border-radius: 8px;
|
|
137
|
-
overflow: hidden;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.emailHeader {
|
|
141
|
-
padding: 24px;
|
|
142
|
-
border-bottom: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.emailSubject {
|
|
146
|
-
font-size: 24px;
|
|
147
|
-
font-weight: 600;
|
|
148
|
-
margin-bottom: 16px;
|
|
149
|
-
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.emailMeta {
|
|
153
|
-
display: flex;
|
|
154
|
-
flex-direction: column;
|
|
155
|
-
gap: 8px;
|
|
156
|
-
font-size: 14px;
|
|
157
|
-
color: ${cssManager.bdTheme('#666', '#999')};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.emailMetaRow {
|
|
161
|
-
display: flex;
|
|
162
|
-
gap: 8px;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.emailMetaLabel {
|
|
166
|
-
font-weight: 600;
|
|
167
|
-
min-width: 80px;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.emailBody {
|
|
171
|
-
flex: 1;
|
|
172
|
-
padding: 24px;
|
|
173
|
-
overflow-y: auto;
|
|
174
|
-
font-size: 15px;
|
|
175
|
-
line-height: 1.6;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.emailActions {
|
|
179
|
-
display: flex;
|
|
180
|
-
gap: 8px;
|
|
181
|
-
padding: 16px 24px;
|
|
182
|
-
border-top: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
183
|
-
background: ${cssManager.bdTheme('#fafafa', '#1a1a1a')};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.emptyState {
|
|
187
|
-
display: flex;
|
|
188
|
-
flex-direction: column;
|
|
189
|
-
align-items: center;
|
|
190
|
-
justify-content: center;
|
|
191
|
-
height: 400px;
|
|
192
|
-
color: ${cssManager.bdTheme('#999', '#666')};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.emptyIcon {
|
|
196
|
-
font-size: 64px;
|
|
197
|
-
margin-bottom: 16px;
|
|
198
|
-
opacity: 0.3;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.emptyText {
|
|
202
|
-
font-size: 18px;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.status-pending {
|
|
206
|
-
color: ${cssManager.bdTheme('#f59e0b', '#fbbf24')};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.status-processing {
|
|
210
|
-
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.status-delivered {
|
|
214
|
-
color: ${cssManager.bdTheme('#10b981', '#34d399')};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.status-failed {
|
|
218
|
-
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.status-deferred {
|
|
222
|
-
color: ${cssManager.bdTheme('#f97316', '#fb923c')};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.severity-info {
|
|
226
|
-
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.severity-warn {
|
|
230
|
-
color: ${cssManager.bdTheme('#f59e0b', '#fbbf24')};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.severity-error {
|
|
234
|
-
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.severity-critical {
|
|
238
|
-
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
|
239
|
-
font-weight: bold;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.incidentDetails {
|
|
243
|
-
padding: 24px;
|
|
244
|
-
background: ${cssManager.bdTheme('#fff', '#222')};
|
|
245
|
-
border: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
246
|
-
border-radius: 8px;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.incidentHeader {
|
|
250
|
-
display: flex;
|
|
251
|
-
justify-content: space-between;
|
|
252
|
-
align-items: flex-start;
|
|
253
|
-
margin-bottom: 16px;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.incidentTitle {
|
|
257
|
-
font-size: 20px;
|
|
258
|
-
font-weight: 600;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.incidentMeta {
|
|
262
|
-
display: grid;
|
|
263
|
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
264
|
-
gap: 12px;
|
|
265
|
-
margin-top: 16px;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
.incidentField {
|
|
269
|
-
padding: 12px;
|
|
270
|
-
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
|
|
271
|
-
border-radius: 6px;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
.incidentFieldLabel {
|
|
275
|
-
font-size: 12px;
|
|
276
|
-
color: ${cssManager.bdTheme('#666', '#999')};
|
|
277
|
-
margin-bottom: 4px;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.incidentFieldValue {
|
|
281
|
-
font-size: 14px;
|
|
282
|
-
word-break: break-all;
|
|
283
57
|
}
|
|
284
58
|
`,
|
|
285
59
|
];
|
|
286
60
|
|
|
287
61
|
public render() {
|
|
288
|
-
if (this.selectedEmail) {
|
|
289
|
-
return this.renderEmailDetail();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (this.selectedIncident) {
|
|
293
|
-
return this.renderIncidentDetail();
|
|
294
|
-
}
|
|
295
|
-
|
|
296
62
|
return html`
|
|
297
63
|
<ops-sectionheading>Email Operations</ops-sectionheading>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
<dees-icon icon="lucide:search" slot="iconSlot"></dees-icon>
|
|
313
|
-
</dees-input-text>
|
|
314
|
-
|
|
315
|
-
<dees-button @click=${() => this.refreshData()}>
|
|
316
|
-
${this.isLoading ? html`<dees-spinner slot="iconSlot" size="small"></dees-spinner>` : html`<dees-icon slot="iconSlot" icon="lucide:refreshCw"></dees-icon>`}
|
|
317
|
-
Refresh
|
|
318
|
-
</dees-button>
|
|
319
|
-
|
|
320
|
-
<div style="margin-left: auto; display: flex; gap: 8px;">
|
|
321
|
-
<dees-button-group>
|
|
322
|
-
<dees-button
|
|
323
|
-
@click=${() => this.selectFolder('queued')}
|
|
324
|
-
.type=${this.selectedFolder === 'queued' ? 'highlighted' : 'normal'}
|
|
325
|
-
>
|
|
326
|
-
Queued ${this.queuedEmails.length > 0 ? `(${this.queuedEmails.length})` : ''}
|
|
327
|
-
</dees-button>
|
|
328
|
-
<dees-button
|
|
329
|
-
@click=${() => this.selectFolder('sent')}
|
|
330
|
-
.type=${this.selectedFolder === 'sent' ? 'highlighted' : 'normal'}
|
|
331
|
-
>
|
|
332
|
-
Sent
|
|
333
|
-
</dees-button>
|
|
334
|
-
<dees-button
|
|
335
|
-
@click=${() => this.selectFolder('failed')}
|
|
336
|
-
.type=${this.selectedFolder === 'failed' ? 'highlighted' : 'normal'}
|
|
337
|
-
>
|
|
338
|
-
Failed ${this.failedEmails.length > 0 ? `(${this.failedEmails.length})` : ''}
|
|
339
|
-
</dees-button>
|
|
340
|
-
<dees-button
|
|
341
|
-
@click=${() => this.selectFolder('security')}
|
|
342
|
-
.type=${this.selectedFolder === 'security' ? 'highlighted' : 'normal'}
|
|
343
|
-
>
|
|
344
|
-
Security ${this.securityIncidents.length > 0 ? `(${this.securityIncidents.length})` : ''}
|
|
345
|
-
</dees-button>
|
|
346
|
-
</dees-button-group>
|
|
347
|
-
</div>
|
|
348
|
-
</div>
|
|
349
|
-
|
|
350
|
-
${this.renderContent()}
|
|
351
|
-
`;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
private renderContent() {
|
|
355
|
-
switch (this.selectedFolder) {
|
|
356
|
-
case 'queued':
|
|
357
|
-
return this.renderEmailTable(this.queuedEmails, 'Queued Emails', 'Emails waiting to be delivered');
|
|
358
|
-
case 'sent':
|
|
359
|
-
return this.renderEmailTable(this.sentEmails, 'Sent Emails', 'Successfully delivered emails');
|
|
360
|
-
case 'failed':
|
|
361
|
-
return this.renderEmailTable(this.failedEmails, 'Failed Emails', 'Emails that failed to deliver', true);
|
|
362
|
-
case 'security':
|
|
363
|
-
return this.renderSecurityIncidents();
|
|
364
|
-
default:
|
|
365
|
-
return this.renderEmptyState('Select a folder');
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
private renderEmailTable(
|
|
370
|
-
emails: interfaces.requests.IEmailQueueItem[],
|
|
371
|
-
heading1: string,
|
|
372
|
-
heading2: string,
|
|
373
|
-
showResend = false
|
|
374
|
-
) {
|
|
375
|
-
const filteredEmails = this.filterEmails(emails);
|
|
376
|
-
|
|
377
|
-
if (filteredEmails.length === 0) {
|
|
378
|
-
return this.renderEmptyState(`No emails in ${this.selectedFolder}`);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const actions = [
|
|
382
|
-
{
|
|
383
|
-
name: 'View Details',
|
|
384
|
-
iconName: 'lucide:eye',
|
|
385
|
-
type: ['doubleClick', 'inRow'] as any,
|
|
386
|
-
actionFunc: async (actionData: any) => {
|
|
387
|
-
this.selectedEmail = actionData.item;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
];
|
|
391
|
-
|
|
392
|
-
if (showResend) {
|
|
393
|
-
actions.push({
|
|
394
|
-
name: 'Resend',
|
|
395
|
-
iconName: 'lucide:send',
|
|
396
|
-
type: ['inRow'] as any,
|
|
397
|
-
actionFunc: async (actionData: any) => {
|
|
398
|
-
await this.resendEmail(actionData.item.id);
|
|
64
|
+
<div class="viewContainer">
|
|
65
|
+
${this.currentView === 'detail' && this.selectedEmail
|
|
66
|
+
? html`
|
|
67
|
+
<sz-mta-detail-view
|
|
68
|
+
.email=${this.selectedEmail}
|
|
69
|
+
@back=${this.handleBack}
|
|
70
|
+
></sz-mta-detail-view>
|
|
71
|
+
`
|
|
72
|
+
: html`
|
|
73
|
+
<sz-mta-list-view
|
|
74
|
+
.emails=${this.emails}
|
|
75
|
+
@email-click=${this.handleEmailClick}
|
|
76
|
+
></sz-mta-list-view>
|
|
77
|
+
`
|
|
399
78
|
}
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return html`
|
|
404
|
-
<dees-table
|
|
405
|
-
.data=${filteredEmails}
|
|
406
|
-
.displayFunction=${(email: interfaces.requests.IEmailQueueItem) => ({
|
|
407
|
-
'Status': html`<span class="status-${email.status}">${email.status}</span>`,
|
|
408
|
-
'From': email.from || 'N/A',
|
|
409
|
-
'To': email.to?.join(', ') || 'N/A',
|
|
410
|
-
'Subject': email.subject || 'No subject',
|
|
411
|
-
'Attempts': email.attempts,
|
|
412
|
-
'Created': this.formatDate(email.createdAt),
|
|
413
|
-
})}
|
|
414
|
-
.dataActions=${actions}
|
|
415
|
-
.selectionMode=${'single'}
|
|
416
|
-
heading1=${heading1}
|
|
417
|
-
heading2=${`${filteredEmails.length} emails - ${heading2}`}
|
|
418
|
-
></dees-table>
|
|
419
|
-
`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
private renderSecurityIncidents() {
|
|
423
|
-
const incidents = this.securityIncidents;
|
|
424
|
-
|
|
425
|
-
if (incidents.length === 0) {
|
|
426
|
-
return this.renderEmptyState('No security incidents');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return html`
|
|
430
|
-
<dees-table
|
|
431
|
-
.data=${incidents}
|
|
432
|
-
.displayFunction=${(incident: interfaces.requests.ISecurityIncident) => ({
|
|
433
|
-
'Severity': html`<span class="severity-${incident.level}">${incident.level.toUpperCase()}</span>`,
|
|
434
|
-
'Type': incident.type,
|
|
435
|
-
'Message': incident.message,
|
|
436
|
-
'IP': incident.ipAddress || 'N/A',
|
|
437
|
-
'Domain': incident.domain || 'N/A',
|
|
438
|
-
'Time': this.formatDate(incident.timestamp),
|
|
439
|
-
})}
|
|
440
|
-
.dataActions=${[
|
|
441
|
-
{
|
|
442
|
-
name: 'View Details',
|
|
443
|
-
iconName: 'lucide:eye',
|
|
444
|
-
type: ['doubleClick', 'inRow'],
|
|
445
|
-
actionFunc: async (actionData: any) => {
|
|
446
|
-
this.selectedIncident = actionData.item;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
]}
|
|
450
|
-
.selectionMode=${'single'}
|
|
451
|
-
heading1="Security Incidents"
|
|
452
|
-
heading2=${`${incidents.length} incidents`}
|
|
453
|
-
></dees-table>
|
|
454
|
-
`;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
private renderEmailDetail() {
|
|
458
|
-
if (!this.selectedEmail) return '';
|
|
459
|
-
|
|
460
|
-
return html`
|
|
461
|
-
<ops-sectionheading>Email Details</ops-sectionheading>
|
|
462
|
-
<div class="emailLayout">
|
|
463
|
-
<div class="sidebar">
|
|
464
|
-
<dees-windowbox>
|
|
465
|
-
<dees-button @click=${() => this.selectedEmail = null} type="secondary" style="width: 100%;">
|
|
466
|
-
<dees-icon icon="lucide:arrowLeft" slot="iconSlot"></dees-icon>
|
|
467
|
-
Back to List
|
|
468
|
-
</dees-button>
|
|
469
|
-
</dees-windowbox>
|
|
470
|
-
</div>
|
|
471
|
-
<div class="mainArea">
|
|
472
|
-
<div class="emailPreview">
|
|
473
|
-
<div class="emailHeader">
|
|
474
|
-
<div class="emailSubject">${this.selectedEmail.subject || 'No subject'}</div>
|
|
475
|
-
<div class="emailMeta">
|
|
476
|
-
<div class="emailMetaRow">
|
|
477
|
-
<span class="emailMetaLabel">Status:</span>
|
|
478
|
-
<span class="status-${this.selectedEmail.status}">${this.selectedEmail.status}</span>
|
|
479
|
-
</div>
|
|
480
|
-
<div class="emailMetaRow">
|
|
481
|
-
<span class="emailMetaLabel">From:</span>
|
|
482
|
-
<span>${this.selectedEmail.from || 'N/A'}</span>
|
|
483
|
-
</div>
|
|
484
|
-
<div class="emailMetaRow">
|
|
485
|
-
<span class="emailMetaLabel">To:</span>
|
|
486
|
-
<span>${this.selectedEmail.to?.join(', ') || 'N/A'}</span>
|
|
487
|
-
</div>
|
|
488
|
-
<div class="emailMetaRow">
|
|
489
|
-
<span class="emailMetaLabel">Mode:</span>
|
|
490
|
-
<span>${this.selectedEmail.processingMode}</span>
|
|
491
|
-
</div>
|
|
492
|
-
<div class="emailMetaRow">
|
|
493
|
-
<span class="emailMetaLabel">Attempts:</span>
|
|
494
|
-
<span>${this.selectedEmail.attempts}</span>
|
|
495
|
-
</div>
|
|
496
|
-
<div class="emailMetaRow">
|
|
497
|
-
<span class="emailMetaLabel">Created:</span>
|
|
498
|
-
<span>${new Date(this.selectedEmail.createdAt).toLocaleString()}</span>
|
|
499
|
-
</div>
|
|
500
|
-
${this.selectedEmail.deliveredAt ? html`
|
|
501
|
-
<div class="emailMetaRow">
|
|
502
|
-
<span class="emailMetaLabel">Delivered:</span>
|
|
503
|
-
<span>${new Date(this.selectedEmail.deliveredAt).toLocaleString()}</span>
|
|
504
|
-
</div>
|
|
505
|
-
` : ''}
|
|
506
|
-
${this.selectedEmail.lastError ? html`
|
|
507
|
-
<div class="emailMetaRow">
|
|
508
|
-
<span class="emailMetaLabel">Last Error:</span>
|
|
509
|
-
<span style="color: #ef4444;">${this.selectedEmail.lastError}</span>
|
|
510
|
-
</div>
|
|
511
|
-
` : ''}
|
|
512
|
-
</div>
|
|
513
|
-
</div>
|
|
514
|
-
|
|
515
|
-
<div class="emailActions">
|
|
516
|
-
${this.selectedEmail.status === 'failed' ? html`
|
|
517
|
-
<dees-button @click=${() => this.resendEmail(this.selectedEmail!.id)} type="highlighted">
|
|
518
|
-
<dees-icon icon="lucide:send" slot="iconSlot"></dees-icon>
|
|
519
|
-
Resend
|
|
520
|
-
</dees-button>
|
|
521
|
-
` : ''}
|
|
522
|
-
<dees-button @click=${() => this.selectedEmail = null}>
|
|
523
|
-
<dees-icon icon="lucide:x" slot="iconSlot"></dees-icon>
|
|
524
|
-
Close
|
|
525
|
-
</dees-button>
|
|
526
|
-
</div>
|
|
527
|
-
</div>
|
|
528
|
-
</div>
|
|
529
|
-
</div>
|
|
530
|
-
`;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
private renderIncidentDetail() {
|
|
534
|
-
if (!this.selectedIncident) return '';
|
|
535
|
-
|
|
536
|
-
const incident = this.selectedIncident;
|
|
537
|
-
|
|
538
|
-
return html`
|
|
539
|
-
<ops-sectionheading>Security Incident Details</ops-sectionheading>
|
|
540
|
-
<div style="margin-bottom: 16px;">
|
|
541
|
-
<dees-button @click=${() => this.selectedIncident = null} type="secondary">
|
|
542
|
-
<dees-icon icon="lucide:arrowLeft" slot="iconSlot"></dees-icon>
|
|
543
|
-
Back to List
|
|
544
|
-
</dees-button>
|
|
545
|
-
</div>
|
|
546
|
-
<div class="incidentDetails">
|
|
547
|
-
<div class="incidentHeader">
|
|
548
|
-
<div>
|
|
549
|
-
<div class="incidentTitle">${incident.message}</div>
|
|
550
|
-
<div style="margin-top: 8px; color: #666;">
|
|
551
|
-
${new Date(incident.timestamp).toLocaleString()}
|
|
552
|
-
</div>
|
|
553
|
-
</div>
|
|
554
|
-
<span class="severity-${incident.level}" style="font-size: 16px; padding: 4px 12px; background: rgba(0,0,0,0.1); border-radius: 4px;">
|
|
555
|
-
${incident.level.toUpperCase()}
|
|
556
|
-
</span>
|
|
557
|
-
</div>
|
|
558
|
-
|
|
559
|
-
<div class="incidentMeta">
|
|
560
|
-
<div class="incidentField">
|
|
561
|
-
<div class="incidentFieldLabel">Type</div>
|
|
562
|
-
<div class="incidentFieldValue">${incident.type}</div>
|
|
563
|
-
</div>
|
|
564
|
-
${incident.ipAddress ? html`
|
|
565
|
-
<div class="incidentField">
|
|
566
|
-
<div class="incidentFieldLabel">IP Address</div>
|
|
567
|
-
<div class="incidentFieldValue">${incident.ipAddress}</div>
|
|
568
|
-
</div>
|
|
569
|
-
` : ''}
|
|
570
|
-
${incident.domain ? html`
|
|
571
|
-
<div class="incidentField">
|
|
572
|
-
<div class="incidentFieldLabel">Domain</div>
|
|
573
|
-
<div class="incidentFieldValue">${incident.domain}</div>
|
|
574
|
-
</div>
|
|
575
|
-
` : ''}
|
|
576
|
-
${incident.emailId ? html`
|
|
577
|
-
<div class="incidentField">
|
|
578
|
-
<div class="incidentFieldLabel">Email ID</div>
|
|
579
|
-
<div class="incidentFieldValue">${incident.emailId}</div>
|
|
580
|
-
</div>
|
|
581
|
-
` : ''}
|
|
582
|
-
${incident.userId ? html`
|
|
583
|
-
<div class="incidentField">
|
|
584
|
-
<div class="incidentFieldLabel">User ID</div>
|
|
585
|
-
<div class="incidentFieldValue">${incident.userId}</div>
|
|
586
|
-
</div>
|
|
587
|
-
` : ''}
|
|
588
|
-
${incident.action ? html`
|
|
589
|
-
<div class="incidentField">
|
|
590
|
-
<div class="incidentFieldLabel">Action</div>
|
|
591
|
-
<div class="incidentFieldValue">${incident.action}</div>
|
|
592
|
-
</div>
|
|
593
|
-
` : ''}
|
|
594
|
-
${incident.result ? html`
|
|
595
|
-
<div class="incidentField">
|
|
596
|
-
<div class="incidentFieldLabel">Result</div>
|
|
597
|
-
<div class="incidentFieldValue">${incident.result}</div>
|
|
598
|
-
</div>
|
|
599
|
-
` : ''}
|
|
600
|
-
${incident.success !== undefined ? html`
|
|
601
|
-
<div class="incidentField">
|
|
602
|
-
<div class="incidentFieldLabel">Success</div>
|
|
603
|
-
<div class="incidentFieldValue">${incident.success ? 'Yes' : 'No'}</div>
|
|
604
|
-
</div>
|
|
605
|
-
` : ''}
|
|
606
|
-
</div>
|
|
607
|
-
|
|
608
|
-
${incident.details ? html`
|
|
609
|
-
<div style="margin-top: 24px;">
|
|
610
|
-
<div class="incidentFieldLabel" style="margin-bottom: 8px;">Details</div>
|
|
611
|
-
<pre style="background: #1a1a1a; color: #e5e5e5; padding: 16px; border-radius: 6px; overflow-x: auto; font-size: 13px;">
|
|
612
|
-
${JSON.stringify(incident.details, null, 2)}
|
|
613
|
-
</pre>
|
|
614
|
-
</div>
|
|
615
|
-
` : ''}
|
|
616
79
|
</div>
|
|
617
80
|
`;
|
|
618
81
|
}
|
|
619
82
|
|
|
620
|
-
private
|
|
621
|
-
|
|
622
|
-
<div class="emptyState">
|
|
623
|
-
<dees-icon class="emptyIcon" icon="lucide:inbox"></dees-icon>
|
|
624
|
-
<div class="emptyText">${message}</div>
|
|
625
|
-
</div>
|
|
626
|
-
`;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
private async openComposeModal() {
|
|
630
|
-
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
631
|
-
|
|
632
|
-
// Ensure domains are loaded before opening modal
|
|
633
|
-
if (this.emailDomains.length === 0) {
|
|
634
|
-
await this.loadEmailDomains();
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
await DeesModal.createAndShow({
|
|
638
|
-
heading: 'New Email',
|
|
639
|
-
width: 'large',
|
|
640
|
-
content: html`
|
|
641
|
-
<div>
|
|
642
|
-
<dees-form @formData=${async (e: CustomEvent) => {
|
|
643
|
-
await this.sendEmail(e.detail);
|
|
644
|
-
const modals = document.querySelectorAll('dees-modal');
|
|
645
|
-
modals.forEach(m => (m as any).destroy?.());
|
|
646
|
-
}}>
|
|
647
|
-
<div style="display: flex; gap: 8px; align-items: flex-end;">
|
|
648
|
-
<dees-input-text
|
|
649
|
-
key="fromUsername"
|
|
650
|
-
label="From"
|
|
651
|
-
placeholder="username"
|
|
652
|
-
.value=${'admin'}
|
|
653
|
-
required
|
|
654
|
-
style="flex: 1;"
|
|
655
|
-
></dees-input-text>
|
|
656
|
-
<span style="padding-bottom: 12px; font-size: 18px; color: #666;">@</span>
|
|
657
|
-
<dees-input-dropdown
|
|
658
|
-
key="fromDomain"
|
|
659
|
-
label=" "
|
|
660
|
-
.options=${this.emailDomains.length > 0
|
|
661
|
-
? this.emailDomains.map(domain => ({ key: domain, value: domain }))
|
|
662
|
-
: [{ key: 'dcrouter.local', value: 'dcrouter.local' }]}
|
|
663
|
-
.selectedKey=${this.emailDomains[0] || 'dcrouter.local'}
|
|
664
|
-
required
|
|
665
|
-
style="flex: 1;"
|
|
666
|
-
></dees-input-dropdown>
|
|
667
|
-
</div>
|
|
668
|
-
|
|
669
|
-
<dees-input-tags
|
|
670
|
-
key="to"
|
|
671
|
-
label="To"
|
|
672
|
-
placeholder="Enter recipient email addresses..."
|
|
673
|
-
required
|
|
674
|
-
></dees-input-tags>
|
|
675
|
-
|
|
676
|
-
<dees-input-tags
|
|
677
|
-
key="cc"
|
|
678
|
-
label="CC"
|
|
679
|
-
placeholder="Enter CC recipients..."
|
|
680
|
-
></dees-input-tags>
|
|
681
|
-
|
|
682
|
-
<dees-input-text
|
|
683
|
-
key="subject"
|
|
684
|
-
label="Subject"
|
|
685
|
-
placeholder="Enter email subject..."
|
|
686
|
-
required
|
|
687
|
-
></dees-input-text>
|
|
688
|
-
|
|
689
|
-
<dees-input-wysiwyg
|
|
690
|
-
key="body"
|
|
691
|
-
label="Message"
|
|
692
|
-
outputFormat="html"
|
|
693
|
-
></dees-input-wysiwyg>
|
|
694
|
-
</dees-form>
|
|
695
|
-
</div>
|
|
696
|
-
`,
|
|
697
|
-
menuOptions: [
|
|
698
|
-
{
|
|
699
|
-
name: 'Send',
|
|
700
|
-
iconName: 'lucide:send',
|
|
701
|
-
action: async (modalArg) => {
|
|
702
|
-
const form = modalArg.shadowRoot?.querySelector('dees-form') as any;
|
|
703
|
-
form?.submit();
|
|
704
|
-
}
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
name: 'Cancel',
|
|
708
|
-
iconName: 'lucide:x',
|
|
709
|
-
action: async (modalArg) => await modalArg.destroy()
|
|
710
|
-
}
|
|
711
|
-
]
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
private filterEmails(emails: interfaces.requests.IEmailQueueItem[]): interfaces.requests.IEmailQueueItem[] {
|
|
716
|
-
if (!this.searchTerm) {
|
|
717
|
-
return emails;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const search = this.searchTerm.toLowerCase();
|
|
721
|
-
return emails.filter(e =>
|
|
722
|
-
(e.subject?.toLowerCase().includes(search)) ||
|
|
723
|
-
(e.from?.toLowerCase().includes(search)) ||
|
|
724
|
-
(e.to?.some(t => t.toLowerCase().includes(search)))
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
private selectFolder(folder: TEmailFolder) {
|
|
729
|
-
// Use router for navigation to update URL
|
|
730
|
-
appRouter.navigateToEmailFolder(folder);
|
|
731
|
-
// Clear selections
|
|
732
|
-
this.selectedEmail = null;
|
|
733
|
-
this.selectedIncident = null;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
private formatDate(timestamp: number): string {
|
|
737
|
-
const date = new Date(timestamp);
|
|
738
|
-
const now = new Date();
|
|
739
|
-
const diff = now.getTime() - date.getTime();
|
|
740
|
-
const hours = diff / (1000 * 60 * 60);
|
|
741
|
-
|
|
742
|
-
if (hours < 24) {
|
|
743
|
-
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
744
|
-
} else if (hours < 168) { // 7 days
|
|
745
|
-
return date.toLocaleDateString([], { weekday: 'short', hour: '2-digit', minute: '2-digit' });
|
|
746
|
-
} else {
|
|
747
|
-
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
private async loadData() {
|
|
752
|
-
this.isLoading = true;
|
|
753
|
-
await this.loadFolderData(this.selectedFolder);
|
|
754
|
-
this.isLoading = false;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
private async loadFolderData(folder: TEmailFolder) {
|
|
758
|
-
switch (folder) {
|
|
759
|
-
case 'queued':
|
|
760
|
-
await appstate.emailOpsStatePart.dispatchAction(appstate.fetchQueuedEmailsAction, null);
|
|
761
|
-
break;
|
|
762
|
-
case 'sent':
|
|
763
|
-
await appstate.emailOpsStatePart.dispatchAction(appstate.fetchSentEmailsAction, null);
|
|
764
|
-
break;
|
|
765
|
-
case 'failed':
|
|
766
|
-
await appstate.emailOpsStatePart.dispatchAction(appstate.fetchFailedEmailsAction, null);
|
|
767
|
-
break;
|
|
768
|
-
case 'security':
|
|
769
|
-
await appstate.emailOpsStatePart.dispatchAction(appstate.fetchSecurityIncidentsAction, null);
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
private async loadEmailDomains() {
|
|
83
|
+
private async handleEmailClick(e: CustomEvent<interfaces.requests.IEmail>) {
|
|
84
|
+
const emailSummary = e.detail;
|
|
775
85
|
try {
|
|
776
|
-
|
|
777
|
-
const
|
|
86
|
+
const context = appstate.loginStatePart.getState();
|
|
87
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
88
|
+
interfaces.requests.IReq_GetEmailDetail
|
|
89
|
+
>('/typedrequest', 'getEmailDetail');
|
|
90
|
+
|
|
91
|
+
const response = await request.fire({
|
|
92
|
+
identity: context.identity,
|
|
93
|
+
emailId: emailSummary.id,
|
|
94
|
+
});
|
|
778
95
|
|
|
779
|
-
if (
|
|
780
|
-
this.
|
|
781
|
-
|
|
782
|
-
this.emailDomains = ['dcrouter.local'];
|
|
96
|
+
if (response.email) {
|
|
97
|
+
this.selectedEmail = response.email;
|
|
98
|
+
this.currentView = 'detail';
|
|
783
99
|
}
|
|
784
100
|
} catch (error) {
|
|
785
|
-
console.error('Failed to
|
|
786
|
-
this.emailDomains = ['dcrouter.local'];
|
|
101
|
+
console.error('Failed to fetch email detail:', error);
|
|
787
102
|
}
|
|
788
103
|
}
|
|
789
104
|
|
|
790
|
-
private
|
|
791
|
-
this.
|
|
792
|
-
|
|
793
|
-
this.isLoading = false;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
private async sendEmail(formData: any) {
|
|
797
|
-
try {
|
|
798
|
-
console.log('Sending email:', formData);
|
|
799
|
-
// TODO: Implement actual email sending via API
|
|
800
|
-
// For now, just log the data
|
|
801
|
-
const fromEmail = `${formData.fromUsername || 'admin'}@${formData.fromDomain || this.emailDomains[0] || 'dcrouter.local'}`;
|
|
802
|
-
console.log('From:', fromEmail);
|
|
803
|
-
console.log('To:', formData.to);
|
|
804
|
-
console.log('Subject:', formData.subject);
|
|
805
|
-
} catch (error: any) {
|
|
806
|
-
console.error('Failed to send email', error);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
private async resendEmail(emailId: string) {
|
|
811
|
-
try {
|
|
812
|
-
await appstate.emailOpsStatePart.dispatchAction(appstate.resendEmailAction, emailId);
|
|
813
|
-
this.selectedEmail = null;
|
|
814
|
-
} catch (error) {
|
|
815
|
-
console.error('Failed to resend email:', error);
|
|
816
|
-
}
|
|
105
|
+
private handleBack() {
|
|
106
|
+
this.selectedEmail = null;
|
|
107
|
+
this.currentView = 'list';
|
|
817
108
|
}
|
|
818
109
|
}
|