@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.
@@ -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 selectedFolder: TEmailFolder = 'queued';
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 selectedIncident: interfaces.requests.ISecurityIncident | null = null;
19
+ accessor selectedEmail: interfaces.requests.IEmailDetail | null = null;
37
20
 
38
21
  @state()
39
- accessor showCompose = false;
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.queuedEmails = state.queuedEmails;
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
- .emailLayout {
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
- <!-- Toolbar -->
300
- <div class="emailToolbar" style="margin-bottom: 16px;">
301
- <dees-button @click=${() => this.openComposeModal()} type="highlighted">
302
- <dees-icon icon="lucide:penLine" slot="iconSlot"></dees-icon>
303
- Compose
304
- </dees-button>
305
-
306
- <dees-input-text
307
- class="searchBox"
308
- placeholder="Search..."
309
- .value=${this.searchTerm}
310
- @input=${(e: Event) => this.searchTerm = (e.target as any).value}
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 renderEmptyState(message: string) {
621
- return html`
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
- await appstate.configStatePart.dispatchAction(appstate.fetchConfigurationAction, null);
777
- const config = appstate.configStatePart.getState().config;
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 (config?.email?.domains && Array.isArray(config.email.domains) && config.email.domains.length > 0) {
780
- this.emailDomains = config.email.domains;
781
- } else {
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 load email domains:', error);
786
- this.emailDomains = ['dcrouter.local'];
101
+ console.error('Failed to fetch email detail:', error);
787
102
  }
788
103
  }
789
104
 
790
- private async refreshData() {
791
- this.isLoading = true;
792
- await this.loadFolderData(this.selectedFolder);
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
  }