@masterteam/system-settings 0.0.15 → 0.0.16

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,31 +1,137 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, signal, computed, input, ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
2
+ import { input, inject, computed, ChangeDetectionStrategy, Component, Injectable, signal, DestroyRef } from '@angular/core';
3
3
  import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { ActivatedRoute, Router } from '@angular/router';
5
5
  import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
6
+ import { Breadcrumb } from '@masterteam/components/breadcrumb';
6
7
  import { EmptyState } from '@masterteam/components/empty-state';
7
8
  import { Page } from '@masterteam/components/page';
8
9
  import { ToastService } from '@masterteam/components/toast';
10
+ import { Tooltip } from '@masterteam/components/tooltip';
11
+ import { EntityStatus } from '@masterteam/components/entities';
9
12
  import { HttpClient, HttpErrorResponse } from '@angular/common/http';
10
13
  import { mergeMap, of, throwError, catchError, map, switchMap, forkJoin, tap, finalize, take, timer } from 'rxjs';
11
- import { EMAIL_DELIVERY_OPTIONS, EMAIL_DELIVERY_ATTEMPT_STATUSES, EMAIL_DELIVERY_PERMISSIONS } from './masterteam-system-settings.mjs';
14
+ import { EMAIL_DELIVERY_OPTIONS, isConnectionTestAttempt, EMAIL_DELIVERY_ATTEMPT_STATUSES, EMAIL_DELIVERY_PERMISSIONS } from './masterteam-system-settings.mjs';
12
15
  import { Icon } from '@masterteam/icons';
13
- import * as i1 from '@angular/forms';
14
- import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
15
16
  import { Button } from '@masterteam/components/button';
16
17
  import { ModalService } from '@masterteam/components/modal';
17
- import { SelectField } from '@masterteam/components/select-field';
18
18
  import { Table } from '@masterteam/components/table';
19
- import { EntityStatus } from '@masterteam/components/entities';
20
19
  import { DatePipe, DOCUMENT } from '@angular/common';
21
- import { Card } from '@masterteam/components/card';
22
20
  import { ModalRef } from '@masterteam/components/dialog';
21
+ import * as i1 from '@angular/forms';
22
+ import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
23
23
  import { TextareaField } from '@masterteam/components/textarea-field';
24
24
  import { ToggleField } from '@masterteam/components/toggle-field';
25
25
  import { ConfirmationService } from '@masterteam/components/confirmation';
26
26
  import { TextareaFieldConfig, TextFieldConfig, ToggleFieldConfig, ValidatorConfig, SelectFieldConfig, RadioButtonFieldConfig, NumberFieldConfig } from '@masterteam/components';
27
27
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
28
28
  import { Menu } from '@masterteam/components/menu';
29
+ import * as i1$1 from 'primeng/skeleton';
30
+ import { SkeletonModule } from 'primeng/skeleton';
31
+
32
+ const STATUS_DEFINITIONS = {
33
+ // Page-level readiness, shown as a chip beside the page title instead of the
34
+ // banner that used to sit above every tab.
35
+ readiness: {
36
+ ready: status('#059669', 'readiness.ready'),
37
+ 'needs-setup': status('#D97706', 'readiness.needs-setup'),
38
+ unavailable: status('#64748B', 'readiness.unavailable'),
39
+ },
40
+ connection: {
41
+ Draft: status('#64748B', 'connection.draft'),
42
+ PendingAuthorization: status('#D97706', 'connection.pending-authorization'),
43
+ PendingValidation: status('#0284C7', 'connection.pending-validation'),
44
+ Ready: status('#059669', 'connection.ready'),
45
+ ReauthorizationRequired: status('#EA580C', 'connection.reauthorization-required'),
46
+ ConditionalAccessRequired: status('#EA580C', 'connection.conditional-access-required'),
47
+ Disabled: status('#64748B', 'connection.disabled'),
48
+ Invalid: status('#DC2626', 'connection.invalid'),
49
+ },
50
+ attempt: {
51
+ Reserved: status('#64748B', 'attempt.reserved'),
52
+ ProviderInvocationStarted: status('#0284C7', 'attempt.provider-invocation-started'),
53
+ ProviderInvocationDefinitivelyNotAccepted: status('#DC2626', 'attempt.provider-not-accepted'),
54
+ ProviderResultReconciliation: status('#7C3AED', 'attempt.reconciling'),
55
+ ProviderResultPending: status('#0284C7', 'attempt.pending'),
56
+ Blocked: status('#DC2626', 'attempt.blocked'),
57
+ RetryScheduled: status('#D97706', 'attempt.retry-scheduled'),
58
+ Accepted: status('#059669', 'attempt.accepted'),
59
+ OutcomeUnknown: status('#EA580C', 'attempt.outcome-unknown'),
60
+ FailedPermanent: status('#DC2626', 'attempt.failed-permanent'),
61
+ DeadLettered: status('#BE123C', 'attempt.dead-lettered'),
62
+ },
63
+ support: {
64
+ active: status('#D97706', 'support.active'),
65
+ 'repair-pending': status('#0284C7', 'support.repair-pending'),
66
+ repaired: status('#059669', 'support.repaired'),
67
+ 'acknowledgement-pending': status('#0284C7', 'support.acknowledgement-pending'),
68
+ acknowledged: status('#64748B', 'support.acknowledged'),
69
+ Owned: status('#059669', 'support.owned'),
70
+ owned: status('#059669', 'support.owned'),
71
+ Missing: status('#D97706', 'support.missing'),
72
+ missing: status('#D97706', 'support.missing'),
73
+ Conflict: status('#DC2626', 'support.conflict'),
74
+ conflict: status('#DC2626', 'support.conflict'),
75
+ IdentityIncomplete: status('#EA580C', 'support.identity-incomplete'),
76
+ identityincomplete: status('#EA580C', 'support.identity-incomplete'),
77
+ 'retryable-after-upgrade': status('#0284C7', 'support.retryable-after-upgrade'),
78
+ 'repair-required': status('#EA580C', 'support.repair-required'),
79
+ 'acknowledge-only': status('#64748B', 'support.acknowledge-only'),
80
+ configured: status('#059669', 'support.configured'),
81
+ 'not-configured': status('#64748B', 'support.not-configured'),
82
+ },
83
+ };
84
+ class EmailDeliveryStatus {
85
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
86
+ kind = input('support', ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
87
+ transloco = inject(TranslocoService);
88
+ activeLanguage = toSignal(this.transloco.langChanges$, {
89
+ initialValue: this.transloco.getActiveLang(),
90
+ });
91
+ statusData = computed(() => {
92
+ this.activeLanguage();
93
+ return {
94
+ name: '',
95
+ viewType: 'Status',
96
+ value: getEmailDeliveryStatusValue(this.kind(), this.value(), (key) => this.transloco.translate(key)),
97
+ configuration: { hideName: true },
98
+ };
99
+ }, ...(ngDevMode ? [{ debugName: "statusData" }] : /* istanbul ignore next */ []));
100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: EmailDeliveryStatus, isStandalone: true, selector: "mt-email-delivery-status", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-flex" }, ngImport: i0, template: `<mt-entity-status [data]="statusData()" />`, isInline: true, dependencies: [{ kind: "component", type: EntityStatus, selector: "mt-entity-status", inputs: ["data", "name", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
102
+ }
103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryStatus, decorators: [{
104
+ type: Component,
105
+ args: [{
106
+ selector: 'mt-email-delivery-status',
107
+ imports: [EntityStatus],
108
+ template: `<mt-entity-status [data]="statusData()" />`,
109
+ host: { class: 'inline-flex' },
110
+ changeDetection: ChangeDetectionStrategy.OnPush,
111
+ }]
112
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: false }] }] } });
113
+ function getEmailDeliveryStatusMap(kind, translate) {
114
+ return Object.fromEntries(Object.entries(STATUS_DEFINITIONS[kind]).map(([key, definition]) => [
115
+ key,
116
+ {
117
+ color: definition.color,
118
+ display: translate(`email-delivery.status.${definition.translationKey}`),
119
+ },
120
+ ]));
121
+ }
122
+ function getEmailDeliveryStatusValue(kind, value, translate) {
123
+ const definition = STATUS_DEFINITIONS[kind][value];
124
+ return {
125
+ key: value,
126
+ display: definition
127
+ ? translate(`email-delivery.status.${definition.translationKey}`)
128
+ : value,
129
+ color: definition?.color ?? '#64748B',
130
+ };
131
+ }
132
+ function status(color, translationKey) {
133
+ return { color, translationKey };
134
+ }
29
135
 
