@masterteam/system-settings 0.0.14 → 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.
- package/assets/i18n/ar.json +382 -370
- package/assets/i18n/en.json +382 -370
- package/assets/system-settings.css +1 -1
- package/fesm2022/{masterteam-system-settings-email-delivery-shell-DowMjl4L.mjs → masterteam-system-settings-email-delivery-shell-Co2SifYp.mjs} +458 -191
- package/fesm2022/masterteam-system-settings-email-delivery-shell-Co2SifYp.mjs.map +1 -0
- package/fesm2022/masterteam-system-settings.mjs +17 -6
- package/fesm2022/masterteam-system-settings.mjs.map +1 -1
- package/package.json +4 -4
- package/types/masterteam-system-settings.d.ts +10 -1
- package/fesm2022/masterteam-system-settings-email-delivery-shell-DowMjl4L.mjs.map +0 -1
|
@@ -1,31 +1,137 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
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) =>
|
|
78
|
-
|
|
79
|
-
|
|
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) =>
|
|
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) =>
|
|
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'\">\
|
|
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'\">\
|
|
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'\">\
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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'\">\
|
|
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
|
-
{
|
|
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: '
|
|
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'\">\
|
|
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,
|
|
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: '
|
|
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'\">\
|
|
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,
|
|
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'\">\
|
|
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'\">\
|
|
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
|
|
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'\">\
|
|
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'\">\
|
|
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
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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'\">\
|
|
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: [
|
|
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:
|
|
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'\">\
|
|
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('
|
|
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:
|
|
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:
|
|
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'\">\
|
|
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'\">\
|
|
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([
|
|
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'\">\
|
|
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'\">\
|
|
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-
|
|
2768
|
+
//# sourceMappingURL=masterteam-system-settings-email-delivery-shell-Co2SifYp.mjs.map
|