@serve.zone/dcrouter 16.5.0 → 17.1.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.
Files changed (153) hide show
  1. package/.smartconfig.json +5 -2
  2. package/deno.json +1 -1
  3. package/dist_serve/bundle.js +1316 -1151
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/classes.dcrouter.d.ts +6 -1
  6. package/dist_ts/classes.dcrouter.js +119 -41
  7. package/dist_ts/db/classes.cache.cleaner.d.ts +3 -0
  8. package/dist_ts/db/classes.cache.cleaner.js +7 -3
  9. package/dist_ts/db/classes.dcrouter-db.js +1 -2
  10. package/dist_ts/db/documents/classes.cached.email.d.ts +41 -4
  11. package/dist_ts/db/documents/classes.cached.email.js +36 -5
  12. package/dist_ts/db/documents/classes.dns-record.doc.js +3 -1
  13. package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +8 -0
  14. package/dist_ts/db/documents/classes.smartmta-storage.doc.js +91 -0
  15. package/dist_ts/db/documents/index.d.ts +1 -0
  16. package/dist_ts/db/documents/index.js +2 -1
  17. package/dist_ts/dns/classes.dns-server-runtime.d.ts +0 -1
  18. package/dist_ts/dns/classes.dns-server-runtime.js +4 -36
  19. package/dist_ts/dns/index.d.ts +1 -0
  20. package/dist_ts/dns/index.js +2 -1
  21. package/dist_ts/dns/manager.dns.d.ts +1 -0
  22. package/dist_ts/dns/manager.dns.js +49 -14
  23. package/dist_ts/dns/providers/cloudflare.provider.js +19 -4
  24. package/dist_ts/dns/txt-record-presentation.d.ts +20 -0
  25. package/dist_ts/dns/txt-record-presentation.js +131 -0
  26. package/dist_ts/email/classes.accepted-email-spool.d.ts +32 -3
  27. package/dist_ts/email/classes.accepted-email-spool.js +174 -13
  28. package/dist_ts/email/classes.email-domain.manager.d.ts +14 -1
  29. package/dist_ts/email/classes.email-domain.manager.js +151 -8
  30. package/dist_ts/email/classes.email-settings.manager.js +2 -1
  31. package/dist_ts/email/classes.mail-dns-sync.d.ts +31 -5
  32. package/dist_ts/email/classes.mail-dns-sync.js +508 -117
  33. package/dist_ts/email/classes.smartmta-blob-storage-manager.d.ts +27 -0
  34. package/dist_ts/email/classes.smartmta-blob-storage-manager.js +110 -0
  35. package/dist_ts/email/classes.smartmta-storage-manager.d.ts +6 -8
  36. package/dist_ts/email/classes.smartmta-storage-manager.js +63 -86
  37. package/dist_ts/email/classes.workapp-mail-manager.d.ts +1 -0
  38. package/dist_ts/email/classes.workapp-mail-manager.js +31 -4
  39. package/dist_ts/email/index.d.ts +4 -0
  40. package/dist_ts/email/index.js +5 -1
  41. package/dist_ts/email/mail-dns-txt-policy.d.ts +18 -0
  42. package/dist_ts/email/mail-dns-txt-policy.js +466 -0
  43. package/dist_ts/email/reconcile-mail-dns-singleton.d.ts +47 -0
  44. package/dist_ts/email/reconcile-mail-dns-singleton.js +159 -0
  45. package/dist_ts/email/smartmta-storage-policy.d.ts +9 -0
  46. package/dist_ts/email/smartmta-storage-policy.js +58 -0
  47. package/dist_ts/opsserver/classes.opsserver.d.ts +8 -0
  48. package/dist_ts/opsserver/classes.opsserver.js +34 -1
  49. package/dist_ts/opsserver/handlers/certificate.handler.js +25 -4
  50. package/dist_ts/opsserver/handlers/config.handler.js +9 -5
  51. package/dist_ts/opsserver/handlers/dns-provider.handler.js +19 -2
  52. package/dist_ts/opsserver/handlers/dns-record.handler.js +25 -4
  53. package/dist_ts/opsserver/handlers/domain.handler.js +37 -3
  54. package/dist_ts/opsserver/handlers/email-domain.handler.js +5 -7
  55. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +1 -0
  56. package/dist_ts/opsserver/handlers/email-ops.handler.js +26 -7
  57. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  58. package/dist_ts/opsserver/handlers/index.js +2 -1
  59. package/dist_ts/opsserver/handlers/logs.handler.d.ts +1 -1
  60. package/dist_ts/opsserver/handlers/logs.handler.js +3 -3
  61. package/dist_ts/opsserver/handlers/realtime.handler.d.ts +37 -0
  62. package/dist_ts/opsserver/handlers/realtime.handler.js +154 -0
  63. package/dist_ts/opsserver/handlers/remoteingress.handler.js +22 -1
  64. package/dist_ts/opsserver/handlers/route-management.handler.js +47 -5
  65. package/dist_ts/opsserver/handlers/stats.handler.js +3 -5
  66. package/dist_ts/paths.d.ts +0 -11
  67. package/dist_ts/paths.js +1 -22
  68. package/dist_ts/plugins.d.ts +2 -35
  69. package/dist_ts/plugins.js +3 -63
  70. package/dist_ts_interfaces/data/email-domain.d.ts +14 -0
  71. package/dist_ts_interfaces/requests/config.d.ts +1 -1
  72. package/dist_ts_interfaces/requests/email-ops.d.ts +34 -1
  73. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  74. package/dist_ts_interfaces/requests/index.js +2 -1
  75. package/dist_ts_interfaces/requests/realtime.d.ts +38 -0
  76. package/dist_ts_interfaces/requests/realtime.js +11 -0
  77. package/dist_ts_migrations/index.d.ts +8 -0
  78. package/dist_ts_migrations/index.js +489 -12
  79. package/dist_ts_web/00_commitinfo_data.js +1 -1
  80. package/dist_ts_web/appstate/certificates.js +9 -2
  81. package/dist_ts_web/appstate/domains.js +16 -5
  82. package/dist_ts_web/appstate/email-domains.d.ts +3 -0
  83. package/dist_ts_web/appstate/email-domains.js +47 -14
  84. package/dist_ts_web/appstate/email-ops.d.ts +1 -0
  85. package/dist_ts_web/appstate/email-ops.js +20 -2
  86. package/dist_ts_web/appstate/realtime.d.ts +27 -0
  87. package/dist_ts_web/appstate/realtime.js +71 -0
  88. package/dist_ts_web/appstate/remoteingress.js +9 -3
  89. package/dist_ts_web/appstate/routes.js +16 -5
  90. package/dist_ts_web/appstate/runtime.js +135 -7
  91. package/dist_ts_web/appstate.d.ts +1 -0
  92. package/dist_ts_web/appstate.js +2 -1
  93. package/dist_ts_web/elements/email/email-domain-creation-progress.d.ts +33 -0
  94. package/dist_ts_web/elements/email/email-domain-creation-progress.js +257 -0
  95. package/dist_ts_web/elements/email/ops-view-email-domains.js +233 -68
  96. package/dist_ts_web/elements/email/ops-view-emails.d.ts +4 -2
  97. package/dist_ts_web/elements/email/ops-view-emails.js +25 -13
  98. package/dist_ts_web/elements/overview/ops-view-config.js +5 -5
  99. package/package.json +23 -19
  100. package/ts/00_commitinfo_data.ts +1 -1
  101. package/ts/classes.dcrouter.ts +136 -44
  102. package/ts/db/classes.cache.cleaner.ts +18 -3
  103. package/ts/db/classes.dcrouter-db.ts +0 -2
  104. package/ts/db/documents/classes.cached.email.ts +78 -4
  105. package/ts/db/documents/classes.dns-record.doc.ts +1 -0
  106. package/ts/db/documents/classes.smartmta-storage.doc.ts +25 -0
  107. package/ts/db/documents/index.ts +1 -0
  108. package/ts/dns/classes.dns-server-runtime.ts +9 -45
  109. package/ts/dns/index.ts +1 -0
  110. package/ts/dns/manager.dns.ts +50 -16
  111. package/ts/dns/providers/cloudflare.provider.ts +21 -3
  112. package/ts/dns/txt-record-presentation.ts +134 -0
  113. package/ts/email/classes.accepted-email-spool.ts +220 -14
  114. package/ts/email/classes.email-domain.manager.ts +201 -7
  115. package/ts/email/classes.email-settings.manager.ts +1 -0
  116. package/ts/email/classes.mail-dns-sync.ts +588 -122
  117. package/ts/email/classes.smartmta-blob-storage-manager.ts +139 -0
  118. package/ts/email/classes.smartmta-storage-manager.ts +86 -83
  119. package/ts/email/classes.workapp-mail-manager.ts +47 -4
  120. package/ts/email/index.ts +4 -0
  121. package/ts/email/mail-dns-txt-policy.ts +505 -0
  122. package/ts/email/reconcile-mail-dns-singleton.ts +224 -0
  123. package/ts/email/smartmta-storage-policy.ts +73 -0
  124. package/ts/opsserver/classes.opsserver.ts +42 -0
  125. package/ts/opsserver/handlers/certificate.handler.ts +24 -3
  126. package/ts/opsserver/handlers/config.handler.ts +9 -5
  127. package/ts/opsserver/handlers/dns-provider.handler.ts +18 -1
  128. package/ts/opsserver/handlers/dns-record.handler.ts +24 -3
  129. package/ts/opsserver/handlers/domain.handler.ts +36 -2
  130. package/ts/opsserver/handlers/email-domain.handler.ts +4 -5
  131. package/ts/opsserver/handlers/email-ops.handler.ts +29 -6
  132. package/ts/opsserver/handlers/index.ts +1 -0
  133. package/ts/opsserver/handlers/logs.handler.ts +2 -2
  134. package/ts/opsserver/handlers/realtime.handler.ts +222 -0
  135. package/ts/opsserver/handlers/remoteingress.handler.ts +21 -0
  136. package/ts/opsserver/handlers/route-management.handler.ts +46 -4
  137. package/ts/opsserver/handlers/stats.handler.ts +2 -4
  138. package/ts/paths.ts +0 -25
  139. package/ts/plugins.ts +2 -69
  140. package/ts_web/00_commitinfo_data.ts +1 -1
  141. package/ts_web/appstate/certificates.ts +6 -1
  142. package/ts_web/appstate/domains.ts +11 -4
  143. package/ts_web/appstate/email-domains.ts +68 -14
  144. package/ts_web/appstate/email-ops.ts +20 -1
  145. package/ts_web/appstate/realtime.ts +109 -0
  146. package/ts_web/appstate/remoteingress.ts +6 -2
  147. package/ts_web/appstate/routes.ts +11 -4
  148. package/ts_web/appstate/runtime.ts +176 -7
  149. package/ts_web/appstate.ts +1 -0
  150. package/ts_web/elements/email/email-domain-creation-progress.ts +340 -0
  151. package/ts_web/elements/email/ops-view-email-domains.ts +273 -74
  152. package/ts_web/elements/email/ops-view-emails.ts +23 -16
  153. package/ts_web/elements/overview/ops-view-config.ts +4 -4