30
136
  class EmailDeliveryApi {
31
137
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryApi, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -73,10 +179,19 @@ class EmailDeliveryHttpApi extends EmailDeliveryApi {
73
179
  }
74
180
  sendConnectionTest(id, request) {
75
181
  return this.http
76
- .post(this.url(`connections/${encodeURIComponent(id)}/test`), request)
77
- .pipe(mergeMap((response) => isEmailSendResult(response)
78
- ? of(response)
79
- : throwError(() => response)), catchError((error) => {
182
+ .post(this.url(`connections/${encodeURIComponent(id)}/test`), request, { observe: 'response' })
183
+ .pipe(mergeMap((response) => {
184
+ const body = response.body;
185
+ if (isEmailSendResult(body))
186
+ return of(body);
187
+ // 202 means the send was accepted without a result on this response.
188
+ // The caller must read the outcome back with the same testRequestId;
189
+ // posting again would deliver a second real email.
190
+ if (response.status === 202) {
191
+ return throwError(() => body ?? PENDING_CONTROLLED_TEST);
192
+ }
193
+ return throwError(() => body);
194
+ }), catchError((error) => {
80
195
  const body = error instanceof HttpErrorResponse ? error.error : error;
81
196
  return isEmailSendResult(body) ? of(body) : throwError(() => error);
82
197
  }));
@@ -158,6 +273,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
158
273
  type: Injectable,
159
274
  args: [{ providedIn: 'root' }]
160
275
  }] });
276
+ /**
277
+ * Stand-in problem for a bare `202 Accepted` that carries no body of its own —
278
+ * it routes the dialog to retrieval by GUID, the same as the catalog problem
279
+ * the backend normally returns for an in-flight test.
280
+ */
281
+ const PENDING_CONTROLLED_TEST = {
282
+ code: 'email.delivery_test_request_in_progress',
283
+ category: 'pending',
284
+ retryable: true,
285
+ action: 'poll_status',
286
+ readinessImpact: 'unchanged',
287
+ correlationId: '',
288
+ retryAfterSeconds: null,
289
+ };
161
290
  function isEmailSendResult(value) {
162
291
  return (typeof value === 'object' &&
163
292
  value !== null &&
@@ -189,6 +318,7 @@ class EmailDeliveryFacade {
189
318
  transloco = inject(TranslocoService);
190
319
  toast = inject(ToastService);
191
320
  initialized = false;
321
+ attemptsLoaded = false;
192
322
  status = signal(null, ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
193
323
  providers = signal([], ...(ngDevMode ? [{ debugName: "providers" }] : /* istanbul ignore next */ []));
194
324
  errorCatalog = signal([], ...(ngDevMode ? [{ debugName: "errorCatalog" }] : /* istanbul ignore next */ []));
@@ -277,11 +407,18 @@ class EmailDeliveryFacade {
277
407
  sendConnectionTest(id, request) {
278
408
  this.mutationLoading.set('test');
279
409
  this.lastConnectionTestRequestId.set(request.testRequestId);
280
- return this.api.sendConnectionTest(id, request).pipe(tap((result) => this.lastConnectionTest.set(result)), switchMap((result) => this.refreshConnection(id).pipe(map(() => result))), finalize(() => this.mutationLoading.set(null)));
410
+ return this.api.sendConnectionTest(id, request).pipe(tap((result) => {
411
+ this.lastConnectionTest.set(result);
412
+ this.refreshActivity();
413
+ }), switchMap((result) => this.refreshConnection(id).pipe(map(() => result))), finalize(() => this.mutationLoading.set(null)));
281
414
  }
282
415
  retrieveConnectionTest(connectionId, testRequestId) {
283
416
  this.mutationLoading.set('retrieve-test');
284
- return this.api.getConnectionTest(connectionId, testRequestId).pipe(tap((stored) => this.lastConnectionTest.set(stored.result)), finalize(() => this.mutationLoading.set(null)));
417
+ return this.api.getConnectionTest(connectionId, testRequestId).pipe(tap((stored) => {
418
+ this.lastConnectionTest.set(stored.result);
419
+ if (stored.result)
420
+ this.refreshActivity();
421
+ }), finalize(() => this.mutationLoading.set(null)));
285
422
  }
286
423
  bindConnection(id, purpose = 'notifications') {
287
424
  this.mutationLoading.set('bind');
@@ -308,6 +445,7 @@ class EmailDeliveryFacade {
308
445
  if (reset)
309
446
  this.attemptStatusFilter.set(status);
310
447
  return this.api.getAttempts(status, cursor, 50).pipe(tap((page) => {
448
+ this.attemptsLoaded = true;
311
449
  this.attempts.set(reset ? page.items : mergeById(this.attempts(), page.items, 'id'));
312
450
  this.attemptsNextCursor.set(page.nextCursor);
313
451
  }), finalize(() => this.attemptsLoading.set(false)));
@@ -420,6 +558,17 @@ class EmailDeliveryFacade {
420
558
  sessionStorage.removeItem(PENDING_AUTHORIZATION_STORAGE_KEY);
421
559
  return id;
422
560
  }
561
+ /**
562
+ * A completed controlled test writes its own Activity row, so the loaded
563
+ * Activity list is stale the moment the test returns. Only refresh once
564
+ * Activity has been opened — before that its own load covers it, and the
565
+ * status filter in effect there is preserved.
566
+ */
567
+ refreshActivity() {
568
+ if (!this.attemptsLoaded)
569
+ return;
570
+ this.loadAttempts(true).subscribe({ error: () => undefined });
571
+ }
423
572
  refreshConnection(id) {
424
573
  return forkJoin({
425
574
  details: this.api.getConnection(id),
@@ -475,103 +624,6 @@ function mergeHostEvidence(current, incoming) {
475
624
  return Array.from(values.values());
476
625
  }
477
626
 
478
- const STATUS_DEFINITIONS = {
479
- connection: {
480
- Draft: status('#64748B', 'connection.draft'),
481
- PendingAuthorization: status('#D97706', 'connection.pending-authorization'),
482
- PendingValidation: status('#0284C7', 'connection.pending-validation'),
483
- Ready: status('#059669', 'connection.ready'),
484
- ReauthorizationRequired: status('#EA580C', 'connection.reauthorization-required'),
485
- ConditionalAccessRequired: status('#EA580C', 'connection.conditional-access-required'),
486
- Disabled: status('#64748B', 'connection.disabled'),
487
- Invalid: status('#DC2626', 'connection.invalid'),
488
- },
489
- attempt: {
490
- Reserved: status('#64748B', 'attempt.reserved'),
491
- ProviderInvocationStarted: status('#0284C7', 'attempt.provider-invocation-started'),
492
- ProviderInvocationDefinitivelyNotAccepted: status('#DC2626', 'attempt.provider-not-accepted'),
493
- ProviderResultReconciliation: status('#7C3AED', 'attempt.reconciling'),
494
- ProviderResultPending: status('#0284C7', 'attempt.pending'),
495
- Blocked: status('#DC2626', 'attempt.blocked'),
496
- RetryScheduled: status('#D97706', 'attempt.retry-scheduled'),
497
- Accepted: status('#059669', 'attempt.accepted'),
498
- OutcomeUnknown: status('#EA580C', 'attempt.outcome-unknown'),
499
- FailedPermanent: status('#DC2626', 'attempt.failed-permanent'),
500
- DeadLettered: status('#BE123C', 'attempt.dead-lettered'),
501
- },
502
- support: {
503
- active: status('#D97706', 'support.active'),
504
- 'repair-pending': status('#0284C7', 'support.repair-pending'),
505
- repaired: status('#059669', 'support.repaired'),
506
- 'acknowledgement-pending': status('#0284C7', 'support.acknowledgement-pending'),
507
- acknowledged: status('#64748B', 'support.acknowledged'),
508
- Owned: status('#059669', 'support.owned'),
509
- owned: status('#059669', 'support.owned'),
510
- Missing: status('#D97706', 'support.missing'),
511
- missing: status('#D97706', 'support.missing'),
512
- Conflict: status('#DC2626', 'support.conflict'),
513
- conflict: status('#DC2626', 'support.conflict'),
514
- IdentityIncomplete: status('#EA580C', 'support.identity-incomplete'),
515
- identityincomplete: status('#EA580C', 'support.identity-incomplete'),
516
- 'retryable-after-upgrade': status('#0284C7', 'support.retryable-after-upgrade'),
517
- 'repair-required': status('#EA580C', 'support.repair-required'),
518
- 'acknowledge-only': status('#64748B', 'support.acknowledge-only'),
519
- configured: status('#059669', 'support.configured'),
520
- 'not-configured': status('#64748B', 'support.not-configured'),
521
- },
522
- };
523
- class EmailDeliveryStatus {
524
- value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
525
- kind = input('support', ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
526
- transloco = inject(TranslocoService);
527
- activeLanguage = toSignal(this.transloco.langChanges$, {
528
- initialValue: this.transloco.getActiveLang(),
529
- });
530
- statusData = computed(() => {
531
- this.activeLanguage();
532
- return {
533
- name: '',
534
- viewType: 'Status',
535
- value: getEmailDeliveryStatusValue(this.kind(), this.value(), (key) => this.transloco.translate(key)),
536
- configuration: { hideName: true },
537
- };
538
- }, ...(ngDevMode ? [{ debugName: "statusData" }] : /* istanbul ignore next */ []));
539
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
540
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: EmailDeliveryStatus, isStandalone: true, selector: "mt-email-delivery-status", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "inline-flex" }, ngImport: i0, template: `<mt-entity-status [data]="statusData()" />`, isInline: true, dependencies: [{ kind: "component", type: EntityStatus, selector: "mt-entity-status", inputs: ["data", "name", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
541
- }
542
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryStatus, decorators: [{
543
- type: Component,
544
- args: [{
545
- selector: 'mt-email-delivery-status',
546
- imports: [EntityStatus],
547
- template: `<mt-entity-status [data]="statusData()" />`,
548
- host: { class: 'inline-flex' },
549
- changeDetection: ChangeDetectionStrategy.OnPush,
550
- }]
551
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: false }] }] } });
552
- function getEmailDeliveryStatusMap(kind, translate) {
553
- return Object.fromEntries(Object.entries(STATUS_DEFINITIONS[kind]).map(([key, definition]) => [
554
- key,
555
- {
556
- color: definition.color,
557
- display: translate(`email-delivery.status.${definition.translationKey}`),
558
- },
559
- ]));
560
- }
561
- function getEmailDeliveryStatusValue(kind, value, translate) {
562
- const definition = STATUS_DEFINITIONS[kind][value];
563
- return {
564
- key: value,
565
- display: definition
566
- ? translate(`email-delivery.status.${definition.translationKey}`)
567
- : value,
568
- color: definition?.color ?? '#64748B',
569
- };
570
- }
571
- function status(color, translationKey) {
572
- return { color, translationKey };
573
- }
574
-
575
627
  class EmailOperatorReasonDialog {
576
628
  itemName = input.required(...(ngDevMode ? [{ debugName: "itemName" }] : /* istanbul ignore next */ []));
577
629
  consequence = input.required(...(ngDevMode ? [{ debugName: "consequence" }] : /* istanbul ignore next */ []));
@@ -605,7 +657,7 @@ class EmailOperatorReasonDialog {
605
657
  this.exceptionalOverride.getRawValue()));
606
658
  }
607
659
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailOperatorReasonDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
608
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailOperatorReasonDialog, isStandalone: true, selector: "mt-email-operator-reason-dialog", inputs: { itemName: { classPropertyName: "itemName", publicName: "itemName", isSignal: true, isRequired: true, transformFunction: null }, consequence: { classPropertyName: "consequence", publicName: "consequence", isSignal: true, isRequired: true, transformFunction: null }, showExceptionalOverride: { classPropertyName: "showExceptionalOverride", publicName: "showExceptionalOverride", isSignal: true, isRequired: false, transformFunction: null }, confirmLabel: { classPropertyName: "confirmLabel", publicName: "confirmLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <form\r\n [class]=\"'space-y-5 p-4 sm:p-5 ' + modal.contentClass\"\r\n (ngSubmit)=\"confirm()\"\r\n >\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-950\"\r\n role=\"note\"\r\n >\r\n <p class=\"m-0 break-words font-semibold\">{{ itemName() }}</p>\r\n <p class=\"mb-0 mt-1\">{{ consequence() }}</p>\r\n </div>\r\n\r\n <mt-textarea-field\r\n [formControl]=\"reason\"\r\n [label]=\"t('operator-reason')\"\r\n [hint]=\"t('operator-reason-hint')\"\r\n [required]=\"true\"\r\n [maxLength]=\"500\"\r\n rows=\"4\"\r\n />\r\n\r\n @if (showExceptionalOverride()) {\r\n <mt-toggle-field\r\n [formControl]=\"exceptionalOverride\"\r\n [label]=\"t('exceptional-override')\"\r\n [hint]=\"t('exceptional-override-hint')\"\r\n />\r\n }\r\n </form>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"cancel()\"\r\n />\r\n <mt-button\r\n type=\"button\"\r\n severity=\"warn\"\r\n [label]=\"confirmLabel() || t('confirm-action')\"\r\n [disabled]=\"!canConfirm()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"confirm()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
660
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailOperatorReasonDialog, isStandalone: true, selector: "mt-email-operator-reason-dialog", inputs: { itemName: { classPropertyName: "itemName", publicName: "itemName", isSignal: true, isRequired: true, transformFunction: null }, consequence: { classPropertyName: "consequence", publicName: "consequence", isSignal: true, isRequired: true, transformFunction: null }, showExceptionalOverride: { classPropertyName: "showExceptionalOverride", publicName: "showExceptionalOverride", isSignal: true, isRequired: false, transformFunction: null }, confirmLabel: { classPropertyName: "confirmLabel", publicName: "confirmLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <form\n [class]=\"'space-y-5 p-4 sm:p-5 ' + modal.contentClass\"\n (ngSubmit)=\"confirm()\"\n >\n <div\n class=\"rounded-md border border-amber-200 bg-amber-50 p-4 text-sm text-amber-950\"\n role=\"note\"\n >\n <p class=\"m-0 break-words font-semibold\">{{ itemName() }}</p>\n <p class=\"mb-0 mt-1\">{{ consequence() }}</p>\n </div>\n\n <mt-textarea-field\n [formControl]=\"reason\"\n [label]=\"t('operator-reason')\"\n [hint]=\"t('operator-reason-hint')\"\n [required]=\"true\"\n [maxLength]=\"500\"\n rows=\"4\"\n />\n\n @if (showExceptionalOverride()) {\n <div class=\"flex flex-col gap-1\">\n <mt-toggle-field\n [formControl]=\"exceptionalOverride\"\n [label]=\"t('exceptional-override')\"\n />\n <!-- mt-toggle-field has no hint input, so the consequence of an\n exceptional replay is rendered here instead of silently dropped. -->\n <p class=\"m-0 text-xs text-muted-color\">\n {{ t(\"exceptional-override-hint\") }}\n </p>\n </div>\n }\n </form>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n type=\"button\"\n [label]=\"t('cancel')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"cancel()\"\n />\n <mt-button\n type=\"button\"\n severity=\"warn\"\n [label]=\"confirmLabel() || t('confirm-action')\"\n [disabled]=\"!canConfirm()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"confirm()\"\n />\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: TextareaField, selector: "mt-textarea-field", inputs: ["field", "hint", "label", "placeholder", "class", "readonly", "noErrorStyle", "pInputs", "rows", "required", "maxLength"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "inputId", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
609
661
  }
610
662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailOperatorReasonDialog, decorators: [{
611
663
  type: Component,
@@ -615,7 +667,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
615
667
  Button,
616
668
  TextareaField,
617
669
  ToggleField,
618
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <form\r\n [class]=\"'space-y-5 p-4 sm:p-5 ' + modal.contentClass\"\r\n (ngSubmit)=\"confirm()\"\r\n >\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-950\"\r\n role=\"note\"\r\n >\r\n <p class=\"m-0 break-words font-semibold\">{{ itemName() }}</p>\r\n <p class=\"mb-0 mt-1\">{{ consequence() }}</p>\r\n </div>\r\n\r\n <mt-textarea-field\r\n [formControl]=\"reason\"\r\n [label]=\"t('operator-reason')\"\r\n [hint]=\"t('operator-reason-hint')\"\r\n [required]=\"true\"\r\n [maxLength]=\"500\"\r\n rows=\"4\"\r\n />\r\n\r\n @if (showExceptionalOverride()) {\r\n <mt-toggle-field\r\n [formControl]=\"exceptionalOverride\"\r\n [label]=\"t('exceptional-override')\"\r\n [hint]=\"t('exceptional-override-hint')\"\r\n />\r\n }\r\n </form>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n type=\"button\"\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"cancel()\"\r\n />\r\n <mt-button\r\n type=\"button\"\r\n severity=\"warn\"\r\n [label]=\"confirmLabel() || t('confirm-action')\"\r\n [disabled]=\"!canConfirm()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"confirm()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
670
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <form\n [class]=\"'space-y-5 p-4 sm:p-5 ' + modal.contentClass\"\n (ngSubmit)=\"confirm()\"\n >\n <div\n class=\"rounded-md border border-amber-200 bg-amber-50 p-4 text-sm text-amber-950\"\n role=\"note\"\n >\n <p class=\"m-0 break-words font-semibold\">{{ itemName() }}</p>\n <p class=\"mb-0 mt-1\">{{ consequence() }}</p>\n </div>\n\n <mt-textarea-field\n [formControl]=\"reason\"\n [label]=\"t('operator-reason')\"\n [hint]=\"t('operator-reason-hint')\"\n [required]=\"true\"\n [maxLength]=\"500\"\n rows=\"4\"\n />\n\n @if (showExceptionalOverride()) {\n <div class=\"flex flex-col gap-1\">\n <mt-toggle-field\n [formControl]=\"exceptionalOverride\"\n [label]=\"t('exceptional-override')\"\n />\n <!-- mt-toggle-field has no hint input, so the consequence of an\n exceptional replay is rendered here instead of silently dropped. -->\n <p class=\"m-0 text-xs text-muted-color\">\n {{ t(\"exceptional-override-hint\") }}\n </p>\n </div>\n }\n </form>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n type=\"button\"\n [label]=\"t('cancel')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"cancel()\"\n />\n <mt-button\n type=\"button\"\n severity=\"warn\"\n [label]=\"confirmLabel() || t('confirm-action')\"\n [disabled]=\"!canConfirm()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"confirm()\"\n />\n </div>\n</ng-container>\n" }]
619
671
  }], propDecorators: { itemName: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemName", required: true }] }], consequence: [{ type: i0.Input, args: [{ isSignal: true, alias: "consequence", required: true }] }], showExceptionalOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExceptionalOverride", required: false }] }], confirmLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmLabel", required: false }] }] } });
620
672
 
621
673
  class EmailAttemptDetails {
@@ -624,7 +676,17 @@ class EmailAttemptDetails {
624
676
  transloco = inject(TranslocoService);
625
677
  modal = inject(ModalService);
626
678
  facade = inject(EmailDeliveryFacade);
679
+ isConnectionTest() {
680
+ return isConnectionTestAttempt(this.attempt());
681
+ }
682
+ /**
683
+ * A controlled test is an operator action against one connection, not product
684
+ * work the platform owes anyone — replaying it would send a second real email
685
+ * nobody asked for, so the backend's test rows are never replayable.
686
+ */
627
687
  canReplay() {
688
+ if (this.isConnectionTest())
689
+ return false;
628
690
  return [
629
691
  'Blocked',
630
692
  'RetryScheduled',
@@ -676,52 +738,121 @@ class EmailAttemptDetails {
676
738
  return this.transloco.translate(`email-delivery.${key}`);
677
739
  }
678
740
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailAttemptDetails, deps: [], target: i0.ɵɵFactoryTarget.Component });
679
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailAttemptDetails, isStandalone: true, selector: "mt-email-attempt-details", inputs: { attempt: { classPropertyName: "attempt", publicName: "attempt", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n @if (attempt(); as item) {\r\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\r\n <div class=\"grid w-full gap-5\">\r\n <div\r\n class=\"rounded-lg border px-4 py-3 text-sm\"\r\n [class.border-amber-200]=\"item.status === 'OutcomeUnknown'\"\r\n [class.bg-amber-50]=\"item.status === 'OutcomeUnknown'\"\r\n [class.border-blue-200]=\"item.status !== 'OutcomeUnknown'\"\r\n [class.bg-blue-50]=\"item.status !== 'OutcomeUnknown'\"\r\n role=\"status\"\r\n >\r\n @if (item.status === \"OutcomeUnknown\") {\r\n {{ t(\"outcome-unknown-copy\") }}\r\n } @else if (item.outcome === \"Accepted\") {\r\n {{ t(\"accepted-copy\") }}\r\n } @else {\r\n {{ facade.describeCode(item.resultCode) }}\r\n }\r\n </div>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"delivery-summary\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.status\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-2\">\r\n <mt-email-delivery-status\r\n kind=\"attempt\"\r\n [value]=\"item.status\"\r\n />\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.outcome\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-2\">\r\n @if (item.outcome) {\r\n <mt-email-delivery-status\r\n kind=\"attempt\"\r\n [value]=\"item.outcome\"\r\n />\r\n } @else {\r\n <span class=\"font-medium text-color\">\u2014</span>\r\n }\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.provider\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ providerLabel(item.providerKey) }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.created\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n <time [attr.datetime]=\"item.createdAtUtc\">{{\r\n item.createdAtUtc | date: \"medium\"\r\n }}</time>\r\n </dd>\r\n </div>\r\n </dl>\r\n </mt-card>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"technical-identifiers\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"recipient-fingerprint\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.recipientFingerprint }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">{{ t(\"attempt.id\") }}</dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.id }}\r\n </dd>\r\n </div>\r\n @if (item.correlationId) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"correlation-id\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.correlationId }}\r\n </dd>\r\n </div>\r\n }\r\n @if (item.providerRequestId) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"provider-request-id\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.providerRequestId }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('close')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (canReplay()) {\r\n <mt-button\r\n severity=\"warn\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"t('replay-attempt')\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n [loading]=\"facade.mutationLoading() === 'replay-' + item.id\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"replay()\"\r\n />\r\n }\r\n </div>\r\n }\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
741
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailAttemptDetails, isStandalone: true, selector: "mt-email-attempt-details", inputs: { attempt: { classPropertyName: "attempt", publicName: "attempt", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n @if (attempt(); as item) {\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\n <div class=\"grid w-full gap-5\">\n <div\n class=\"rounded-md border px-4 py-3 text-sm\"\n [class.border-amber-200]=\"item.status === 'OutcomeUnknown'\"\n [class.bg-amber-50]=\"item.status === 'OutcomeUnknown'\"\n [class.border-blue-200]=\"item.status !== 'OutcomeUnknown'\"\n [class.bg-blue-50]=\"item.status !== 'OutcomeUnknown'\"\n role=\"status\"\n >\n @if (item.status === \"OutcomeUnknown\") {\n {{ t(\"outcome-unknown-copy\") }}\n } @else if (item.outcome === \"Accepted\") {\n {{ t(\"accepted-copy\") }}\n } @else {\n {{ facade.describeCode(item.resultCode) }}\n }\n </div>\n\n <!-- Same section shape the connection form uses (mt-client-form section\n mode): header strip on a bordered, flat, rounded-md container. -->\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"delivery-summary\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.operation\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{\n isConnectionTest()\n ? t(\"operation.connection-test\")\n : item.operationKind\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.status\") }}\n </dt>\n <dd class=\"mb-0 mt-2\">\n <mt-email-delivery-status\n kind=\"attempt\"\n [value]=\"item.status\"\n />\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.outcome\") }}\n </dt>\n <dd class=\"mb-0 mt-2\">\n @if (item.outcome) {\n <mt-email-delivery-status\n kind=\"attempt\"\n [value]=\"item.outcome\"\n />\n } @else {\n <span class=\"font-medium text-color\">\u2014</span>\n }\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.provider\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ providerLabel(item.providerKey) }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.created\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n <time [attr.datetime]=\"item.createdAtUtc\">{{\n item.createdAtUtc | date: \"medium\"\n }}</time>\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.count\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{ item.attemptCount }}\n </dd>\n </div>\n </dl>\n </section>\n\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"technical-identifiers\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"recipient-fingerprint\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.recipientFingerprint }}\n </dd>\n <p class=\"mt-2 text-xs text-muted-color\">\n {{ t(\"recipient-fingerprint-hint\") }}\n </p>\n </div>\n @if (item.testRequestId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"test-request-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.testRequestId }}\n </dd>\n </div>\n }\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">{{ t(\"attempt.id\") }}</dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.id }}\n </dd>\n </div>\n @if (item.correlationId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"correlation-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.correlationId }}\n </dd>\n </div>\n }\n @if (item.providerRequestId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"provider-request-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.providerRequestId }}\n </dd>\n </div>\n }\n </dl>\n </section>\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('close')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (canReplay()) {\n <mt-button\n severity=\"warn\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('replay-attempt')\"\n [disabled]=\"!facade.managementAvailable()\"\n [loading]=\"facade.mutationLoading() === 'replay-' + item.id\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"replay()\"\n />\n }\n </div>\n }\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
680
742
  }
681
743
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailAttemptDetails, decorators: [{
682
744
  type: Component,
683
- args: [{ selector: 'mt-email-attempt-details', imports: [DatePipe, TranslocoDirective, Button, Card, EmailDeliveryStatus], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n @if (attempt(); as item) {\r\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\r\n <div class=\"grid w-full gap-5\">\r\n <div\r\n class=\"rounded-lg border px-4 py-3 text-sm\"\r\n [class.border-amber-200]=\"item.status === 'OutcomeUnknown'\"\r\n [class.bg-amber-50]=\"item.status === 'OutcomeUnknown'\"\r\n [class.border-blue-200]=\"item.status !== 'OutcomeUnknown'\"\r\n [class.bg-blue-50]=\"item.status !== 'OutcomeUnknown'\"\r\n role=\"status\"\r\n >\r\n @if (item.status === \"OutcomeUnknown\") {\r\n {{ t(\"outcome-unknown-copy\") }}\r\n } @else if (item.outcome === \"Accepted\") {\r\n {{ t(\"accepted-copy\") }}\r\n } @else {\r\n {{ facade.describeCode(item.resultCode) }}\r\n }\r\n </div>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"delivery-summary\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.status\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-2\">\r\n <mt-email-delivery-status\r\n kind=\"attempt\"\r\n [value]=\"item.status\"\r\n />\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.outcome\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-2\">\r\n @if (item.outcome) {\r\n <mt-email-delivery-status\r\n kind=\"attempt\"\r\n [value]=\"item.outcome\"\r\n />\r\n } @else {\r\n <span class=\"font-medium text-color\">\u2014</span>\r\n }\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.provider\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ providerLabel(item.providerKey) }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"attempt.created\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n <time [attr.datetime]=\"item.createdAtUtc\">{{\r\n item.createdAtUtc | date: \"medium\"\r\n }}</time>\r\n </dd>\r\n </div>\r\n </dl>\r\n </mt-card>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"technical-identifiers\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"recipient-fingerprint\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.recipientFingerprint }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">{{ t(\"attempt.id\") }}</dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.id }}\r\n </dd>\r\n </div>\r\n @if (item.correlationId) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"correlation-id\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.correlationId }}\r\n </dd>\r\n </div>\r\n }\r\n @if (item.providerRequestId) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"provider-request-id\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\r\n {{ item.providerRequestId }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('close')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (canReplay()) {\r\n <mt-button\r\n severity=\"warn\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"t('replay-attempt')\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n [loading]=\"facade.mutationLoading() === 'replay-' + item.id\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"replay()\"\r\n />\r\n }\r\n </div>\r\n }\r\n</ng-container>\r\n" }]
745
+ args: [{ selector: 'mt-email-attempt-details', imports: [DatePipe, TranslocoDirective, Button, EmailDeliveryStatus], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n @if (attempt(); as item) {\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\n <div class=\"grid w-full gap-5\">\n <div\n class=\"rounded-md border px-4 py-3 text-sm\"\n [class.border-amber-200]=\"item.status === 'OutcomeUnknown'\"\n [class.bg-amber-50]=\"item.status === 'OutcomeUnknown'\"\n [class.border-blue-200]=\"item.status !== 'OutcomeUnknown'\"\n [class.bg-blue-50]=\"item.status !== 'OutcomeUnknown'\"\n role=\"status\"\n >\n @if (item.status === \"OutcomeUnknown\") {\n {{ t(\"outcome-unknown-copy\") }}\n } @else if (item.outcome === \"Accepted\") {\n {{ t(\"accepted-copy\") }}\n } @else {\n {{ facade.describeCode(item.resultCode) }}\n }\n </div>\n\n <!-- Same section shape the connection form uses (mt-client-form section\n mode): header strip on a bordered, flat, rounded-md container. -->\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"delivery-summary\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.operation\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{\n isConnectionTest()\n ? t(\"operation.connection-test\")\n : item.operationKind\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.status\") }}\n </dt>\n <dd class=\"mb-0 mt-2\">\n <mt-email-delivery-status\n kind=\"attempt\"\n [value]=\"item.status\"\n />\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.outcome\") }}\n </dt>\n <dd class=\"mb-0 mt-2\">\n @if (item.outcome) {\n <mt-email-delivery-status\n kind=\"attempt\"\n [value]=\"item.outcome\"\n />\n } @else {\n <span class=\"font-medium text-color\">\u2014</span>\n }\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.provider\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ providerLabel(item.providerKey) }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.created\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n <time [attr.datetime]=\"item.createdAtUtc\">{{\n item.createdAtUtc | date: \"medium\"\n }}</time>\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"attempt.count\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{ item.attemptCount }}\n </dd>\n </div>\n </dl>\n </section>\n\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"technical-identifiers\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"recipient-fingerprint\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.recipientFingerprint }}\n </dd>\n <p class=\"mt-2 text-xs text-muted-color\">\n {{ t(\"recipient-fingerprint-hint\") }}\n </p>\n </div>\n @if (item.testRequestId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"test-request-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.testRequestId }}\n </dd>\n </div>\n }\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">{{ t(\"attempt.id\") }}</dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.id }}\n </dd>\n </div>\n @if (item.correlationId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"correlation-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.correlationId }}\n </dd>\n </div>\n }\n @if (item.providerRequestId) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"provider-request-id\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-all font-mono text-sm text-color\">\n {{ item.providerRequestId }}\n </dd>\n </div>\n }\n </dl>\n </section>\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('close')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (canReplay()) {\n <mt-button\n severity=\"warn\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('replay-attempt')\"\n [disabled]=\"!facade.managementAvailable()\"\n [loading]=\"facade.mutationLoading() === 'replay-' + item.id\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"replay()\"\n />\n }\n </div>\n }\n</ng-container>\n" }]
684
746
  }], propDecorators: { attempt: [{ type: i0.Input, args: [{ isSignal: true, alias: "attempt", required: true }] }] } });
685
747
  function isExceptionalReplay(status) {
686
748
  return status === 'Accepted' || status === 'OutcomeUnknown';
687
749
  }
688
750
 
751
+ /**
752
+ * Shared chrome for the Email Delivery overlays.
753
+ *
754
+ * Every drawer on this page opens over the page content (`appendTo:
755
+ * 'page-content'`), so it is positioned absolutely and drops its own shadow.
756
+ * It takes the full width only while there is no room to spare — from `lg` up
757
+ * it settles at half the page so the table stays readable behind it, which is
758
+ * also what keeps the forms and callouts inside from stretching into
759
+ * unreadably long lines.
760
+ */
761
+ const EMAIL_DELIVERY_DRAWER_STYLE_CLASS = '!absolute !shadow-none !w-full !max-w-full md:!w-[70%] lg:!w-[50%]';
762
+
689
763
  class EmailDeliveryAttempts {
690
764
  modal = inject(ModalService);
691
765
  transloco = inject(TranslocoService);
692
766
  facade = inject(EmailDeliveryFacade);
693
- status = new FormControl(null);
694
767
  activeLanguage = toSignal(this.transloco.langChanges$, {
695
768
  initialValue: this.transloco.getActiveLang(),
696
769
  });
697
- statusOptions = computed(() => {
770
+ /**
771
+ * Status is the one filter the backend understands (`?status=`), so it is
772
+ * held here and round-tripped into the query; every other column filters the
773
+ * rows already loaded.
774
+ */
775
+ tableFilters = signal({}, ...(ngDevMode ? [{ debugName: "tableFilters" }] : /* istanbul ignore next */ []));
776
+ /**
777
+ * Activity mixes product sends with the operator's own controlled tests. The
778
+ * table renders plain fields, so the labels are projected onto the row here;
779
+ * the extra properties are inert everywhere the raw attempt is used.
780
+ */
781
+ rows = computed(() => {
698
782
  this.activeLanguage();
699
- const statusMap = getEmailDeliveryStatusMap('attempt', this.transloco.translate.bind(this.transloco));
700
- return [
701
- { label: this.t('all-statuses'), value: null },
702
- ...EMAIL_DELIVERY_ATTEMPT_STATUSES.map((value) => ({
703
- label: statusMap[value]?.display ?? value,
704
- value,
705
- })),
706
- ];
707
- }, ...(ngDevMode ? [{ debugName: "statusOptions" }] : /* istanbul ignore next */ []));
783
+ return this.facade.attempts().map((attempt) => ({
784
+ ...attempt,
785
+ operationLabel: this.operationLabel(attempt),
786
+ providerLabel: this.facade.providerByKey(attempt.providerKey)?.displayName ??
787
+ attempt.providerKey ??
788
+ '—',
789
+ resultLabel: attempt.resultCode
790
+ ? this.facade.describeCode(attempt.resultCode)
791
+ : '—',
792
+ recipientFingerprintShort: shortenFingerprint(attempt.recipientFingerprint),
793
+ }));
794
+ }, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
708
795
  columns = computed(() => {
709
796
  this.activeLanguage();
797
+ const statusMap = getEmailDeliveryStatusMap('attempt', this.transloco.translate.bind(this.transloco));
710
798
  return [
711
799
  {
712
800
  key: 'status',
713
801
  label: this.t('attempt.status'),
714
802
  type: 'status',
715
- statusMap: getEmailDeliveryStatusMap('attempt', this.transloco.translate.bind(this.transloco)),
803
+ width: '15rem',
804
+ statusMap,
805
+ filterConfig: {
806
+ type: 'select',
807
+ multiple: false,
808
+ options: EMAIL_DELIVERY_ATTEMPT_STATUSES.map((value) => ({
809
+ label: statusMap[value]?.display ?? value,
810
+ value,
811
+ })),
812
+ },
813
+ },
814
+ {
815
+ key: 'operationLabel',
816
+ label: this.t('attempt.operation'),
817
+ width: '14rem',
818
+ filterConfig: {
819
+ type: 'select',
820
+ options: this.distinctOptions((row) => row.operationLabel),
821
+ },
822
+ },
823
+ {
824
+ key: 'providerLabel',
825
+ label: this.t('attempt.provider'),
826
+ width: '15rem',
827
+ filterConfig: {
828
+ type: 'select',
829
+ options: this.distinctOptions((row) => row.providerLabel),
830
+ },
831
+ },
832
+ {
833
+ key: 'resultLabel',
834
+ label: this.t('attempt.result'),
835
+ filterConfig: { type: 'text' },
836
+ },
837
+ {
838
+ key: 'recipientFingerprintShort',
839
+ label: this.t('recipient-fingerprint'),
840
+ width: '15rem',
841
+ filterable: false,
716
842
  },
717
- { key: 'providerKey', label: this.t('attempt.provider') },
718
- { key: 'recipientFingerprint', label: this.t('recipient-fingerprint') },
719
843
  {
720
844
  key: 'createdAtUtc',
721
845
  label: this.t('attempt.created'),
722
846
  type: 'dateTime',
847
+ width: '10rem',
848
+ filterConfig: { type: 'date' },
849
+ },
850
+ {
851
+ key: 'attemptCount',
852
+ label: this.t('attempt.count'),
853
+ type: 'number',
854
+ width: '7rem',
723
855
  },
724
- { key: 'attemptCount', label: this.t('attempt.count'), type: 'number' },
725
856
  ];
726
857
  }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
727
858
  actions = computed(() => {
@@ -737,7 +868,18 @@ class EmailDeliveryAttempts {
737
868
  ngOnInit() {
738
869
  this.load(true);
739
870
  }
740
- applyFilter() {
871
+ /**
872
+ * Only the status filter reaches the backend; the rest narrow the rows the
873
+ * table already holds, so nothing else needs a round trip.
874
+ */
875
+ onFiltersChange(filters) {
876
+ this.tableFilters.set(filters);
877
+ const status = readStatusFilter(filters['status']);
878
+ if (status === this.facade.attemptStatusFilter())
879
+ return;
880
+ this.load(true, status);
881
+ }
882
+ refresh() {
741
883
  this.load(true);
742
884
  }
743
885
  loadMore() {
@@ -746,7 +888,7 @@ class EmailDeliveryAttempts {
746
888
  openDetails(attempt) {
747
889
  const ref = this.modal.openModal(EmailAttemptDetails, 'drawer', {
748
890
  header: this.t('attempt-details'),
749
- styleClass: '!absolute !shadow-none !w-full !max-w-full',
891
+ styleClass: EMAIL_DELIVERY_DRAWER_STYLE_CLASS,
750
892
  position: 'end',
751
893
  appendTo: 'page-content',
752
894
  dismissableMask: true,
@@ -758,28 +900,62 @@ class EmailDeliveryAttempts {
758
900
  this.load(true);
759
901
  });
760
902
  }
761
- load(reset) {
762
- this.facade.loadAttempts(reset, this.status.getRawValue()).subscribe({
903
+ load(reset, status = this.facade.attemptStatusFilter()) {
904
+ this.facade.loadAttempts(reset, status).subscribe({
763
905
  error: (error) => this.facade.showError(error),
764
906
  });
765
907
  }
908
+ /**
909
+ * Select filters over locally-filtered columns only offer values that are
910
+ * actually present, so the operator never picks an option that empties the
911
+ * table.
912
+ */
913
+ distinctOptions(select) {
914
+ const values = new Set(this.rows().map(select).filter(Boolean));
915
+ return Array.from(values)
916
+ .sort((left, right) => left.localeCompare(right))
917
+ .map((value) => ({ label: value, value }));
918
+ }
919
+ /**
920
+ * Controlled tests are always shown as "Connection test"; anything else falls
921
+ * back to the backend's own operation kind rather than a guessed label.
922
+ */
923
+ operationLabel(attempt) {
924
+ return isConnectionTestAttempt(attempt)
925
+ ? this.t('operation.connection-test')
926
+ : attempt.operationKind;
927
+ }
766
928
  t(key) {
767
929
  return this.transloco.translate(`email-delivery.${key}`);
768
930
  }
769
931
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryAttempts, deps: [], target: i0.ɵɵFactoryTarget.Component });
770
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryAttempts, isStandalone: true, selector: "mt-email-delivery-attempts", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section class=\"space-y-4\" aria-labelledby=\"attempts-heading\">\r\n <div>\r\n <h2 id=\"attempts-heading\" class=\"text-lg font-semibold text-color\">\r\n {{ t(\"activity-title\") }}\r\n </h2>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"activity-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-end\">\r\n <mt-select-field\r\n class=\"w-full sm:w-72\"\r\n [formControl]=\"status\"\r\n [label]=\"t('attempt.status-filter')\"\r\n [options]=\"statusOptions()\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('apply-filter')\"\r\n (onClick)=\"applyFilter()\"\r\n />\r\n </div>\r\n\r\n @if (!facade.attemptsLoading() && facade.attempts().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-attempts-title')\"\r\n [description]=\"t('no-attempts-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"facade.attempts()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.attemptsLoading()\"\r\n [generalSearch]=\"true\"\r\n dataKey=\"id\"\r\n actionShape=\"popover\"\r\n [clickableRows]=\"true\"\r\n (rowClick)=\"openDetails($event)\"\r\n />\r\n }\r\n\r\n @if (facade.attemptsNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.attemptsLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
932
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryAttempts, isStandalone: true, selector: "mt-email-delivery-attempts", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-4\" aria-label=\"{{ t('activity-title') }}\">\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [filters]=\"tableFilters()\"\n [loading]=\"facade.attemptsLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-attempts-title')\"\n [emptyDescription]=\"t('no-attempts-description')\"\n filterMode=\"column\"\n dataKey=\"id\"\n actionShape=\"popover\"\n [clickableRows]=\"true\"\n storageKey=\"email-delivery-attempts\"\n (filtersChange)=\"onFiltersChange($event)\"\n (rowClick)=\"openDetails($event)\"\n >\n <ng-template #captionEnd>\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('refresh')\"\n [loading]=\"facade.attemptsLoading()\"\n (onClick)=\"refresh()\"\n />\n </ng-template>\n </mt-table>\n\n @if (facade.attemptsNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.attemptsLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
771
933
  }
772
934
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryAttempts, decorators: [{
773
935
  type: Component,
774
- args: [{ selector: 'mt-email-delivery-attempts', imports: [
775
- ReactiveFormsModule,
776
- TranslocoDirective,
777
- Button,
778
- EmptyState,
779
- SelectField,
780
- Table,
781
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section class=\"space-y-4\" aria-labelledby=\"attempts-heading\">\r\n <div>\r\n <h2 id=\"attempts-heading\" class=\"text-lg font-semibold text-color\">\r\n {{ t(\"activity-title\") }}\r\n </h2>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"activity-description\") }}\r\n </p>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-3 sm:flex-row sm:items-end\">\r\n <mt-select-field\r\n class=\"w-full sm:w-72\"\r\n [formControl]=\"status\"\r\n [label]=\"t('attempt.status-filter')\"\r\n [options]=\"statusOptions()\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n />\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('apply-filter')\"\r\n (onClick)=\"applyFilter()\"\r\n />\r\n </div>\r\n\r\n @if (!facade.attemptsLoading() && facade.attempts().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-attempts-title')\"\r\n [description]=\"t('no-attempts-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"facade.attempts()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.attemptsLoading()\"\r\n [generalSearch]=\"true\"\r\n dataKey=\"id\"\r\n actionShape=\"popover\"\r\n [clickableRows]=\"true\"\r\n (rowClick)=\"openDetails($event)\"\r\n />\r\n }\r\n\r\n @if (facade.attemptsNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.attemptsLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n" }]
936
+ args: [{ selector: 'mt-email-delivery-attempts', imports: [TranslocoDirective, Button, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-4\" aria-label=\"{{ t('activity-title') }}\">\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [filters]=\"tableFilters()\"\n [loading]=\"facade.attemptsLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-attempts-title')\"\n [emptyDescription]=\"t('no-attempts-description')\"\n filterMode=\"column\"\n dataKey=\"id\"\n actionShape=\"popover\"\n [clickableRows]=\"true\"\n storageKey=\"email-delivery-attempts\"\n (filtersChange)=\"onFiltersChange($event)\"\n (rowClick)=\"openDetails($event)\"\n >\n <ng-template #captionEnd>\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('refresh')\"\n [loading]=\"facade.attemptsLoading()\"\n (onClick)=\"refresh()\"\n />\n </ng-template>\n </mt-table>\n\n @if (facade.attemptsNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.attemptsLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n" }]
782
937
  }] });
938
+ /**
939
+ * The status column is a single-select, so its stored value is the raw status.
940
+ * An empty selection clears the backend filter rather than sending a blank.
941
+ */
942
+ function readStatusFilter(value) {
943
+ const raw = Array.isArray(value) ? value[0] : value;
944
+ return typeof raw === 'string' && raw.trim() !== '' ? raw : null;
945
+ }
946
+ const FINGERPRINT_PREFIX_LENGTH = 12;
947
+ /**
948
+ * A 64-character hash has nowhere to wrap, so rendering it whole pushed its
949
+ * column far past the width the table asked for. The list only needs enough of
950
+ * it to tell two recipients apart; the drawer still shows the value in full.
951
+ */
952
+ function shortenFingerprint(value) {
953
+ if (!value)
954
+ return '—';
955
+ return value.length > FINGERPRINT_PREFIX_LENGTH
956
+ ? value.slice(0, FINGERPRINT_PREFIX_LENGTH) + '…'
957
+ : value;
958
+ }
783
959
 
784
960
  class EmailDeliveryHostEvidence {
785
961
  document = inject(DOCUMENT);
@@ -801,30 +977,37 @@ class EmailDeliveryHostEvidence {
801
977
  this.activeLanguage();
802
978
  const statusMap = getEmailDeliveryStatusMap('support', this.transloco.translate.bind(this.transloco));
803
979
  return [
804
- { key: 'evidenceKindLabel', label: this.t('evidence-kind') },
980
+ {
981
+ key: 'evidenceKindLabel',
982
+ label: this.t('evidence-kind'),
983
+ width: '11rem',
984
+ },
805
985
  {
806
986
  key: 'resolutionState',
807
987
  label: this.t('resolution-state'),
808
988
  type: 'status',
989
+ width: '12rem',
809
990
  statusMap,
810
991
  },
811
992
  {
812
993
  key: 'ownershipState',
813
994
  label: this.t('ownership-state'),
814
995
  type: 'status',
996
+ width: '11rem',
815
997
  statusMap,
816
998
  },
817
999
  {
818
1000
  key: 'detectedAtUtc',
819
1001
  label: this.t('detected'),
820
1002
  type: 'dateTime',
1003
+ width: '10rem',
821
1004
  },
822
1005
  { key: 'errorLabel', label: this.t('issue') },
823
1006
  {
824
1007
  key: 'evidenceOccurrenceIdentity',
825
1008
  label: this.t('evidence-occurrence-id'),
826
1009
  type: 'entity',
827
- width: '20rem',
1010
+ width: '15rem',
828
1011
  shrink: true,
829
1012
  },
830
1013
  ];
@@ -933,11 +1116,11 @@ class EmailDeliveryHostEvidence {
933
1116
  return this.transloco.translate(`email-delivery.${key}`);
934
1117
  }
935
1118
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryHostEvidence, deps: [], target: i0.ɵɵFactoryTarget.Component });
936
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryHostEvidence, isStandalone: true, selector: "mt-email-delivery-host-evidence", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section\r\n class=\"space-y-4 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:p-5\"\r\n aria-labelledby=\"host-evidence-heading\"\r\n >\r\n <div\r\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\r\n >\r\n <div>\r\n <h3 id=\"host-evidence-heading\" class=\"font-semibold text-color\">\r\n {{ t(\"host-evidence-title\") }}\r\n </h3>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"host-evidence-description\") }}\r\n </p>\r\n </div>\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"t('rebuild-evidence-index')\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n [loading]=\"facade.mutationLoading() === 'rebuild-evidence'\"\r\n (onClick)=\"confirmRebuild()\"\r\n />\r\n </div>\r\n\r\n @if (facade.hostEvidenceScanBudgetExhausted()) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-950\"\r\n role=\"status\"\r\n >\r\n {{ t(\"scan-budget-exhausted\") }}\r\n </div>\r\n }\r\n\r\n @if (!facade.hostEvidenceLoading() && facade.hostEvidence().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-evidence-title')\"\r\n [description]=\"t('no-evidence-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.hostEvidenceLoading()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n dataKey=\"evidenceRowId\"\r\n storageKey=\"email-delivery-host-evidence\"\r\n />\r\n }\r\n\r\n @if (facade.hostEvidenceNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.hostEvidenceLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1119
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryHostEvidence, isStandalone: true, selector: "mt-email-delivery-host-evidence", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-3\" aria-labelledby=\"host-evidence-heading\">\n <div\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div>\n <h3 id=\"host-evidence-heading\" class=\"font-semibold text-color\">\n {{ t(\"host-evidence-title\") }}\n </h3>\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ t(\"host-evidence-description\") }}\n </p>\n </div>\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('rebuild-evidence-index')\"\n [disabled]=\"!facade.managementAvailable()\"\n [loading]=\"facade.mutationLoading() === 'rebuild-evidence'\"\n (onClick)=\"confirmRebuild()\"\n />\n </div>\n\n @if (facade.hostEvidenceScanBudgetExhausted()) {\n <div\n class=\"rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-950\"\n role=\"status\"\n >\n {{ t(\"scan-budget-exhausted\") }}\n </div>\n }\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [loading]=\"facade.hostEvidenceLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-evidence-title')\"\n [emptyDescription]=\"t('no-evidence-description')\"\n filterMode=\"column\"\n actionShape=\"popover\"\n dataKey=\"evidenceRowId\"\n storageKey=\"email-delivery-host-evidence\"\n />\n\n @if (facade.hostEvidenceNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.hostEvidenceLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
937
1120
  }
938
1121
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryHostEvidence, decorators: [{
939
1122
  type: Component,
940
- args: [{ selector: 'mt-email-delivery-host-evidence', imports: [TranslocoDirective, Button, EmptyState, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section\r\n class=\"space-y-4 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:p-5\"\r\n aria-labelledby=\"host-evidence-heading\"\r\n >\r\n <div\r\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\r\n >\r\n <div>\r\n <h3 id=\"host-evidence-heading\" class=\"font-semibold text-color\">\r\n {{ t(\"host-evidence-title\") }}\r\n </h3>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"host-evidence-description\") }}\r\n </p>\r\n </div>\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [label]=\"t('rebuild-evidence-index')\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n [loading]=\"facade.mutationLoading() === 'rebuild-evidence'\"\r\n (onClick)=\"confirmRebuild()\"\r\n />\r\n </div>\r\n\r\n @if (facade.hostEvidenceScanBudgetExhausted()) {\r\n <div\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-950\"\r\n role=\"status\"\r\n >\r\n {{ t(\"scan-budget-exhausted\") }}\r\n </div>\r\n }\r\n\r\n @if (!facade.hostEvidenceLoading() && facade.hostEvidence().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-evidence-title')\"\r\n [description]=\"t('no-evidence-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.hostEvidenceLoading()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n dataKey=\"evidenceRowId\"\r\n storageKey=\"email-delivery-host-evidence\"\r\n />\r\n }\r\n\r\n @if (facade.hostEvidenceNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.hostEvidenceLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n" }]
1123
+ args: [{ selector: 'mt-email-delivery-host-evidence', imports: [TranslocoDirective, Button, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-3\" aria-labelledby=\"host-evidence-heading\">\n <div\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div>\n <h3 id=\"host-evidence-heading\" class=\"font-semibold text-color\">\n {{ t(\"host-evidence-title\") }}\n </h3>\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ t(\"host-evidence-description\") }}\n </p>\n </div>\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"arrow.refresh-cw-01\"\n [label]=\"t('rebuild-evidence-index')\"\n [disabled]=\"!facade.managementAvailable()\"\n [loading]=\"facade.mutationLoading() === 'rebuild-evidence'\"\n (onClick)=\"confirmRebuild()\"\n />\n </div>\n\n @if (facade.hostEvidenceScanBudgetExhausted()) {\n <div\n class=\"rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-950\"\n role=\"status\"\n >\n {{ t(\"scan-budget-exhausted\") }}\n </div>\n }\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [loading]=\"facade.hostEvidenceLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-evidence-title')\"\n [emptyDescription]=\"t('no-evidence-description')\"\n filterMode=\"column\"\n actionShape=\"popover\"\n dataKey=\"evidenceRowId\"\n storageKey=\"email-delivery-host-evidence\"\n />\n\n @if (facade.hostEvidenceNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.hostEvidenceLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n" }]
941
1124
  }] });
942
1125
  function identifierEntity$1(value) {
943
1126
  return {
@@ -980,13 +1163,14 @@ class EmailDeliveryQuarantine {
980
1163
  key: 'attemptIdentity',
981
1164
  label: this.t('attempt.id'),
982
1165
  type: 'entity',
983
- width: '19rem',
1166
+ width: '15rem',
984
1167
  shrink: true,
985
1168
  },
986
1169
  {
987
1170
  key: 'recoveryClassification',
988
1171
  label: this.t('recovery-classification'),
989
1172
  type: 'status',
1173
+ width: '12rem',
990
1174
  statusMap: getEmailDeliveryStatusMap('support', this.transloco.translate.bind(this.transloco)),
991
1175
  },
992
1176
  { key: 'errorLabel', label: this.t('issue') },
@@ -994,6 +1178,7 @@ class EmailDeliveryQuarantine {
994
1178
  key: 'quarantinedAtUtc',
995
1179
  label: this.t('detected'),
996
1180
  type: 'dateTime',
1181
+ width: '10rem',
997
1182
  },
998
1183
  ];
999
1184
  }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
@@ -1056,11 +1241,11 @@ class EmailDeliveryQuarantine {
1056
1241
  return this.transloco.translate(`email-delivery.${key}`);
1057
1242
  }
1058
1243
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryQuarantine, deps: [], target: i0.ɵɵFactoryTarget.Component });
1059
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryQuarantine, isStandalone: true, selector: "mt-email-delivery-quarantine", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section\r\n class=\"space-y-4 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:p-5\"\r\n aria-labelledby=\"quarantine-heading\"\r\n >\r\n <div>\r\n <h3 id=\"quarantine-heading\" class=\"font-semibold text-color\">\r\n {{ t(\"quarantine-title\") }}\r\n </h3>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"quarantine-description\") }}\r\n </p>\r\n </div>\r\n\r\n @if (!facade.quarantineLoading() && facade.quarantine().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-quarantine-title')\"\r\n [description]=\"t('no-quarantine-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.quarantineLoading()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n dataKey=\"itemId\"\r\n storageKey=\"email-delivery-quarantine\"\r\n />\r\n }\r\n\r\n @if (facade.quarantineNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.quarantineLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1244
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryQuarantine, isStandalone: true, selector: "mt-email-delivery-quarantine", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-3\" aria-labelledby=\"quarantine-heading\">\n <div>\n <h3 id=\"quarantine-heading\" class=\"font-semibold text-color\">\n {{ t(\"quarantine-title\") }}\n </h3>\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ t(\"quarantine-description\") }}\n </p>\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [loading]=\"facade.quarantineLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-quarantine-title')\"\n [emptyDescription]=\"t('no-quarantine-description')\"\n filterMode=\"column\"\n actionShape=\"popover\"\n dataKey=\"itemId\"\n storageKey=\"email-delivery-quarantine\"\n />\n\n @if (facade.quarantineNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.quarantineLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1060
1245
  }
1061
1246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryQuarantine, decorators: [{
1062
1247
  type: Component,
1063
- args: [{ selector: 'mt-email-delivery-quarantine', imports: [TranslocoDirective, Button, EmptyState, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section\r\n class=\"space-y-4 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:p-5\"\r\n aria-labelledby=\"quarantine-heading\"\r\n >\r\n <div>\r\n <h3 id=\"quarantine-heading\" class=\"font-semibold text-color\">\r\n {{ t(\"quarantine-title\") }}\r\n </h3>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"quarantine-description\") }}\r\n </p>\r\n </div>\r\n\r\n @if (!facade.quarantineLoading() && facade.quarantine().length === 0) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-quarantine-title')\"\r\n [description]=\"t('no-quarantine-description')\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"actions()\"\r\n [loading]=\"facade.quarantineLoading()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n dataKey=\"itemId\"\r\n storageKey=\"email-delivery-quarantine\"\r\n />\r\n }\r\n\r\n @if (facade.quarantineNextCursor()) {\r\n <div class=\"flex justify-center\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('load-more')\"\r\n [loading]=\"facade.quarantineLoading()\"\r\n (onClick)=\"loadMore()\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n</ng-container>\r\n" }]
1248
+ args: [{ selector: 'mt-email-delivery-quarantine', imports: [TranslocoDirective, Button, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-3\" aria-labelledby=\"quarantine-heading\">\n <div>\n <h3 id=\"quarantine-heading\" class=\"font-semibold text-color\">\n {{ t(\"quarantine-title\") }}\n </h3>\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ t(\"quarantine-description\") }}\n </p>\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"actions()\"\n [loading]=\"facade.quarantineLoading()\"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-quarantine-title')\"\n [emptyDescription]=\"t('no-quarantine-description')\"\n filterMode=\"column\"\n actionShape=\"popover\"\n dataKey=\"itemId\"\n storageKey=\"email-delivery-quarantine\"\n />\n\n @if (facade.quarantineNextCursor()) {\n <div class=\"flex justify-center\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('load-more')\"\n [loading]=\"facade.quarantineLoading()\"\n (onClick)=\"loadMore()\"\n />\n </div>\n }\n </section>\n</ng-container>\n" }]
1064
1249
  }] });
1065
1250
  function identifierEntity(value) {
1066
1251
  return {
@@ -1077,7 +1262,7 @@ class EmailDeliveryActivity {
1077
1262
  this.advancedExpanded.set(event.currentTarget.open);
1078
1263
  }
1079
1264
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryActivity, deps: [], target: i0.ɵɵFactoryTarget.Component });
1080
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryActivity, isStandalone: true, selector: "mt-email-delivery-activity", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div class=\"grid gap-6\">\r\n <mt-email-delivery-attempts class=\"block min-w-0\" />\r\n\r\n <details\r\n class=\"group overflow-hidden rounded-xl border border-surface-200 bg-surface-0\"\r\n (toggle)=\"onAdvancedToggle($event)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer list-none p-5 font-semibold text-color focus-visible:outline-2 focus-visible:outline-offset-2\"\r\n >\r\n <span class=\"flex items-center justify-between gap-3\">\r\n <span>\r\n {{ t(\"advanced-support\") }}\r\n <span class=\"mt-1 block text-sm font-normal text-muted-color\">{{\r\n t(\"advanced-support-description\")\r\n }}</span>\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n aria-hidden=\"true\"\r\n class=\"text-base transition-transform group-open:rotate-180\"\r\n />\r\n </span>\r\n </summary>\r\n @if (advancedExpanded()) {\r\n <div\r\n class=\"grid gap-6 border-t border-surface-200 bg-surface-50/60 p-4 sm:p-5\"\r\n >\r\n <mt-email-delivery-quarantine class=\"block min-w-0\" />\r\n <mt-email-delivery-host-evidence class=\"block min-w-0\" />\r\n </div>\r\n }\r\n </details>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: EmailDeliveryAttempts, selector: "mt-email-delivery-attempts" }, { kind: "component", type: EmailDeliveryQuarantine, selector: "mt-email-delivery-quarantine" }, { kind: "component", type: EmailDeliveryHostEvidence, selector: "mt-email-delivery-host-evidence" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1265
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryActivity, isStandalone: true, selector: "mt-email-delivery-activity", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div class=\"grid gap-5\">\n <mt-email-delivery-attempts class=\"block min-w-0\" />\n\n <!-- Operator recovery tools. Kept collapsed and visually quieter than the\n Activity table above it: they are for incident work, not daily use. -->\n <details\n class=\"group overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n (toggle)=\"onAdvancedToggle($event)\"\n >\n <summary\n class=\"flex cursor-pointer list-none items-center gap-3 p-4 focus-visible:outline-2 focus-visible:outline-offset-2\"\n >\n <span\n class=\"inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-surface-100 text-muted-color\"\n >\n <mt-icon icon=\"general.tool-02\" aria-hidden=\"true\" />\n </span>\n <span class=\"min-w-0 flex-1\">\n <span class=\"block font-semibold text-color\">\n {{ t(\"advanced-support\") }}\n </span>\n <span class=\"mt-0.5 block text-sm font-normal text-muted-color\">\n {{ t(\"advanced-support-description\") }}\n </span>\n </span>\n <mt-icon\n icon=\"arrow.chevron-down\"\n aria-hidden=\"true\"\n class=\"shrink-0 text-base text-muted-color transition-transform group-open:rotate-180\"\n />\n </summary>\n @if (advancedExpanded()) {\n <div\n class=\"grid gap-4 border-t border-surface-200 bg-surface-50/60 p-4 sm:p-5\"\n >\n <mt-email-delivery-quarantine class=\"block min-w-0\" />\n <mt-email-delivery-host-evidence class=\"block min-w-0\" />\n </div>\n }\n </details>\n </div>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: EmailDeliveryAttempts, selector: "mt-email-delivery-attempts" }, { kind: "component", type: EmailDeliveryQuarantine, selector: "mt-email-delivery-quarantine" }, { kind: "component", type: EmailDeliveryHostEvidence, selector: "mt-email-delivery-host-evidence" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1081
1266
  }
1082
1267
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryActivity, decorators: [{
1083
1268
  type: Component,
@@ -1087,7 +1272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1087
1272
  EmailDeliveryAttempts,
1088
1273
  EmailDeliveryQuarantine,
1089
1274
  EmailDeliveryHostEvidence,
1090
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div class=\"grid gap-6\">\r\n <mt-email-delivery-attempts class=\"block min-w-0\" />\r\n\r\n <details\r\n class=\"group overflow-hidden rounded-xl border border-surface-200 bg-surface-0\"\r\n (toggle)=\"onAdvancedToggle($event)\"\r\n >\r\n <summary\r\n class=\"cursor-pointer list-none p-5 font-semibold text-color focus-visible:outline-2 focus-visible:outline-offset-2\"\r\n >\r\n <span class=\"flex items-center justify-between gap-3\">\r\n <span>\r\n {{ t(\"advanced-support\") }}\r\n <span class=\"mt-1 block text-sm font-normal text-muted-color\">{{\r\n t(\"advanced-support-description\")\r\n }}</span>\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n aria-hidden=\"true\"\r\n class=\"text-base transition-transform group-open:rotate-180\"\r\n />\r\n </span>\r\n </summary>\r\n @if (advancedExpanded()) {\r\n <div\r\n class=\"grid gap-6 border-t border-surface-200 bg-surface-50/60 p-4 sm:p-5\"\r\n >\r\n <mt-email-delivery-quarantine class=\"block min-w-0\" />\r\n <mt-email-delivery-host-evidence class=\"block min-w-0\" />\r\n </div>\r\n }\r\n </details>\r\n </div>\r\n</ng-container>\r\n" }]
1275
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div class=\"grid gap-5\">\n <mt-email-delivery-attempts class=\"block min-w-0\" />\n\n <!-- Operator recovery tools. Kept collapsed and visually quieter than the\n Activity table above it: they are for incident work, not daily use. -->\n <details\n class=\"group overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n (toggle)=\"onAdvancedToggle($event)\"\n >\n <summary\n class=\"flex cursor-pointer list-none items-center gap-3 p-4 focus-visible:outline-2 focus-visible:outline-offset-2\"\n >\n <span\n class=\"inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-surface-100 text-muted-color\"\n >\n <mt-icon icon=\"general.tool-02\" aria-hidden=\"true\" />\n </span>\n <span class=\"min-w-0 flex-1\">\n <span class=\"block font-semibold text-color\">\n {{ t(\"advanced-support\") }}\n </span>\n <span class=\"mt-0.5 block text-sm font-normal text-muted-color\">\n {{ t(\"advanced-support-description\") }}\n </span>\n </span>\n <mt-icon\n icon=\"arrow.chevron-down\"\n aria-hidden=\"true\"\n class=\"shrink-0 text-base text-muted-color transition-transform group-open:rotate-180\"\n />\n </summary>\n @if (advancedExpanded()) {\n <div\n class=\"grid gap-4 border-t border-surface-200 bg-surface-50/60 p-4 sm:p-5\"\n >\n <mt-email-delivery-quarantine class=\"block min-w-0\" />\n <mt-email-delivery-host-evidence class=\"block min-w-0\" />\n </div>\n }\n </details>\n </div>\n</ng-container>\n" }]
1091
1276
  }] });
1092
1277
 
1093
1278
  class EmailControlledTestDialog {
@@ -1106,7 +1291,7 @@ class EmailControlledTestDialog {
1106
1291
  form = new FormControl({
1107
1292
  recipients: null,
1108
1293
  subject: 'Pplus email delivery test',
1109
- body: 'This is a controlled Pplus email delivery test.',
1294
+ body: 'This is a controlled test.',
1110
1295
  isHtml: false,
1111
1296
  }, { nonNullable: true });
1112
1297
  formStatus = toSignal(this.form.statusChanges, {
@@ -1160,6 +1345,15 @@ class EmailControlledTestDialog {
1160
1345
  },
1161
1346
  ],
1162
1347
  }), ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
1348
+ /**
1349
+ * The provider-acceptance caveat is shown once. When the backend already
1350
+ * answers with the accepted-delivery-unconfirmed code, the code message
1351
+ * above carries that sentence and the extra line would just repeat it.
1352
+ */
1353
+ showsAcceptedCaveat(result) {
1354
+ return (result.outcome === 'Accepted' &&
1355
+ result.code !== ACCEPTED_DELIVERY_UNCONFIRMED_CODE);
1356
+ }
1163
1357
  submit() {
1164
1358
  if (!this.canSubmit()) {
1165
1359
  this.form.markAllAsTouched();
@@ -1249,7 +1443,7 @@ class EmailControlledTestDialog {
1249
1443
  return this.transloco.translate(`email-delivery.${key}`);
1250
1444
  }
1251
1445
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailControlledTestDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1252
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailControlledTestDialog, isStandalone: true, selector: "mt-email-controlled-test-dialog", inputs: { connectionId: { classPropertyName: "connectionId", publicName: "connectionId", isSignal: true, isRequired: true, transformFunction: null }, connectionName: { classPropertyName: "connectionName", publicName: "connectionName", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <div\r\n class=\"mb-4 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-950\"\r\n role=\"note\"\r\n >\r\n <strong>{{ t(\"test.real-email-warning-title\") }}</strong>\r\n {{ t(\"test.real-email-warning\") }}\r\n </div>\r\n\r\n @if (!result()) {\r\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\r\n }\r\n\r\n @if (timedOut()) {\r\n <div\r\n class=\"mt-4 rounded-lg border border-orange-200 bg-orange-50 px-4 py-3\"\r\n role=\"alert\"\r\n >\r\n <p class=\"font-semibold text-orange-950\">\r\n {{ t(\"test.timeout-title\") }}\r\n </p>\r\n <p class=\"mt-1 text-sm text-orange-900\">\r\n {{ t(\"test.timeout-description\") }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (result(); as testResult) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 px-4 py-4\"\r\n aria-live=\"polite\"\r\n >\r\n <mt-email-delivery-status kind=\"attempt\" [value]=\"testResult.outcome\" />\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ facade.describeCode(testResult.code) }}\r\n </p>\r\n @if (testResult.outcome === \"Accepted\") {\r\n <p class=\"mt-3 text-sm font-medium text-amber-800\">\r\n {{ t(\"accepted-unconfirmed\") }}\r\n </p>\r\n }\r\n @if (testResult.outcome === \"OutcomeUnknown\") {\r\n <p class=\"mt-3 text-sm font-medium text-red-700\">\r\n {{ t(\"outcome-unknown-warning\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('close')\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (timedOut()) {\r\n <mt-button\r\n [label]=\"t('test.retrieve')\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [disabled]=\"polling()\"\r\n [loading]=\"polling() || facade.mutationLoading() === 'retrieve-test'\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"retrieve()\"\r\n />\r\n } @else if (!result()) {\r\n <mt-button\r\n [label]=\"t('test.send')\"\r\n icon=\"communication.send-01\"\r\n [disabled]=\"!canSubmit()\"\r\n [loading]=\"facade.mutationLoading() === 'test'\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"submit()\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues", "serverFieldErrors"], outputs: ["runtimeMessagesChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1446
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailControlledTestDialog, isStandalone: true, selector: "mt-email-controlled-test-dialog", inputs: { connectionId: { classPropertyName: "connectionId", publicName: "connectionId", isSignal: true, isRequired: true, transformFunction: null }, connectionName: { classPropertyName: "connectionName", publicName: "connectionName", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\n <div\n class=\"mb-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-950\"\n role=\"note\"\n >\n <strong>{{ t(\"test.real-email-warning-title\") }}</strong>\n {{ t(\"test.real-email-warning\") }}\n </div>\n\n @if (!result()) {\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\n }\n\n @if (timedOut()) {\n <div\n class=\"mt-4 rounded-md border border-orange-200 bg-orange-50 px-4 py-3\"\n role=\"alert\"\n >\n <p class=\"font-semibold text-orange-950\">\n {{ t(\"test.timeout-title\") }}\n </p>\n <p class=\"mt-1 text-sm text-orange-900\">\n {{ t(\"test.timeout-description\") }}\n </p>\n </div>\n }\n\n @if (result(); as testResult) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-4 py-4\"\n aria-live=\"polite\"\n >\n <mt-email-delivery-status kind=\"attempt\" [value]=\"testResult.outcome\" />\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ facade.describeCode(testResult.code) }}\n </p>\n @if (showsAcceptedCaveat(testResult)) {\n <p class=\"mt-3 text-sm font-medium text-amber-800\">\n {{ t(\"accepted-unconfirmed\") }}\n </p>\n }\n @if (testResult.outcome === \"OutcomeUnknown\") {\n <p class=\"mt-3 text-sm font-medium text-red-700\">\n {{ t(\"outcome-unknown-warning\") }}\n </p>\n }\n </div>\n }\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('close')\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (timedOut()) {\n <mt-button\n [label]=\"t('test.retrieve')\"\n icon=\"arrow.refresh-cw-01\"\n [disabled]=\"polling()\"\n [loading]=\"polling() || facade.mutationLoading() === 'retrieve-test'\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"retrieve()\"\n />\n } @else if (!result()) {\n <mt-button\n [label]=\"t('test.send')\"\n icon=\"communication.send-01\"\n [disabled]=\"!canSubmit()\"\n [loading]=\"facade.mutationLoading() === 'test'\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"submit()\"\n />\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues", "serverFieldErrors"], outputs: ["runtimeMessagesChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1253
1447
  }
1254
1448
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailControlledTestDialog, decorators: [{
1255
1449
  type: Component,
@@ -1259,8 +1453,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1259
1453
  DynamicForm,
1260
1454
  Button,
1261
1455
  EmailDeliveryStatus,
1262
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <div\r\n class=\"mb-4 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-950\"\r\n role=\"note\"\r\n >\r\n <strong>{{ t(\"test.real-email-warning-title\") }}</strong>\r\n {{ t(\"test.real-email-warning\") }}\r\n </div>\r\n\r\n @if (!result()) {\r\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\r\n }\r\n\r\n @if (timedOut()) {\r\n <div\r\n class=\"mt-4 rounded-lg border border-orange-200 bg-orange-50 px-4 py-3\"\r\n role=\"alert\"\r\n >\r\n <p class=\"font-semibold text-orange-950\">\r\n {{ t(\"test.timeout-title\") }}\r\n </p>\r\n <p class=\"mt-1 text-sm text-orange-900\">\r\n {{ t(\"test.timeout-description\") }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (result(); as testResult) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 px-4 py-4\"\r\n aria-live=\"polite\"\r\n >\r\n <mt-email-delivery-status kind=\"attempt\" [value]=\"testResult.outcome\" />\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ facade.describeCode(testResult.code) }}\r\n </p>\r\n @if (testResult.outcome === \"Accepted\") {\r\n <p class=\"mt-3 text-sm font-medium text-amber-800\">\r\n {{ t(\"accepted-unconfirmed\") }}\r\n </p>\r\n }\r\n @if (testResult.outcome === \"OutcomeUnknown\") {\r\n <p class=\"mt-3 text-sm font-medium text-red-700\">\r\n {{ t(\"outcome-unknown-warning\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n [label]=\"t('close')\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (timedOut()) {\r\n <mt-button\r\n [label]=\"t('test.retrieve')\"\r\n icon=\"arrow.refresh-cw-01\"\r\n [disabled]=\"polling()\"\r\n [loading]=\"polling() || facade.mutationLoading() === 'retrieve-test'\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"retrieve()\"\r\n />\r\n } @else if (!result()) {\r\n <mt-button\r\n [label]=\"t('test.send')\"\r\n icon=\"communication.send-01\"\r\n [disabled]=\"!canSubmit()\"\r\n [loading]=\"facade.mutationLoading() === 'test'\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"submit()\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
1456
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\n <div\n class=\"mb-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-950\"\n role=\"note\"\n >\n <strong>{{ t(\"test.real-email-warning-title\") }}</strong>\n {{ t(\"test.real-email-warning\") }}\n </div>\n\n @if (!result()) {\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\n }\n\n @if (timedOut()) {\n <div\n class=\"mt-4 rounded-md border border-orange-200 bg-orange-50 px-4 py-3\"\n role=\"alert\"\n >\n <p class=\"font-semibold text-orange-950\">\n {{ t(\"test.timeout-title\") }}\n </p>\n <p class=\"mt-1 text-sm text-orange-900\">\n {{ t(\"test.timeout-description\") }}\n </p>\n </div>\n }\n\n @if (result(); as testResult) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-4 py-4\"\n aria-live=\"polite\"\n >\n <mt-email-delivery-status kind=\"attempt\" [value]=\"testResult.outcome\" />\n <p class=\"mt-1 text-sm text-muted-color\">\n {{ facade.describeCode(testResult.code) }}\n </p>\n @if (showsAcceptedCaveat(testResult)) {\n <p class=\"mt-3 text-sm font-medium text-amber-800\">\n {{ t(\"accepted-unconfirmed\") }}\n </p>\n }\n @if (testResult.outcome === \"OutcomeUnknown\") {\n <p class=\"mt-3 text-sm font-medium text-red-700\">\n {{ t(\"outcome-unknown-warning\") }}\n </p>\n }\n </div>\n }\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n severity=\"secondary\"\n variant=\"outlined\"\n [label]=\"t('close')\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (timedOut()) {\n <mt-button\n [label]=\"t('test.retrieve')\"\n icon=\"arrow.refresh-cw-01\"\n [disabled]=\"polling()\"\n [loading]=\"polling() || facade.mutationLoading() === 'retrieve-test'\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"retrieve()\"\n />\n } @else if (!result()) {\n <mt-button\n [label]=\"t('test.send')\"\n icon=\"communication.send-01\"\n [disabled]=\"!canSubmit()\"\n [loading]=\"facade.mutationLoading() === 'test'\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"submit()\"\n />\n }\n </div>\n</ng-container>\n" }]
1263
1457
  }], propDecorators: { connectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectionId", required: true }] }], connectionName: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectionName", required: true }] }] } });