@@ -10,7 +10,13 @@ import {
10
10
  import * as appstate from '../../appstate.js';
11
11
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
12
12
  import { viewHostCss } from '../shared/css.js';
13
- import { type IStatsTile } from '@design.estate/dees-catalog';
13
+ import { type IStep, type IStatsTile } from '@design.estate/dees-catalog';
14
+ import {
15
+ findNewEmailDomain,
16
+ getEmailDomainCreationProgress,
17
+ observeNewEmailDomain,
18
+ withAbortableDeadline,
19
+ } from './email-domain-creation-progress.js';
14
20
 
15
21
  declare global {
16
22
  interface HTMLElementTagNameMap {
@@ -416,86 +422,279 @@ export class OpsViewEmailDomains extends DeesElement {
416
422
  }
417
423
 
418
424
  private async showCreateDialog() {
419
- const { DeesModal } = await import('@design.estate/dees-catalog');
420
- const domainOptions = this.domainsState.domains.map((d) => ({
421
- option: `${d.name} (${d.source})`,
422
- key: d.id,
425
+ const { DeesStepper } = await import('@design.estate/dees-catalog');
426
+ const domainOptions = this.domainsState.domains.map((domain) => ({
427
+ option: `${domain.name} (${domain.source})`,
428
+ key: domain.id,
423
429
  }));
430
+ const initialProgress = getEmailDomainCreationProgress({
431
+ requestCompleted: false,
432
+ });
424
433
 
425
- DeesModal.createAndShow({
426
- heading: 'Add Email Domain',
434
+ let createArgs: {
435
+ linkedDomainId: string;
436
+ subdomain?: string;
437
+ dkimSelector?: string;
438
+ dkimKeySize?: number;
439
+ rotateKeys?: boolean;
440
+ } | undefined;
441
+ let expectedDomain: string | undefined;
442
+ let progressStepDefinition: IStep;
443
+
444
+ progressStepDefinition = {
445
+ title: 'Create Email Domain',
446
+ allowBack: false,
427
447
  content: html`
428
- <dees-form>
429
- <dees-input-dropdown
430
- .key=${'linkedDomainId'}
431
- .label=${'Domain'}
432
- .description=${'Select an existing DNS domain'}
433
- .options=${domainOptions}
434
- .required=${true}
435
- ></dees-input-dropdown>
436
- <dees-input-text
437
- .key=${'subdomain'}
438
- .label=${'Subdomain'}
439
- .description=${'Leave empty for bare domain, e.g. "mail" for mail.example.com'}
440
- ></dees-input-text>
441
- <dees-input-text
442
- .key=${'dkimSelector'}
443
- .label=${'DKIM Selector'}
444
- .description=${'Identifier used in DNS record name'}
445
- .value=${'default'}
446
- ></dees-input-text>
447
- <dees-input-dropdown
448
- .key=${'dkimKeySize'}
449
- .label=${'DKIM Key Size'}
450
- .options=${[
451
- { option: '2048 (recommended)', key: '2048' },
452
- { option: '1024', key: '1024' },
453
- { option: '4096', key: '4096' },
454
- ]}
455
- .selectedOption=${{ option: '2048 (recommended)', key: '2048' }}
456
- ></dees-input-dropdown>
457
- <dees-input-checkbox
458
- .key=${'rotateKeys'}
459
- .label=${'Auto-rotate DKIM keys'}
460
- .value=${false}
461
- ></dees-input-checkbox>
462
- </dees-form>
448
+ <p style="margin: 0; color: var(--dees-color-text-secondary); line-height: 1.5;">
449
+ dcrouter is creating the domain, preparing its managed DNS plan, and reconciling the
450
+ records with the linked DNS provider. The final state and any scheduled retry remain
451
+ visible here.
452
+ </p>
463
453
  `,
464
- menuOptions: [
465
- { name: 'Cancel', action: async (m: any) => m.destroy() },
466
- {
467
- name: 'Create',
468
- action: async (m: any) => {
469
- const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
470
- if (!form) return;
471
- const data = await form.collectFormData();
472
- const linkedDomainId =
473
- typeof data.linkedDomainId === 'object'
474
- ? data.linkedDomainId.key
475
- : data.linkedDomainId;
476
- const keySize =
477
- typeof data.dkimKeySize === 'object'
478
- ? parseInt(data.dkimKeySize.key, 10)
479
- : parseInt(data.dkimKeySize || '2048', 10);
480
-
481
- const subdomain = data.subdomain?.trim() || undefined;
482
- const nextState = await appstate.emailDomainsStatePart.dispatchAction(
483
- appstate.createEmailDomainAction,
454
+ progressStep: {
455
+ ...initialProgress,
456
+ label: 'Email domain setup',
457
+ statusRows: 8,
458
+ autoAdvance: false,
459
+ },
460
+ menuOptions: [],
461
+ validationFunc: async (stepper, _selectedStep, signal) => {
462
+ let observedDomain: interfaces.data.IEmailDomain | undefined;
463
+ let observationError: Error | undefined;
464
+ let observationPromise: Promise<interfaces.data.IEmailDomain | undefined> | undefined;
465
+ const observationController = new AbortController();
466
+ const abortObservation = () => observationController.abort();
467
+ signal?.addEventListener('abort', abortObservation, { once: true });
468
+
469
+ const renderProgress = (
470
+ domainArg: interfaces.data.IEmailDomain | undefined,
471
+ operationArg: interfaces.requests.IEmailDomainActionResult | null,
472
+ requestCompletedArg: boolean,
473
+ ) => {
474
+ if (signal?.aborted || !stepper.isConnected) return;
475
+ stepper.updateProgressStep({
476
+ ...getEmailDomainCreationProgress({
477
+ domain: domainArg,
478
+ operation: operationArg,
479
+ requestCompleted: requestCompletedArg,
480
+ }),
481
+ statusRows: 8,
482
+ });
483
+ };
484
+
485
+ try {
486
+ if (!createArgs || !expectedDomain) {
487
+ throw new Error('Email domain configuration is incomplete');
488
+ }
489
+
490
+ stepper.updateProgressStep({
491
+ percentage: 0,
492
+ indeterminate: true,
493
+ showPercentage: false,
494
+ statusText: 'Checking the current email-domain state...',
495
+ terminalLines: ['Checking for an existing email domain'],
496
+ statusRows: 8,
497
+ });
498
+ const initialDomains = await appstate.fetchEmailDomainsSnapshot();
499
+ if (signal?.aborted) return;
500
+ const existingIds = new Set(
501
+ initialDomains
502
+ .filter((domain) => domain.domain.toLowerCase() === expectedDomain)
503
+ .map((domain) => domain.id),
504
+ );
505
+
506
+ renderProgress(undefined, null, false);
507
+ observationPromise = observeNewEmailDomain({
508
+ expectedDomain,
509
+ existingIds,
510
+ signal: observationController.signal,
511
+ listDomains: appstate.fetchEmailDomainsSnapshot,
512
+ getDomain: appstate.fetchEmailDomainSnapshot,
513
+ onDomain: (domain) => {
514
+ observedDomain = domain;
515
+ renderProgress(domain, null, false);
516
+ },
517
+ subscribeToDomains: (listenerArg) => {
518
+ const subscription = appstate.emailDomainsStatePart
519
+ .select((state) => state.domains)
520
+ .subscribe(listenerArg);
521
+ return () => subscription.unsubscribe();
522
+ },
523
+ timeoutMs: 15 * 60_000,
524
+ });
525
+ void observationPromise.then((domain) => {
526
+ if (domain) observedDomain = domain;
527
+ }).catch((error: unknown) => {
528
+ observationError = error instanceof Error
529
+ ? error
530
+ : new Error('Failed to observe the new email domain');
531
+ });
532
+
533
+ const createPromise = appstate.emailDomainsStatePart.dispatchAction(
534
+ appstate.createEmailDomainAction,
535
+ createArgs,
536
+ );
537
+ const nextState = await withAbortableDeadline(createPromise, {
538
+ signal,
539
+ timeoutMs: 120_000,
540
+ timeoutMessage:
541
+ 'Email domain creation timed out. The server operation may still complete; refresh the domain list before retrying.',
542
+ });
543
+ const operation = nextState.lastOperation;
544
+ const finalDomain =
545
+ operation?.domain ||
546
+ observedDomain ||
547
+ findNewEmailDomain(nextState.domains, expectedDomain, existingIds);
548
+ if (!finalDomain && observationError) {
549
+ throw observationError;
550
+ }
551
+ renderProgress(finalDomain, operation, true);
552
+ if (!signal?.aborted && stepper.isConnected) {
553
+ progressStepDefinition.menuOptions = [
484
554
  {
485
- linkedDomainId,
486
- subdomain,
487
- dkimSelector: data.dkimSelector || 'default',
488
- dkimKeySize: keySize,
489
- rotateKeys: Boolean(data.rotateKeys),
555
+ name: 'Close',
556
+ action: async (stepperArg) => stepperArg?.destroy(),
490
557
  },
491
- );
492
- const wasAccepted = await this.showOperationToast(
493
- nextState,
494
- `Email domain created`,
495
- );
496
- if (wasAccepted) m.destroy();
497
- },
558
+ ];
559
+ stepper.steps = [...stepper.steps];
560
+ }
561
+
562
+ const lifecycleStatus = finalDomain?.reconciliation?.lifecycleStatus;
563
+ if (
564
+ observationPromise &&
565
+ lifecycleStatus !== 'active' &&
566
+ lifecycleStatus !== 'failed'
567
+ ) {
568
+ const terminalDomain = await observationPromise;
569
+ if (terminalDomain) {
570
+ observedDomain = terminalDomain;
571
+ renderProgress(terminalDomain, operation, true);
572
+ }
573
+ }
574
+ } catch (error: unknown) {
575
+ const message = error instanceof Error ? error.message : 'Email domain creation failed';
576
+ renderProgress(
577
+ observedDomain,
578
+ {
579
+ success: false,
580
+ message,
581
+ code: 'EMAIL_DOMAIN_CREATE_PROGRESS_FAILED',
582
+ errors: [{
583
+ code: 'EMAIL_DOMAIN_CREATE_PROGRESS_FAILED',
584
+ message,
585
+ retryable: true,
586
+ }],
587
+ },
588
+ true,
589
+ );
590
+ } finally {
591
+ observationController.abort();
592
+ signal?.removeEventListener('abort', abortObservation);
593
+ if (!signal?.aborted && stepper.isConnected) {
594
+ progressStepDefinition.menuOptions = [
595
+ {
596
+ name: 'Close',
597
+ action: async (stepperArg) => stepperArg?.destroy(),
598
+ },
599
+ ];
600
+ stepper.steps = [...stepper.steps];
601
+ }
602
+ }
603
+ },
604
+ };
605
+
606
+ await DeesStepper.createAndShow({
607
+ cancelable: false,
608
+ steps: [
609
+ {
610
+ title: 'Add Email Domain',
611
+ content: html`
612
+ <dees-form>
613
+ <dees-input-dropdown
614
+ .key=${'linkedDomainId'}
615
+ .label=${'Domain'}
616
+ .description=${'Select an existing DNS domain'}
617
+ .options=${domainOptions}
618
+ .required=${true}
619
+ ></dees-input-dropdown>
620
+ <dees-input-text
621
+ .key=${'subdomain'}
622
+ .label=${'Subdomain'}
623
+ .description=${'Leave empty for bare domain, e.g. "mail" for mail.example.com'}
624
+ ></dees-input-text>
625
+ <dees-input-text
626
+ .key=${'dkimSelector'}
627
+ .label=${'DKIM Selector'}
628
+ .description=${'Identifier used in DNS record name'}
629
+ .value=${'default'}
630
+ ></dees-input-text>
631
+ <dees-input-dropdown
632
+ .key=${'dkimKeySize'}
633
+ .label=${'DKIM Key Size'}
634
+ .options=${[
635
+ { option: '2048 (recommended)', key: '2048' },
636
+ { option: '1024', key: '1024' },
637
+ { option: '4096', key: '4096' },
638
+ ]}
639
+ .selectedOption=${{ option: '2048 (recommended)', key: '2048' }}
640
+ ></dees-input-dropdown>
641
+ <dees-input-checkbox
642
+ .key=${'rotateKeys'}
643
+ .label=${'Auto-rotate DKIM keys'}
644
+ .value=${false}
645
+ ></dees-input-checkbox>
646
+ </dees-form>
647
+ `,
648
+ menuOptions: [
649
+ {
650
+ name: 'Cancel',
651
+ action: async (stepperArg) => stepperArg?.destroy(),
652
+ },
653
+ {
654
+ name: 'Create',
655
+ action: async (stepperArg) => {
656
+ if (!stepperArg) return;
657
+ const form = stepperArg.shadowRoot?.querySelector('.selected dees-form') as any;
658
+ if (!form) return;
659
+ const data = await form.collectFormData();
660
+ const linkedDomainId = String(
661
+ typeof data.linkedDomainId === 'object'
662
+ ? data.linkedDomainId.key
663
+ : data.linkedDomainId || '',
664
+ );
665
+ const linkedDomain = this.domainsState.domains.find(
666
+ (domain) => domain.id === linkedDomainId,
667
+ );
668
+ if (!linkedDomain) {
669
+ form.setStatus?.('error', 'Select an existing DNS domain.');
670
+ return;
671
+ }
672
+
673
+ const keySize = Number.parseInt(
674
+ String(
675
+ typeof data.dkimKeySize === 'object'
676
+ ? data.dkimKeySize.key
677
+ : data.dkimKeySize || '2048',
678
+ ),
679
+ 10,
680
+ );
681
+ const subdomain = String(data.subdomain || '').trim().toLowerCase() || undefined;
682
+ expectedDomain = (
683
+ subdomain ? `${subdomain}.${linkedDomain.name}` : linkedDomain.name
684
+ ).toLowerCase();
685
+ createArgs = {
686
+ linkedDomainId,
687
+ subdomain,
688
+ dkimSelector: String(data.dkimSelector || 'default').trim() || 'default',
689
+ dkimKeySize: keySize,
690
+ rotateKeys: Boolean(data.rotateKeys),
691
+ };
692
+ stepperArg.goNext();
693
+ },
694
+ },
695
+ ],
498
696
  },
697
+ progressStepDefinition,
499
698
  ],
500
699
  });
501
700
  }
@@ -1,5 +1,4 @@
1
1
  import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
2
- import * as plugins from '../../plugins.js';
3
2
  import * as appstate from '../../appstate.js';
4
3
  import * as shared from '../shared/index.js';
5
4
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
@@ -25,11 +24,18 @@ export class OpsViewEmails extends DeesElement {
25
24
  accessor isLoading = false;
26
25
 
27
26
  private stateSubscription: any;
27
+ private lastEmailOpsUpdate = 0;
28
+ private detailRequestGeneration = 0;
28
29
 
29
30
  async connectedCallback() {
30
31
  await super.connectedCallback();
31
32
  this.stateSubscription = appstate.emailOpsStatePart.select().subscribe((state) => {
32
33
  this.emails = state.emails;
34
+ const hasNewSnapshot = state.lastUpdated > this.lastEmailOpsUpdate;
35
+ this.lastEmailOpsUpdate = Math.max(this.lastEmailOpsUpdate, state.lastUpdated);
36
+ if (hasNewSnapshot && this.selectedEmail) {
37
+ void this.refreshEmailDetail(this.selectedEmail.id);
38
+ }
33
39
  this.isLoading = state.isLoading;
34
40
  });
35
41
  // Initial fetch
@@ -80,29 +86,30 @@ export class OpsViewEmails extends DeesElement {
80
86
  `;
81
87
  }
82
88
 
83
- private async handleEmailClick(e: CustomEvent<interfaces.requests.IEmail>) {
84
- const emailSummary = e.detail;
89
+ private async refreshEmailDetail(emailIdArg: string, openDetailArg = false): Promise<void> {
90
+ const requestGeneration = ++this.detailRequestGeneration;
85
91
  try {
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
- });
95
-
96
- if (response.email) {
97
- this.selectedEmail = response.email;
98
- this.currentView = 'detail';
92
+ const email = await appstate.fetchEmailDetailSnapshot(emailIdArg);
93
+ if (requestGeneration !== this.detailRequestGeneration) return;
94
+ if (email) {
95
+ this.selectedEmail = email;
96
+ if (openDetailArg) {
97
+ this.currentView = 'detail';
98
+ }
99
+ } else if (this.selectedEmail?.id === emailIdArg) {
100
+ this.handleBack();
99
101
  }
100
102
  } catch (error) {
101
103
  console.error('Failed to fetch email detail:', error);
102
104
  }
103
105
  }
104
106
 
107
+ private async handleEmailClick(e: CustomEvent<interfaces.requests.IEmail>) {
108
+ await this.refreshEmailDetail(e.detail.id, true);
109
+ }
110
+
105
111
  private handleBack() {
112
+ this.detailRequestGeneration++;
106
113
  this.selectedEmail = null;
107
114
  this.currentView = 'list';
108
115
  }
@@ -114,18 +114,18 @@ export class OpsViewConfig extends DeesElement {
114
114
 
115
115
  const fields: IConfigField[] = [
116
116
  { key: 'Base Directory', value: sys.baseDir },
117
- { key: 'Data Directory', value: sys.dataDir },
117
+ { key: 'Legacy Migration Source', value: sys.dataDir },
118
118
  { key: 'Public IP', value: sys.publicIp },
119
119
  { key: 'Proxy IPs', value: proxyIpValues, type: 'pills' },
120
120
  { key: 'Uptime', value: this.formatUptime(sys.uptime) },
121
- { key: 'Storage Backend', value: sys.storageBackend, type: 'badge' },
122
- { key: 'Storage Path', value: sys.storagePath },
121
+ { key: 'Structured Data Backend', value: sys.storageBackend, type: 'badge' },
122
+ { key: 'Local SmartData Path', value: sys.storagePath },
123
123
  ];
124
124
 
125
125
  return html`
126
126
  <sz-config-section
127
127
  title="System"
128
- subtitle="Base paths and infrastructure"
128
+ subtitle="Runtime paths and managed persistence"
129
129
  icon="lucide:server"
130
130
  status="enabled"
131
131
  .fields=${fields}