1458
+ const ACCEPTED_DELIVERY_UNCONFIRMED_CODE = 'email.accepted_delivery_unconfirmed';
1264
1459
  const POLL_DELAYS_MS = [1_000, 2_000, 4_000, 5_000, 5_000, 5_000];
1265
1460
  function isPendingRequestState(value) {
1266
1461
  return !['Completed', 'Failed', 'Cancelled', 'Canceled'].includes(value);
@@ -1419,30 +1614,24 @@ class EmailConnectionEditor {
1419
1614
  canSave = computed(() => !this.editorLoading() &&
1420
1615
  this.formStatus() === 'VALID' &&
1421
1616
  !this.facade.mutationLoading(), ...(ngDevMode ? [{ debugName: "canSave" }] : /* istanbul ignore next */ []));
1617
+ configurationFields = computed(() => this.buildConfigurationFields(), ...(ngDevMode ? [{ debugName: "configurationFields" }] : /* istanbul ignore next */ []));
1618
+ /**
1619
+ * Empty whenever the selected mode authenticates without a stored secret —
1620
+ * an anonymous QC-test SMTP endpoint, for instance. That is a valid provider
1621
+ * descriptor, not a load failure.
1622
+ */
1623
+ credentialFields = computed(() => this.buildCredentialFields(), ...(ngDevMode ? [{ debugName: "credentialFields" }] : /* istanbul ignore next */ []));
1624
+ /**
1625
+ * A section header over an empty body reads as something that failed to
1626
+ * load, so a section with no applicable fields is not rendered at all.
1627
+ */
1422
1628
  formConfig = computed(() => ({
1629
+ layout: SECTION_MODE_LAYOUT,
1423
1630
  sections: [
1424
- {
1425
- key: 'basics',
1426
- label: this.t('editor.basics'),
1427
- type: 'header',
1428
- columns: 12,
1429
- fields: this.buildBasicFields(),
1430
- },
1431
- {
1432
- key: 'configuration',
1433
- label: this.t('editor.configuration'),
1434
- type: 'header',
1435
- columns: 12,
1436
- fields: this.buildConfigurationFields(),
1437
- },
1438
- {
1439
- key: 'credentials',
1440
- label: this.t('editor.credentials'),
1441
- type: 'header',
1442
- columns: 12,
1443
- fields: this.buildCredentialFields(),
1444
- },
1445
- ],
1631
+ this.section('basics', this.buildBasicFields()),
1632
+ this.section('configuration', this.configurationFields()),
1633
+ this.section('credentials', this.credentialFields()),
1634
+ ].filter((section) => section.fields.length > 0),
1446
1635
  }), ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
1447
1636
  constructor() {
1448
1637
  this.form.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
@@ -1583,6 +1772,22 @@ class EmailConnectionEditor {
1583
1772
  });
1584
1773
  return reset;
1585
1774
  }
1775
+ /**
1776
+ * Every section wears the same shell mt-client-form gives its sections — a
1777
+ * bordered, flat, rounded-md card with a header strip — so the connection
1778
+ * form reads like the runtime forms in the rest of the product.
1779
+ */
1780
+ section(key, fields) {
1781
+ return {
1782
+ key,
1783
+ label: this.t(`editor.${key}`),
1784
+ type: 'header',
1785
+ columns: 12,
1786
+ headerClass: SECTION_MODE_HEADER_CLASS,
1787
+ bodyClass: SECTION_MODE_BODY_CLASS,
1788
+ fields,
1789
+ };
1790
+ }
1586
1791
  buildBasicFields() {
1587
1792
  const provider = this.selectedProvider();
1588
1793
  return [
@@ -1809,12 +2014,24 @@ class EmailConnectionEditor {
1809
2014
  return this.transloco.translate(`email-delivery.${key}`);
1810
2015
  }
1811
2016
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailConnectionEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
1812
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailConnectionEditor, isStandalone: true, selector: "mt-email-connection-editor", inputs: { editorConnectionId: { classPropertyName: "editorConnectionId", publicName: "editorConnectionId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div\r\n [class]=\"\r\n 'flex min-h-0 flex-col gap-5 overflow-y-auto p-4 sm:p-5 ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <p class=\"m-0 text-sm text-muted-color\">\r\n {{ t(\"editor.description\") }}\r\n </p>\r\n\r\n @if (selectedProvider(); as provider) {\r\n @if (provider.warnings.length > 0) {\r\n <section\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-4 py-3\"\r\n role=\"note\"\r\n aria-labelledby=\"provider-warnings-heading\"\r\n >\r\n <h2\r\n id=\"provider-warnings-heading\"\r\n class=\"m-0 text-sm font-semibold text-amber-950\"\r\n >\r\n {{ t(\"provider-warnings\") }}\r\n </h2>\r\n <ul class=\"mb-0 mt-2 list-disc space-y-1 ps-5 text-sm text-amber-900\">\r\n @for (warning of provider.warnings; track warning) {\r\n <li>{{ warning }}</li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n }\r\n\r\n @if (isEdit()) {\r\n <div\r\n class=\"rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-950\"\r\n role=\"note\"\r\n >\r\n {{ t(\"editor.update-invalidates-proof\") }}\r\n </div>\r\n }\r\n\r\n @if (editorLoading()) {\r\n <div class=\"py-12 text-center text-sm text-muted-color\" role=\"status\">\r\n {{ t(\"loading\") }}\r\n </div>\r\n } @else {\r\n @for (generation of [formGeneration()]; track generation) {\r\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\r\n }\r\n }\r\n\r\n @if (applicableFields().length === 0 && !editorLoading()) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 px-4 py-3 text-sm text-muted-color\"\r\n >\r\n {{ t(\"editor.select-provider-mode\") }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"!!facade.mutationLoading()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"cancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [disabled]=\"!canSave()\"\r\n [loading]=\"facade.mutationLoading() === (isEdit() ? 'update' : 'create')\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"save()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues", "serverFieldErrors"], outputs: ["runtimeMessagesChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2017
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailConnectionEditor, isStandalone: true, selector: "mt-email-connection-editor", inputs: { editorConnectionId: { classPropertyName: "editorConnectionId", publicName: "editorConnectionId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div\n [class]=\"\n 'flex min-h-0 flex-col gap-5 overflow-y-auto p-4 sm:p-5 ' +\n modal.contentClass\n \"\n >\n @if (editorLoading()) {\n <div\n class=\"flex flex-col gap-5\"\n role=\"status\"\n [attr.aria-label]=\"t('loading')\"\n >\n @for (section of [1, 2, 3]; track section) {\n <div class=\"overflow-hidden rounded-md border border-surface-200\">\n <div class=\"border-b border-surface-200 bg-surface-50 px-4 py-3\">\n <p-skeleton\n width=\"10rem\"\n height=\"1.25rem\"\n borderRadius=\"0.375rem\"\n />\n </div>\n <div class=\"grid gap-4 p-4 sm:grid-cols-2\">\n @for (field of [1, 2, 3, 4]; track field) {\n <p-skeleton height=\"3.25rem\" borderRadius=\"0.375rem\" />\n }\n </div>\n </div>\n }\n </div>\n } @else {\n @if (selectedProvider(); as provider) {\n @if (provider.warnings.length > 0) {\n <section\n class=\"rounded-md border border-amber-200 bg-amber-50 p-4\"\n role=\"note\"\n aria-labelledby=\"provider-warnings-heading\"\n >\n <h2\n id=\"provider-warnings-heading\"\n class=\"m-0 text-sm font-semibold text-amber-950\"\n >\n {{ t(\"provider-warnings\") }}\n </h2>\n <ul\n class=\"mb-0 mt-2.5 list-disc space-y-2 ps-5 text-sm leading-relaxed text-amber-900\"\n >\n @for (warning of provider.warnings; track warning) {\n <li class=\"ps-1\">{{ warning }}</li>\n }\n </ul>\n </section>\n }\n }\n\n @if (isEdit()) {\n <div\n class=\"rounded-md border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-950\"\n role=\"note\"\n >\n {{ t(\"editor.update-invalidates-proof\") }}\n </div>\n }\n\n @for (generation of [formGeneration()]; track generation) {\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\n }\n\n @if (applicableFields().length === 0) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-4 py-3 text-sm text-muted-color\"\n >\n {{ t(\"editor.select-provider-mode\") }}\n </div>\n }\n }\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('cancel')\"\n variant=\"outlined\"\n [disabled]=\"!!facade.mutationLoading()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"cancel()\"\n />\n <mt-button\n [label]=\"t('save')\"\n icon=\"general.save-01\"\n [disabled]=\"!canSave()\"\n [loading]=\"facade.mutationLoading() === (isEdit() ? 'update' : 'create')\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"save()\"\n />\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues", "serverFieldErrors"], outputs: ["runtimeMessagesChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1813
2018
  }
1814
2019
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailConnectionEditor, decorators: [{
1815
2020
  type: Component,
1816
- args: [{ selector: 'mt-email-connection-editor', imports: [ReactiveFormsModule, TranslocoDirective, DynamicForm, Button], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <div\r\n [class]=\"\r\n 'flex min-h-0 flex-col gap-5 overflow-y-auto p-4 sm:p-5 ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <p class=\"m-0 text-sm text-muted-color\">\r\n {{ t(\"editor.description\") }}\r\n </p>\r\n\r\n @if (selectedProvider(); as provider) {\r\n @if (provider.warnings.length > 0) {\r\n <section\r\n class=\"rounded-lg border border-amber-200 bg-amber-50 px-4 py-3\"\r\n role=\"note\"\r\n aria-labelledby=\"provider-warnings-heading\"\r\n >\r\n <h2\r\n id=\"provider-warnings-heading\"\r\n class=\"m-0 text-sm font-semibold text-amber-950\"\r\n >\r\n {{ t(\"provider-warnings\") }}\r\n </h2>\r\n <ul class=\"mb-0 mt-2 list-disc space-y-1 ps-5 text-sm text-amber-900\">\r\n @for (warning of provider.warnings; track warning) {\r\n <li>{{ warning }}</li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n }\r\n\r\n @if (isEdit()) {\r\n <div\r\n class=\"rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-950\"\r\n role=\"note\"\r\n >\r\n {{ t(\"editor.update-invalidates-proof\") }}\r\n </div>\r\n }\r\n\r\n @if (editorLoading()) {\r\n <div class=\"py-12 text-center text-sm text-muted-color\" role=\"status\">\r\n {{ t(\"loading\") }}\r\n </div>\r\n } @else {\r\n @for (generation of [formGeneration()]; track generation) {\r\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\r\n }\r\n }\r\n\r\n @if (applicableFields().length === 0 && !editorLoading()) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 px-4 py-3 text-sm text-muted-color\"\r\n >\r\n {{ t(\"editor.select-provider-mode\") }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n variant=\"outlined\"\r\n [disabled]=\"!!facade.mutationLoading()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"cancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-01\"\r\n [disabled]=\"!canSave()\"\r\n [loading]=\"facade.mutationLoading() === (isEdit() ? 'update' : 'create')\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"save()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
2021
+ args: [{ selector: 'mt-email-connection-editor', imports: [
2022
+ ReactiveFormsModule,
2023
+ TranslocoDirective,
2024
+ SkeletonModule,
2025
+ DynamicForm,
2026
+ Button,
2027
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <div\n [class]=\"\n 'flex min-h-0 flex-col gap-5 overflow-y-auto p-4 sm:p-5 ' +\n modal.contentClass\n \"\n >\n @if (editorLoading()) {\n <div\n class=\"flex flex-col gap-5\"\n role=\"status\"\n [attr.aria-label]=\"t('loading')\"\n >\n @for (section of [1, 2, 3]; track section) {\n <div class=\"overflow-hidden rounded-md border border-surface-200\">\n <div class=\"border-b border-surface-200 bg-surface-50 px-4 py-3\">\n <p-skeleton\n width=\"10rem\"\n height=\"1.25rem\"\n borderRadius=\"0.375rem\"\n />\n </div>\n <div class=\"grid gap-4 p-4 sm:grid-cols-2\">\n @for (field of [1, 2, 3, 4]; track field) {\n <p-skeleton height=\"3.25rem\" borderRadius=\"0.375rem\" />\n }\n </div>\n </div>\n }\n </div>\n } @else {\n @if (selectedProvider(); as provider) {\n @if (provider.warnings.length > 0) {\n <section\n class=\"rounded-md border border-amber-200 bg-amber-50 p-4\"\n role=\"note\"\n aria-labelledby=\"provider-warnings-heading\"\n >\n <h2\n id=\"provider-warnings-heading\"\n class=\"m-0 text-sm font-semibold text-amber-950\"\n >\n {{ t(\"provider-warnings\") }}\n </h2>\n <ul\n class=\"mb-0 mt-2.5 list-disc space-y-2 ps-5 text-sm leading-relaxed text-amber-900\"\n >\n @for (warning of provider.warnings; track warning) {\n <li class=\"ps-1\">{{ warning }}</li>\n }\n </ul>\n </section>\n }\n }\n\n @if (isEdit()) {\n <div\n class=\"rounded-md border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-950\"\n role=\"note\"\n >\n {{ t(\"editor.update-invalidates-proof\") }}\n </div>\n }\n\n @for (generation of [formGeneration()]; track generation) {\n <mt-dynamic-form [formConfig]=\"formConfig()\" [formControl]=\"form\" />\n }\n\n @if (applicableFields().length === 0) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 px-4 py-3 text-sm text-muted-color\"\n >\n {{ t(\"editor.select-provider-mode\") }}\n </div>\n }\n }\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('cancel')\"\n variant=\"outlined\"\n [disabled]=\"!!facade.mutationLoading()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"cancel()\"\n />\n <mt-button\n [label]=\"t('save')\"\n icon=\"general.save-01\"\n [disabled]=\"!canSave()\"\n [loading]=\"facade.mutationLoading() === (isEdit() ? 'update' : 'create')\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"save()\"\n />\n </div>\n</ng-container>\n" }]
1817
2028
  }], ctorParameters: () => [], propDecorators: { editorConnectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "editorConnectionId", required: false }] }] } });
2029
+ // Mirrors the section-mode styling mt-client-form applies to its own sections.
2030
+ const SECTION_MODE_LAYOUT = {
2031
+ sectionClass: 'flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0',
2032
+ };
2033
+ const SECTION_MODE_HEADER_CLASS = 'm-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color';
2034
+ const SECTION_MODE_BODY_CLASS = '@container grid grid-cols-12 gap-4 items-start p-4';
1818
2035
  function emptyEditorValue() {
1819
2036
  return {
1820
2037
  name: null,
@@ -2023,7 +2240,7 @@ class EmailConnectionDetailsPage {
2023
2240
  edit() {
2024
2241
  const drawer = this.modal.openModal(EmailConnectionEditor, 'drawer', {
2025
2242
  header: this.t('editor.edit-title'),
2026
- styleClass: '!absolute !shadow-none !w-full !max-w-full',
2243
+ styleClass: EMAIL_DELIVERY_DRAWER_STYLE_CLASS,
2027
2244
  position: 'end',
2028
2245
  appendTo: 'page-content',
2029
2246
  dismissableMask: true,
@@ -2140,19 +2357,19 @@ class EmailConnectionDetailsPage {
2140
2357
  return field?.dataType === 'Choice' ? humanizeIdentifier(value) : value;
2141
2358
  }
2142
2359
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailConnectionDetailsPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
2143
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailConnectionDetailsPage, isStandalone: true, selector: "mt-email-connection-details", inputs: { connectionId: { classPropertyName: "connectionId", publicName: "connectionId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n @if (facade.detailsLoading()) {\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <div class=\"py-12 text-center text-sm text-muted-color\" role=\"status\">\r\n {{ t(\"loading\") }}\r\n </div>\r\n </div>\r\n } @else if (facade.selectedConnection(); as details) {\r\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\r\n <div class=\"grid w-full gap-5\">\r\n <section\r\n class=\"flex flex-col gap-3 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:flex-row sm:items-center sm:justify-between\"\r\n >\r\n <div class=\"min-w-0\">\r\n <h2 class=\"m-0 truncate text-lg font-semibold text-color\">\r\n {{ details.connection.name }}\r\n </h2>\r\n <p class=\"mb-0 mt-1 break-all text-sm text-muted-color\">\r\n {{ details.connection.senderAddress }}\r\n </p>\r\n </div>\r\n <mt-email-delivery-status\r\n class=\"shrink-0\"\r\n kind=\"connection\"\r\n [value]=\"details.connection.state\"\r\n />\r\n </section>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"connection-overview\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.provider\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ provider()?.displayName || details.connection.providerKey }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.authentication-mode\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ authenticationModeLabel() }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"used-for-notifications\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n {{\r\n details.bindings.includes(\"notifications\")\r\n ? t(\"yes\")\r\n : t(\"no\")\r\n }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.last-validation\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n {{\r\n details.connection.lastValidatedAtUtc\r\n ? (details.connection.lastValidatedAtUtc | date: \"medium\")\r\n : \"\u2014\"\r\n }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"authorization-state\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ details.authorizationState || \"\u2014\" }}\r\n </dd>\r\n </div>\r\n @if (details.connection.senderDisplayName) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.sender-display-name\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ details.connection.senderDisplayName }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n\r\n @if (publicConfiguration().length > 0) {\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"provider-settings\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\r\n @for (entry of publicConfiguration(); track entry.key) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">{{ entry.label }}</dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ entry.value }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n }\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"credential-status\") }}\r\n </h3>\r\n <div class=\"grid gap-3\">\r\n @for (credential of credentialItems(); track credential.key) {\r\n <div\r\n class=\"grid gap-3 rounded-lg border border-surface-200 bg-surface-50 p-4 sm:grid-cols-3 sm:items-center\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p class=\"m-0 break-words font-medium text-color\">\r\n {{ credential.label }}\r\n </p>\r\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\r\n {{ credential.key }}\r\n </p>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <p class=\"m-0 text-sm font-medium text-color\">\r\n {{ credential.storageLabel }}\r\n </p>\r\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\r\n @if (credential.lastRotatedAtUtc) {\r\n {{ t(\"last-rotated\") }} \u00B7\r\n {{ credential.lastRotatedAtUtc | date: \"medium\" }}\r\n } @else {\r\n {{ t(\"last-rotated\") }} \u00B7 \u2014\r\n }\r\n </p>\r\n </div>\r\n <mt-email-delivery-status\r\n class=\"shrink-0 sm:justify-self-end\"\r\n kind=\"support\"\r\n [value]=\"\r\n credential.configured ? 'configured' : 'not-configured'\r\n \"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"text-sm text-muted-color\">{{ t(\"no-credentials\") }}</p>\r\n }\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('close')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (actionMenuItems().length > 0) {\r\n <mt-button\r\n [label]=\"t('actions')\"\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"actionMenu.toggle($event)\"\r\n />\r\n <mt-menu\r\n #actionMenu\r\n [model]=\"actionMenuItems()\"\r\n appendTo=\"body\"\r\n [ariaLabel]=\"t('actions')\"\r\n />\r\n }\r\n <mt-button\r\n [label]=\"t('edit')\"\r\n icon=\"general.edit-01\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"edit()\"\r\n />\r\n </div>\r\n } @else {\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <mt-empty-state illustration=\"none\" [title]=\"t('connection-not-found')\" />\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button [label]=\"t('close')\" variant=\"outlined\" (onClick)=\"close()\" />\r\n </div>\r\n }\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Menu, selector: "mt-menu", inputs: ["model", "popup", "appendTo", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "activeItemId"], outputs: ["activeItemIdChange", "onShow", "onHide", "onBlur", "onFocus", "onItemSelect"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2360
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailConnectionDetailsPage, isStandalone: true, selector: "mt-email-connection-details", inputs: { connectionId: { classPropertyName: "connectionId", publicName: "connectionId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n @if (facade.detailsLoading()) {\n <div\n [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\"\n role=\"status\"\n [attr.aria-label]=\"t('loading')\"\n >\n <div class=\"grid w-full gap-5\">\n <p-skeleton height=\"5rem\" borderRadius=\"0.375rem\" />\n @for (section of [1, 2, 3]; track section) {\n <div class=\"overflow-hidden rounded-md border border-surface-200\">\n <div class=\"border-b border-surface-200 bg-surface-50 px-4 py-3\">\n <p-skeleton\n width=\"12rem\"\n height=\"1.25rem\"\n borderRadius=\"0.375rem\"\n />\n </div>\n <div class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n @for (tile of [1, 2, 3]; track tile) {\n <p-skeleton height=\"4rem\" borderRadius=\"0.375rem\" />\n }\n </div>\n </div>\n }\n </div>\n </div>\n } @else if (facade.selectedConnection(); as details) {\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\n <div class=\"grid w-full gap-5\">\n <section\n class=\"flex flex-col gap-3 rounded-md border border-surface-200 bg-surface-0 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div class=\"min-w-0\">\n <h2 class=\"m-0 truncate text-lg font-semibold text-color\">\n {{ details.connection.name }}\n </h2>\n <p class=\"mb-0 mt-1 break-all text-sm text-muted-color\">\n {{ details.connection.senderAddress }}\n </p>\n </div>\n <mt-email-delivery-status\n class=\"shrink-0\"\n kind=\"connection\"\n [value]=\"details.connection.state\"\n />\n </section>\n\n <!-- Same section shape the connection form uses (mt-client-form section\n mode): header strip on a bordered, flat, rounded-md container. -->\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"connection-overview\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.provider\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ provider()?.displayName || details.connection.providerKey }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.authentication-mode\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ authenticationModeLabel() }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"used-for-notifications\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{\n details.bindings.includes(\"notifications\")\n ? t(\"yes\")\n : t(\"no\")\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.last-validation\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{\n details.connection.lastValidatedAtUtc\n ? (details.connection.lastValidatedAtUtc | date: \"medium\")\n : \"\u2014\"\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"authorization-state\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ details.authorizationState || \"\u2014\" }}\n </dd>\n </div>\n @if (details.connection.senderDisplayName) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.sender-display-name\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ details.connection.senderDisplayName }}\n </dd>\n </div>\n }\n </dl>\n </section>\n\n @if (publicConfiguration().length > 0) {\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"provider-settings\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n @for (entry of publicConfiguration(); track entry.key) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">{{ entry.label }}</dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ entry.value }}\n </dd>\n </div>\n }\n </dl>\n </section>\n }\n\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"credential-status\") }}\n </h3>\n <div class=\"grid gap-3 p-4\">\n @for (credential of credentialItems(); track credential.key) {\n <div\n class=\"grid gap-3 rounded-md border border-surface-200 bg-surface-50 p-3 sm:grid-cols-3 sm:items-center\"\n >\n <div class=\"min-w-0\">\n <p class=\"m-0 break-words font-medium text-color\">\n {{ credential.label }}\n </p>\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\n {{ credential.key }}\n </p>\n </div>\n <div class=\"min-w-0\">\n <p class=\"m-0 text-sm font-medium text-color\">\n {{ credential.storageLabel }}\n </p>\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\n @if (credential.lastRotatedAtUtc) {\n {{ t(\"last-rotated\") }} \u00B7\n {{ credential.lastRotatedAtUtc | date: \"medium\" }}\n } @else {\n {{ t(\"last-rotated\") }} \u00B7 \u2014\n }\n </p>\n </div>\n <mt-email-delivery-status\n class=\"shrink-0 sm:justify-self-end\"\n kind=\"support\"\n [value]=\"\n credential.configured ? 'configured' : 'not-configured'\n \"\n />\n </div>\n } @empty {\n <p class=\"m-0 text-sm text-muted-color\">\n {{ t(\"no-credentials\") }}\n </p>\n }\n </div>\n </section>\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('close')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (actionMenuItems().length > 0) {\n <mt-button\n [label]=\"t('actions')\"\n icon=\"general.dots-vertical\"\n severity=\"secondary\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"actionMenu.toggle($event)\"\n />\n <mt-menu\n #actionMenu\n [model]=\"actionMenuItems()\"\n appendTo=\"body\"\n [ariaLabel]=\"t('actions')\"\n />\n }\n <mt-button\n [label]=\"t('edit')\"\n icon=\"general.edit-01\"\n [disabled]=\"!facade.managementAvailable()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"edit()\"\n />\n </div>\n } @else {\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\n <mt-empty-state illustration=\"none\" [title]=\"t('connection-not-found')\" />\n </div>\n <div [class]=\"modal.footerClass\">\n <mt-button [label]=\"t('close')\" variant=\"outlined\" (onClick)=\"close()\" />\n </div>\n }\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Menu, selector: "mt-menu", inputs: ["model", "popup", "appendTo", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "activeItemId"], outputs: ["activeItemIdChange", "onShow", "onHide", "onBlur", "onFocus", "onItemSelect"] }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2144
2361
  }
2145
2362
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailConnectionDetailsPage, decorators: [{
2146
2363
  type: Component,
2147
2364
  args: [{ selector: 'mt-email-connection-details', imports: [
2148
2365
  DatePipe,
2149
2366
  TranslocoDirective,
2367
+ SkeletonModule,
2150
2368
  Button,
2151
- Card,
2152
2369
  EmptyState,
2153
2370
  Menu,
2154
2371
  EmailDeliveryStatus,
2155
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n @if (facade.detailsLoading()) {\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <div class=\"py-12 text-center text-sm text-muted-color\" role=\"status\">\r\n {{ t(\"loading\") }}\r\n </div>\r\n </div>\r\n } @else if (facade.selectedConnection(); as details) {\r\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\r\n <div class=\"grid w-full gap-5\">\r\n <section\r\n class=\"flex flex-col gap-3 rounded-xl border border-surface-200 bg-surface-0 p-4 shadow-xs sm:flex-row sm:items-center sm:justify-between\"\r\n >\r\n <div class=\"min-w-0\">\r\n <h2 class=\"m-0 truncate text-lg font-semibold text-color\">\r\n {{ details.connection.name }}\r\n </h2>\r\n <p class=\"mb-0 mt-1 break-all text-sm text-muted-color\">\r\n {{ details.connection.senderAddress }}\r\n </p>\r\n </div>\r\n <mt-email-delivery-status\r\n class=\"shrink-0\"\r\n kind=\"connection\"\r\n [value]=\"details.connection.state\"\r\n />\r\n </section>\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"connection-overview\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.provider\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ provider()?.displayName || details.connection.providerKey }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.authentication-mode\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ authenticationModeLabel() }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"used-for-notifications\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n {{\r\n details.bindings.includes(\"notifications\")\r\n ? t(\"yes\")\r\n : t(\"no\")\r\n }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.last-validation\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 font-medium text-color\">\r\n {{\r\n details.connection.lastValidatedAtUtc\r\n ? (details.connection.lastValidatedAtUtc | date: \"medium\")\r\n : \"\u2014\"\r\n }}\r\n </dd>\r\n </div>\r\n <div class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\">\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"authorization-state\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ details.authorizationState || \"\u2014\" }}\r\n </dd>\r\n </div>\r\n @if (details.connection.senderDisplayName) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">\r\n {{ t(\"connection.sender-display-name\") }}\r\n </dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ details.connection.senderDisplayName }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n\r\n @if (publicConfiguration().length > 0) {\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"provider-settings\") }}\r\n </h3>\r\n <dl class=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\r\n @for (entry of publicConfiguration(); track entry.key) {\r\n <div\r\n class=\"rounded-lg border border-surface-200 bg-surface-50 p-4\"\r\n >\r\n <dt class=\"text-xs text-muted-color\">{{ entry.label }}</dt>\r\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\r\n {{ entry.value }}\r\n </dd>\r\n </div>\r\n }\r\n </dl>\r\n </mt-card>\r\n }\r\n\r\n <mt-card class=\"block\">\r\n <h3 class=\"mb-4 text-base font-semibold text-color\">\r\n {{ t(\"credential-status\") }}\r\n </h3>\r\n <div class=\"grid gap-3\">\r\n @for (credential of credentialItems(); track credential.key) {\r\n <div\r\n class=\"grid gap-3 rounded-lg border border-surface-200 bg-surface-50 p-4 sm:grid-cols-3 sm:items-center\"\r\n >\r\n <div class=\"min-w-0\">\r\n <p class=\"m-0 break-words font-medium text-color\">\r\n {{ credential.label }}\r\n </p>\r\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\r\n {{ credential.key }}\r\n </p>\r\n </div>\r\n <div class=\"min-w-0\">\r\n <p class=\"m-0 text-sm font-medium text-color\">\r\n {{ credential.storageLabel }}\r\n </p>\r\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\r\n @if (credential.lastRotatedAtUtc) {\r\n {{ t(\"last-rotated\") }} \u00B7\r\n {{ credential.lastRotatedAtUtc | date: \"medium\" }}\r\n } @else {\r\n {{ t(\"last-rotated\") }} \u00B7 \u2014\r\n }\r\n </p>\r\n </div>\r\n <mt-email-delivery-status\r\n class=\"shrink-0 sm:justify-self-end\"\r\n kind=\"support\"\r\n [value]=\"\r\n credential.configured ? 'configured' : 'not-configured'\r\n \"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"text-sm text-muted-color\">{{ t(\"no-credentials\") }}</p>\r\n }\r\n </div>\r\n </mt-card>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('close')\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"close()\"\r\n />\r\n @if (actionMenuItems().length > 0) {\r\n <mt-button\r\n [label]=\"t('actions')\"\r\n icon=\"general.dots-vertical\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"actionMenu.toggle($event)\"\r\n />\r\n <mt-menu\r\n #actionMenu\r\n [model]=\"actionMenuItems()\"\r\n appendTo=\"body\"\r\n [ariaLabel]=\"t('actions')\"\r\n />\r\n }\r\n <mt-button\r\n [label]=\"t('edit')\"\r\n icon=\"general.edit-01\"\r\n [disabled]=\"!facade.managementAvailable()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n (onClick)=\"edit()\"\r\n />\r\n </div>\r\n } @else {\r\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\r\n <mt-empty-state illustration=\"none\" [title]=\"t('connection-not-found')\" />\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button [label]=\"t('close')\" variant=\"outlined\" (onClick)=\"close()\" />\r\n </div>\r\n }\r\n</ng-container>\r\n" }]
2372
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n @if (facade.detailsLoading()) {\n <div\n [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\"\n role=\"status\"\n [attr.aria-label]=\"t('loading')\"\n >\n <div class=\"grid w-full gap-5\">\n <p-skeleton height=\"5rem\" borderRadius=\"0.375rem\" />\n @for (section of [1, 2, 3]; track section) {\n <div class=\"overflow-hidden rounded-md border border-surface-200\">\n <div class=\"border-b border-surface-200 bg-surface-50 px-4 py-3\">\n <p-skeleton\n width=\"12rem\"\n height=\"1.25rem\"\n borderRadius=\"0.375rem\"\n />\n </div>\n <div class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n @for (tile of [1, 2, 3]; track tile) {\n <p-skeleton height=\"4rem\" borderRadius=\"0.375rem\" />\n }\n </div>\n </div>\n }\n </div>\n </div>\n } @else if (facade.selectedConnection(); as details) {\n <div [class]=\"'min-h-0 overflow-y-auto p-4 sm:p-5 ' + modal.contentClass\">\n <div class=\"grid w-full gap-5\">\n <section\n class=\"flex flex-col gap-3 rounded-md border border-surface-200 bg-surface-0 p-4 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div class=\"min-w-0\">\n <h2 class=\"m-0 truncate text-lg font-semibold text-color\">\n {{ details.connection.name }}\n </h2>\n <p class=\"mb-0 mt-1 break-all text-sm text-muted-color\">\n {{ details.connection.senderAddress }}\n </p>\n </div>\n <mt-email-delivery-status\n class=\"shrink-0\"\n kind=\"connection\"\n [value]=\"details.connection.state\"\n />\n </section>\n\n <!-- Same section shape the connection form uses (mt-client-form section\n mode): header strip on a bordered, flat, rounded-md container. -->\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"connection-overview\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.provider\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ provider()?.displayName || details.connection.providerKey }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.authentication-mode\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ authenticationModeLabel() }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"used-for-notifications\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{\n details.bindings.includes(\"notifications\")\n ? t(\"yes\")\n : t(\"no\")\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.last-validation\") }}\n </dt>\n <dd class=\"mb-0 mt-1 font-medium text-color\">\n {{\n details.connection.lastValidatedAtUtc\n ? (details.connection.lastValidatedAtUtc | date: \"medium\")\n : \"\u2014\"\n }}\n </dd>\n </div>\n <div class=\"rounded-md border border-surface-200 bg-surface-50 p-3\">\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"authorization-state\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ details.authorizationState || \"\u2014\" }}\n </dd>\n </div>\n @if (details.connection.senderDisplayName) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">\n {{ t(\"connection.sender-display-name\") }}\n </dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ details.connection.senderDisplayName }}\n </dd>\n </div>\n }\n </dl>\n </section>\n\n @if (publicConfiguration().length > 0) {\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"provider-settings\") }}\n </h3>\n <dl class=\"grid gap-3 p-4 sm:grid-cols-2 xl:grid-cols-3\">\n @for (entry of publicConfiguration(); track entry.key) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3\"\n >\n <dt class=\"text-xs text-muted-color\">{{ entry.label }}</dt>\n <dd class=\"mb-0 mt-1 break-words font-medium text-color\">\n {{ entry.value }}\n </dd>\n </div>\n }\n </dl>\n </section>\n }\n\n <section\n class=\"flex flex-col gap-0 overflow-hidden rounded-md border border-surface-200 bg-surface-0\"\n >\n <h3\n class=\"m-0 border-b border-surface-200 bg-surface-50 px-4 py-3 text-lg font-semibold text-color\"\n >\n {{ t(\"credential-status\") }}\n </h3>\n <div class=\"grid gap-3 p-4\">\n @for (credential of credentialItems(); track credential.key) {\n <div\n class=\"grid gap-3 rounded-md border border-surface-200 bg-surface-50 p-3 sm:grid-cols-3 sm:items-center\"\n >\n <div class=\"min-w-0\">\n <p class=\"m-0 break-words font-medium text-color\">\n {{ credential.label }}\n </p>\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\n {{ credential.key }}\n </p>\n </div>\n <div class=\"min-w-0\">\n <p class=\"m-0 text-sm font-medium text-color\">\n {{ credential.storageLabel }}\n </p>\n <p class=\"mb-0 mt-1 text-xs text-muted-color\">\n @if (credential.lastRotatedAtUtc) {\n {{ t(\"last-rotated\") }} \u00B7\n {{ credential.lastRotatedAtUtc | date: \"medium\" }}\n } @else {\n {{ t(\"last-rotated\") }} \u00B7 \u2014\n }\n </p>\n </div>\n <mt-email-delivery-status\n class=\"shrink-0 sm:justify-self-end\"\n kind=\"support\"\n [value]=\"\n credential.configured ? 'configured' : 'not-configured'\n \"\n />\n </div>\n } @empty {\n <p class=\"m-0 text-sm text-muted-color\">\n {{ t(\"no-credentials\") }}\n </p>\n }\n </div>\n </section>\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('close')\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"close()\"\n />\n @if (actionMenuItems().length > 0) {\n <mt-button\n [label]=\"t('actions')\"\n icon=\"general.dots-vertical\"\n severity=\"secondary\"\n variant=\"outlined\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"actionMenu.toggle($event)\"\n />\n <mt-menu\n #actionMenu\n [model]=\"actionMenuItems()\"\n appendTo=\"body\"\n [ariaLabel]=\"t('actions')\"\n />\n }\n <mt-button\n [label]=\"t('edit')\"\n icon=\"general.edit-01\"\n [disabled]=\"!facade.managementAvailable()\"\n styleClass=\"w-full sm:w-auto\"\n (onClick)=\"edit()\"\n />\n </div>\n } @else {\n <div [class]=\"modal.contentClass + ' p-4 sm:p-5'\">\n <mt-empty-state illustration=\"none\" [title]=\"t('connection-not-found')\" />\n </div>\n <div [class]=\"modal.footerClass\">\n <mt-button [label]=\"t('close')\" variant=\"outlined\" (onClick)=\"close()\" />\n </div>\n }\n</ng-container>\n" }]
2156
2373
  }], propDecorators: { connectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectionId", required: true }] }] } });
2157
2374
  function humanizeIdentifier(value) {
2158
2375
  return value
@@ -2177,6 +2394,10 @@ class EmailDeliveryConnections {
2177
2394
  initialValue: this.transloco.getActiveLang(),
2178
2395
  });
2179
2396
  canConfigure = this.facade.hasPermission(EMAIL_DELIVERY_PERMISSIONS.controlPanel);
2397
+ /** One rule for both the caption action and the empty-state action. */
2398
+ canCreate = computed(() => this.canConfigure &&
2399
+ this.facade.managementAvailable() &&
2400
+ this.facade.providers().length > 0, ...(ngDevMode ? [{ debugName: "canCreate" }] : /* istanbul ignore next */ []));
2180
2401
  rows = computed(() => this.facade.connections().map((connection) => ({
2181
2402
  ...connection,
2182
2403
  providerLabel: this.facade.providerByKey(connection.providerKey)?.displayName ??
@@ -2195,6 +2416,7 @@ class EmailDeliveryConnections {
2195
2416
  {
2196
2417
  key: 'providerLabel',
2197
2418
  label: this.t('connection.provider'),
2419
+ width: '18rem',
2198
2420
  filterConfig: {
2199
2421
  type: 'select',
2200
2422
  options: this.facade.providers().map((provider) => ({
@@ -2206,12 +2428,14 @@ class EmailDeliveryConnections {
2206
2428
  {
2207
2429
  key: 'senderAddress',
2208
2430
  label: this.t('connection.sender'),
2431
+ width: '18rem',
2209
2432
  filterConfig: { type: 'text' },
2210
2433
  },
2211
2434
  {
2212
2435
  key: 'state',
2213
2436
  label: this.t('connection.state'),
2214
2437
  type: 'status',
2438
+ width: '15rem',
2215
2439
  filterConfig: {
2216
2440
  type: 'select',
2217
2441
  options: connectionStates.map((state) => ({
@@ -2223,8 +2447,9 @@ class EmailDeliveryConnections {
2223
2447
  },
2224
2448
  {
2225
2449
  key: 'usedForNotifications',
2226
- label: this.t('used-for-notifications'),
2450
+ label: this.t('in-use'),
2227
2451
  type: 'boolean',
2452
+ width: '10rem',
2228
2453
  readonly: true,
2229
2454
  filterConfig: { type: 'boolean' },
2230
2455
  },
@@ -2232,6 +2457,7 @@ class EmailDeliveryConnections {
2232
2457
  key: 'lastValidatedAtUtc',
2233
2458
  label: this.t('connection.last-validation'),
2234
2459
  type: 'dateTime',
2460
+ width: '15rem',
2235
2461
  },
2236
2462
  ];
2237
2463
  }, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
@@ -2299,7 +2525,7 @@ class EmailDeliveryConnections {
2299
2525
  openDetails(connection) {
2300
2526
  const drawer = this.modal.openModal(EmailConnectionDetailsPage, 'drawer', {
2301
2527
  header: this.t('connection-details'),
2302
- styleClass: '!absolute !shadow-none !w-full !max-w-full',
2528
+ styleClass: EMAIL_DELIVERY_DRAWER_STYLE_CLASS,
2303
2529
  position: 'end',
2304
2530
  appendTo: 'page-content',
2305
2531
  dismissableMask: true,
@@ -2361,7 +2587,7 @@ class EmailDeliveryConnections {
2361
2587
  openEditor(connectionId) {
2362
2588
  const drawer = this.modal.openModal(EmailConnectionEditor, 'drawer', {
2363
2589
  header: this.t(connectionId ? 'editor.edit-title' : 'editor.create-title'),
2364
- styleClass: '!absolute !shadow-none !w-full !max-w-full',
2590
+ styleClass: EMAIL_DELIVERY_DRAWER_STYLE_CLASS,
2365
2591
  position: 'end',
2366
2592
  appendTo: 'page-content',
2367
2593
  dismissableMask: true,
@@ -2383,11 +2609,11 @@ class EmailDeliveryConnections {
2383
2609
  return this.transloco.translate(`email-delivery.${key}`);
2384
2610
  }
2385
2611
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryConnections, deps: [], target: i0.ɵɵFactoryTarget.Component });
2386
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryConnections, isStandalone: true, selector: "mt-email-delivery-connections", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section class=\"space-y-4\" aria-labelledby=\"connections-heading\">\r\n <div\r\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\r\n >\r\n <div>\r\n <h2 id=\"connections-heading\" class=\"text-lg font-semibold text-color\">\r\n {{ t(\"connections-title\") }}\r\n </h2>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"connections-description\") }}\r\n </p>\r\n </div>\r\n @if (\r\n canConfigure &&\r\n facade.managementAvailable() &&\r\n facade.providers().length > 0\r\n ) {\r\n <mt-button\r\n [label]=\"t('create-connection')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"createConnection()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (\r\n !facade.statusLoading() &&\r\n !facade.catalogLoading() &&\r\n facade.providers().length === 0\r\n ) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-providers-title')\"\r\n [description]=\"t('no-providers-description')\"\r\n />\r\n } @else if (\r\n !facade.statusLoading() &&\r\n !facade.connectionsLoading() &&\r\n facade.connections().length === 0\r\n ) {\r\n <mt-empty-state\r\n [title]=\"t('no-connections-title')\"\r\n [description]=\"t('no-connections-description')\"\r\n [actionLabel]=\"\r\n canConfigure && facade.managementAvailable()\r\n ? t('create-connection')\r\n : undefined\r\n \"\r\n actionIcon=\"general.plus\"\r\n (actionClick)=\"createConnection()\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"\r\n facade.statusLoading() ||\r\n facade.connectionsLoading() ||\r\n facade.catalogLoading()\r\n \"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n [clickableRows]=\"true\"\r\n dataKey=\"id\"\r\n storageKey=\"email-delivery-connections\"\r\n (rowClick)=\"openDetails($event)\"\r\n />\r\n }\r\n </section>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2612
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryConnections, isStandalone: true, selector: "mt-email-delivery-connections", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-4\" aria-label=\"{{ t('connections-title') }}\">\n @if (\n !facade.statusLoading() &&\n !facade.catalogLoading() &&\n facade.providers().length === 0\n ) {\n <mt-empty-state\n illustration=\"none\"\n [title]=\"t('no-providers-title')\"\n [description]=\"t('no-providers-description')\"\n />\n } @else {\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"\n facade.statusLoading() ||\n facade.connectionsLoading() ||\n facade.catalogLoading()\n \"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-connections-title')\"\n [emptyDescription]=\"t('no-connections-description')\"\n [emptyActionLabel]=\"canCreate() ? t('create-connection') : undefined\"\n emptyActionIcon=\"general.plus\"\n filterMode=\"column\"\n actionShape=\"popover\"\n [clickableRows]=\"true\"\n dataKey=\"id\"\n storageKey=\"email-delivery-connections\"\n (emptyAction)=\"createConnection()\"\n (rowClick)=\"openDetails($event)\"\n >\n <ng-template #captionEnd>\n @if (canCreate()) {\n <mt-button\n [label]=\"t('create-connection')\"\n icon=\"general.plus\"\n (onClick)=\"createConnection()\"\n />\n }\n </ng-template>\n </mt-table>\n }\n </section>\n</ng-container>\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "captionStartTemplate", "captionEndTemplate", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2387
2613
  }
2388
2614
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryConnections, decorators: [{
2389
2615
  type: Component,
2390
- args: [{ selector: 'mt-email-delivery-connections', imports: [TranslocoDirective, Button, EmptyState, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <section class=\"space-y-4\" aria-labelledby=\"connections-heading\">\r\n <div\r\n class=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\"\r\n >\r\n <div>\r\n <h2 id=\"connections-heading\" class=\"text-lg font-semibold text-color\">\r\n {{ t(\"connections-title\") }}\r\n </h2>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ t(\"connections-description\") }}\r\n </p>\r\n </div>\r\n @if (\r\n canConfigure &&\r\n facade.managementAvailable() &&\r\n facade.providers().length > 0\r\n ) {\r\n <mt-button\r\n [label]=\"t('create-connection')\"\r\n icon=\"general.plus\"\r\n (onClick)=\"createConnection()\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (\r\n !facade.statusLoading() &&\r\n !facade.catalogLoading() &&\r\n facade.providers().length === 0\r\n ) {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('no-providers-title')\"\r\n [description]=\"t('no-providers-description')\"\r\n />\r\n } @else if (\r\n !facade.statusLoading() &&\r\n !facade.connectionsLoading() &&\r\n facade.connections().length === 0\r\n ) {\r\n <mt-empty-state\r\n [title]=\"t('no-connections-title')\"\r\n [description]=\"t('no-connections-description')\"\r\n [actionLabel]=\"\r\n canConfigure && facade.managementAvailable()\r\n ? t('create-connection')\r\n : undefined\r\n \"\r\n actionIcon=\"general.plus\"\r\n (actionClick)=\"createConnection()\"\r\n />\r\n } @else {\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"columns()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"\r\n facade.statusLoading() ||\r\n facade.connectionsLoading() ||\r\n facade.catalogLoading()\r\n \"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n filterMode=\"column\"\r\n actionShape=\"popover\"\r\n [clickableRows]=\"true\"\r\n dataKey=\"id\"\r\n storageKey=\"email-delivery-connections\"\r\n (rowClick)=\"openDetails($event)\"\r\n />\r\n }\r\n </section>\r\n</ng-container>\r\n" }]
2616
+ args: [{ selector: 'mt-email-delivery-connections', imports: [TranslocoDirective, Button, EmptyState, Table], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <section class=\"space-y-4\" aria-label=\"{{ t('connections-title') }}\">\n @if (\n !facade.statusLoading() &&\n !facade.catalogLoading() &&\n facade.providers().length === 0\n ) {\n <mt-empty-state\n illustration=\"none\"\n [title]=\"t('no-providers-title')\"\n [description]=\"t('no-providers-description')\"\n />\n } @else {\n <mt-table\n [data]=\"rows()\"\n [columns]=\"columns()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"\n facade.statusLoading() ||\n facade.connectionsLoading() ||\n facade.catalogLoading()\n \"\n [generalSearch]=\"true\"\n [showFilters]=\"true\"\n [emptyTitle]=\"t('no-connections-title')\"\n [emptyDescription]=\"t('no-connections-description')\"\n [emptyActionLabel]=\"canCreate() ? t('create-connection') : undefined\"\n emptyActionIcon=\"general.plus\"\n filterMode=\"column\"\n actionShape=\"popover\"\n [clickableRows]=\"true\"\n dataKey=\"id\"\n storageKey=\"email-delivery-connections\"\n (emptyAction)=\"createConnection()\"\n (rowClick)=\"openDetails($event)\"\n >\n <ng-template #captionEnd>\n @if (canCreate()) {\n <mt-button\n [label]=\"t('create-connection')\"\n icon=\"general.plus\"\n (onClick)=\"createConnection()\"\n />\n }\n </ng-template>\n </mt-table>\n }\n </section>\n</ng-container>\n" }]
2391
2617
  }] });
2392
2618
  const connectionStates = [
2393
2619
  'Draft',
@@ -2421,6 +2647,43 @@ class EmailDeliveryShell {
2421
2647
  { label: this.t('tabs.activity'), value: 'activity' },
2422
2648
  ];
2423
2649
  }, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
2650
+ /**
2651
+ * Same trail the sibling control-panel pages render, so Email Delivery reads
2652
+ * as part of Product Settings rather than a standalone screen.
2653
+ */
2654
+ breadcrumbItems = computed(() => {
2655
+ this.activeLanguage();
2656
+ return [
2657
+ {
2658
+ label: '',
2659
+ icon: 'general.home-line',
2660
+ routerLink: '/control-panel/workspaces',
2661
+ },
2662
+ {
2663
+ label: this.t('breadcrumb.product-settings'),
2664
+ routerLink: PRODUCT_SETTINGS_ROUTE,
2665
+ },
2666
+ { label: this.t('title') },
2667
+ ];
2668
+ }, ...(ngDevMode ? [{ debugName: "breadcrumbItems" }] : /* istanbul ignore next */ []));
2669
+ /**
2670
+ * Readiness moved out of a page-wide banner and onto a chip beside the title.
2671
+ * The banner's explanation survives as the chip's tooltip, so the reason is
2672
+ * still one hover away without a block of text above every tab.
2673
+ */
2674
+ readiness = computed(() => {
2675
+ const status = this.facade.status();
2676
+ if (!status)
2677
+ return null;
2678
+ if (!status.managementEnabled)
2679
+ return 'unavailable';
2680
+ return status.isReady ? 'ready' : 'needs-setup';
2681
+ }, ...(ngDevMode ? [{ debugName: "readiness" }] : /* istanbul ignore next */ []));
2682
+ readinessHint = computed(() => {
2683
+ this.activeLanguage();
2684
+ const status = this.facade.status();
2685
+ return status ? this.facade.describeCode(status.code) : '';
2686
+ }, ...(ngDevMode ? [{ debugName: "readinessHint" }] : /* istanbul ignore next */ []));
2424
2687
  constructor() {
2425
2688
  this.route.queryParamMap.pipe(takeUntilDestroyed()).subscribe((params) => {
2426
2689
  this.activeTab.set(params.get('tab') === 'activity' ? 'activity' : 'connections');
@@ -2443,7 +2706,7 @@ class EmailDeliveryShell {
2443
2706
  });
2444
2707
  }
2445
2708
  goBack() {
2446
- void this.router.navigate(['/control-panel/product-settings']);
2709
+ void this.router.navigate([PRODUCT_SETTINGS_ROUTE]);
2447
2710
  }
2448
2711
  syncTabFromUrl() {
2449
2712
  this.activeTab.set(this.route.snapshot.queryParamMap.get('tab') === 'activity'
@@ -2484,18 +2747,22 @@ class EmailDeliveryShell {
2484
2747
  return this.transloco.translate(`email-delivery.${key}`);
2485
2748
  }
2486
2749
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryShell, deps: [], target: i0.ɵɵFactoryTarget.Component });
2487
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryShell, isStandalone: true, selector: "mt-email-delivery-shell", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <mt-page\r\n [title]=\"t('title')\"\r\n avatarIcon=\"communication.mail-01\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-blue-100)',\r\n '--p-avatar-color': 'var(--p-blue-700)',\r\n }\"\r\n [tabs]=\"canView ? tabs() : []\"\r\n [activeTab]=\"activeTab()\"\r\n contentClass=\"min-h-0 max-[1025px]:p-4 max-[640px]:p-3\"\r\n backButton\r\n (backButtonClick)=\"goBack()\"\r\n (tabChange)=\"onTabChange($event)\"\r\n >\r\n @if (canView) {\r\n <p class=\"mb-4 text-sm text-muted-color\">\r\n {{ t(\"description\") }}\r\n </p>\r\n @if (facade.status(); as status) {\r\n <div\r\n class=\"mb-4 rounded-lg border px-4 py-3\"\r\n [class.border-emerald-200]=\"status.isReady\"\r\n [class.bg-emerald-50]=\"status.isReady\"\r\n [class.border-amber-200]=\"!status.isReady && status.managementEnabled\"\r\n [class.bg-amber-50]=\"!status.isReady && status.managementEnabled\"\r\n [class.border-surface-300]=\"!status.managementEnabled\"\r\n [class.bg-surface-100]=\"!status.managementEnabled\"\r\n role=\"status\"\r\n >\r\n <p class=\"font-semibold text-color\">\r\n {{\r\n status.isReady\r\n ? t(\"ready\")\r\n : status.managementEnabled\r\n ? t(\"needs-setup\")\r\n : t(\"unavailable\")\r\n }}\r\n </p>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ facade.describeCode(status.code) }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (activeTab() === \"activity\") {\r\n <mt-email-delivery-activity />\r\n } @else {\r\n <mt-email-delivery-connections />\r\n }\r\n } @else {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('access-denied-title')\"\r\n [description]=\"t('access-denied-description')\"\r\n />\r\n }\r\n </mt-page>\r\n</ng-container>\r\n", dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "component", type: EmailDeliveryConnections, selector: "mt-email-delivery-connections" }, { kind: "component", type: EmailDeliveryActivity, selector: "mt-email-delivery-activity" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2750
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: EmailDeliveryShell, isStandalone: true, selector: "mt-email-delivery-shell", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <mt-page\n [title]=\"t('title')\"\n avatarIcon=\"communication.mail-01\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-blue-100)',\n '--p-avatar-color': 'var(--p-blue-700)',\n }\"\n [tabs]=\"canView ? tabs() : []\"\n [activeTab]=\"activeTab()\"\n contentClass=\"min-h-0 max-[1025px]:p-4 max-[640px]:p-3\"\n backButton\n (backButtonClick)=\"goBack()\"\n (tabChange)=\"onTabChange($event)\"\n >\n <!-- Declared unconditionally: a content query cannot reach a template that\n only exists inside a control-flow block, so the condition lives here. -->\n <ng-template #titleEnd>\n @if (canView && readiness(); as state) {\n <span class=\"shrink-0\" [mtTooltip]=\"readinessHint()\">\n <mt-email-delivery-status kind=\"readiness\" [value]=\"state\" />\n </span>\n }\n </ng-template>\n\n @if (canView) {\n <div\n class=\"flex min-h-0 flex-col gap-4 [&_.mt-actions-column]:w-[5rem] [&_mt-card]:rounded-md [&_mt-table_:has(>.p-datatable)]:rounded-md\"\n >\n <mt-breadcrumb\n [items]=\"breadcrumbItems()\"\n styleClass=\"mx-1 flex justify-start\"\n />\n\n @if (activeTab() === \"activity\") {\n <mt-email-delivery-activity />\n } @else {\n <mt-email-delivery-connections />\n }\n </div>\n } @else {\n <mt-empty-state\n illustration=\"none\"\n [title]=\"t('access-denied-title')\"\n [description]=\"t('access-denied-description')\"\n />\n }\n </mt-page>\n</ng-container>\n", dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: EmptyState, selector: "mt-empty-state", inputs: ["variant", "illustration", "title", "description", "actionLabel", "actionIcon"], outputs: ["actionClick"] }, { kind: "directive", type: Tooltip, selector: "[mtTooltip]" }, { kind: "component", type: EmailDeliveryStatus, selector: "mt-email-delivery-status", inputs: ["value", "kind"] }, { kind: "component", type: EmailDeliveryConnections, selector: "mt-email-delivery-connections" }, { kind: "component", type: EmailDeliveryActivity, selector: "mt-email-delivery-activity" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2488
2751
  }
2489
2752
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: EmailDeliveryShell, decorators: [{
2490
2753
  type: Component,
2491
2754
  args: [{ selector: 'mt-email-delivery-shell', imports: [
2492
2755
  Page,
2493
2756
  TranslocoDirective,
2757
+ Breadcrumb,
2494
2758
  EmptyState,
2759
+ Tooltip,
2760
+ EmailDeliveryStatus,
2495
2761
  EmailDeliveryConnections,
2496
2762
  EmailDeliveryActivity,
2497
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\r\n <mt-page\r\n [title]=\"t('title')\"\r\n avatarIcon=\"communication.mail-01\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-blue-100)',\r\n '--p-avatar-color': 'var(--p-blue-700)',\r\n }\"\r\n [tabs]=\"canView ? tabs() : []\"\r\n [activeTab]=\"activeTab()\"\r\n contentClass=\"min-h-0 max-[1025px]:p-4 max-[640px]:p-3\"\r\n backButton\r\n (backButtonClick)=\"goBack()\"\r\n (tabChange)=\"onTabChange($event)\"\r\n >\r\n @if (canView) {\r\n <p class=\"mb-4 text-sm text-muted-color\">\r\n {{ t(\"description\") }}\r\n </p>\r\n @if (facade.status(); as status) {\r\n <div\r\n class=\"mb-4 rounded-lg border px-4 py-3\"\r\n [class.border-emerald-200]=\"status.isReady\"\r\n [class.bg-emerald-50]=\"status.isReady\"\r\n [class.border-amber-200]=\"!status.isReady && status.managementEnabled\"\r\n [class.bg-amber-50]=\"!status.isReady && status.managementEnabled\"\r\n [class.border-surface-300]=\"!status.managementEnabled\"\r\n [class.bg-surface-100]=\"!status.managementEnabled\"\r\n role=\"status\"\r\n >\r\n <p class=\"font-semibold text-color\">\r\n {{\r\n status.isReady\r\n ? t(\"ready\")\r\n : status.managementEnabled\r\n ? t(\"needs-setup\")\r\n : t(\"unavailable\")\r\n }}\r\n </p>\r\n <p class=\"mt-1 text-sm text-muted-color\">\r\n {{ facade.describeCode(status.code) }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (activeTab() === \"activity\") {\r\n <mt-email-delivery-activity />\r\n } @else {\r\n <mt-email-delivery-connections />\r\n }\r\n } @else {\r\n <mt-empty-state\r\n illustration=\"none\"\r\n [title]=\"t('access-denied-title')\"\r\n [description]=\"t('access-denied-description')\"\r\n />\r\n }\r\n </mt-page>\r\n</ng-container>\r\n" }]
2763
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'email-delivery'\">\n <mt-page\n [title]=\"t('title')\"\n avatarIcon=\"communication.mail-01\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-blue-100)',\n '--p-avatar-color': 'var(--p-blue-700)',\n }\"\n [tabs]=\"canView ? tabs() : []\"\n [activeTab]=\"activeTab()\"\n contentClass=\"min-h-0 max-[1025px]:p-4 max-[640px]:p-3\"\n backButton\n (backButtonClick)=\"goBack()\"\n (tabChange)=\"onTabChange($event)\"\n >\n <!-- Declared unconditionally: a content query cannot reach a template that\n only exists inside a control-flow block, so the condition lives here. -->\n <ng-template #titleEnd>\n @if (canView && readiness(); as state) {\n <span class=\"shrink-0\" [mtTooltip]=\"readinessHint()\">\n <mt-email-delivery-status kind=\"readiness\" [value]=\"state\" />\n </span>\n }\n </ng-template>\n\n @if (canView) {\n <div\n class=\"flex min-h-0 flex-col gap-4 [&_.mt-actions-column]:w-[5rem] [&_mt-card]:rounded-md [&_mt-table_:has(>.p-datatable)]:rounded-md\"\n >\n <mt-breadcrumb\n [items]=\"breadcrumbItems()\"\n styleClass=\"mx-1 flex justify-start\"\n />\n\n @if (activeTab() === \"activity\") {\n <mt-email-delivery-activity />\n } @else {\n <mt-email-delivery-connections />\n }\n </div>\n } @else {\n <mt-empty-state\n illustration=\"none\"\n [title]=\"t('access-denied-title')\"\n [description]=\"t('access-denied-description')\"\n />\n }\n </mt-page>\n</ng-container>\n" }]
2498
2764
  }], ctorParameters: () => [] });
2765
+ const PRODUCT_SETTINGS_ROUTE = '/control-panel/product-settings';
2499
2766
 
2500
2767
  export { EmailDeliveryShell };
2501
- //# sourceMappingURL=masterteam-system-settings-email-delivery-shell-DowMjl4L.mjs.map
2768
+ //# sourceMappingURL=masterteam-system-settings-email-delivery-shell-Co2SifYp.mjs.map