mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-31 03:22:15 +00:00
feat: SMTP Templates (#6932)
* feat: smtp templates poc * feat: add isActive & ProviderType to SMTP backend * feat: change providertype to uint32 and fix tests * feat: minimal smtp provider component * feat: woking on diiferent providers * feat: keep working on providers * feat: initial stepper for new provider * fix: settings list and working on stepper * feat: step 1 and 2 form inputs * feat: starter for smtp test step * fix: misspelled SMPT * fix: remove tests for now * feat: add tls toggle remove old google provider * feat: working on add smtp and table * fix: duplicated identifiers * fix: settings list * fix: add missing smtp config properties * fix: add configID to smtp config table * fix: working on listproviders * feat: working in listSMTPConfigs * fix: add count to listsmtpconfigs * fix: getting empty results from listSMTPConfigs * feat: table now shows real data * fix: remaining styles for smtp-table * fix: remove old notification-smtp-provider-component * feat: delete smtp configuration * feat: deactivate smtp config * feat: replace isActive with state for smtp config * feat: activate smtp config * fix: remaining errors after main merge * fix: list smtp providers panic and material mdc * feat: refactor to only one provider component * feat: current provider details view * fix: refactor AddSMTPConfig and ChangeSMTPConfig * fix: smtp config reduce issue * fix: recover domain in NewIAMSMTPConfigWriteModel * fix: add code needed by SetUpInstance * fix: go tests and warn about passing context to InstanceAggregateFromWriteModel * fix: i18n and add missing trans for fr, it, zh * fix: add e2e tests * docs: add smtp templates * fix: remove provider_type, add description * fix: remaining error from merge main * fix: add @stebenz change for primary key * fix: inactive placed after removed to prevent deleted configs to show as inactive * fix: smtp provider id can be empty (migrated) * feat: add mailchimp transactional template * feat: add Brevo (Sendinblue) template * feat: change brevo logo, add color to tls icon * fix: queries use resourceowner, id must not be empty * fix: deal with old smtp settings and tests * fix: resourceOwner is the instanceID * fix: remove aggregate_id, rename SMTPConfigByAggregateID with SMTPConfigActive * fix: add tests for multiple configs with different IDs * fix: conflict * fix: remove notification-smtp-provider * fix: add @peintnermax suggestions, rename module and fix e2e tests * fix: remove material legacy modules * fix: remove ctx as parameter for InstanceAggregateFromWriteModel * fix: add Id to SMTPConfigToPb * fix: change InstanceAggregateFromWriteModel to avoid linter errors * fix import * rm unused package-lock * update yarn lock --------- Co-authored-by: Elio Bischof <[email protected]> Co-authored-by: Max Peintner <[email protected]> Co-authored-by: Stefan Benz <[email protected]>
This commit is contained in:
co-authored by
Elio Bischof
Max Peintner
Stefan Benz
parent
e2f0cd034a
commit
d229da6af7
+24
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider.component';
|
||||
|
||||
describe('PasswordDialogComponent', () => {
|
||||
let component: DialogAddSMSProviderComponent;
|
||||
let fixture: ComponentFixture<DialogAddSMSProviderComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DialogAddSMSProviderComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DialogAddSMSProviderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+2
-2
@@ -10,7 +10,7 @@ import {
|
||||
import { SMSProvider, TwilioConfig } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { PasswordDialogComponent } from '../password-dialog/password-dialog.component';
|
||||
import { PasswordDialogSMSProviderComponent } from '../password-dialog-sms-provider/password-dialog-sms-provider.component';
|
||||
|
||||
enum SMSProviderType {
|
||||
Twilio = 1,
|
||||
@@ -73,7 +73,7 @@ export class DialogAddSMSProviderComponent {
|
||||
}
|
||||
|
||||
public changeToken(): void {
|
||||
const dialogRef = this.dialog.open(PasswordDialogComponent, {
|
||||
const dialogRef = this.dialog.open(PasswordDialogSMSProviderComponent, {
|
||||
width: '400px',
|
||||
data: {
|
||||
i18nTitle: 'SETTING.SMS.TWILIO.SETTOKEN',
|
||||
|
||||
+4
-2
@@ -6,6 +6,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
|
||||
@@ -16,10 +17,11 @@ import { InputModule } from '../../input/input.module';
|
||||
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
|
||||
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
|
||||
import { NotificationSMSProviderComponent } from './notification-sms-provider.component';
|
||||
import { PasswordDialogSMSProviderComponent } from './password-dialog-sms-provider/password-dialog-sms-provider.component';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationSMSProviderComponent, DialogAddSMSProviderComponent],
|
||||
declarations: [NotificationSMSProviderComponent, DialogAddSMSProviderComponent, PasswordDialogSMSProviderComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardModule,
|
||||
@@ -34,9 +36,9 @@ import { MatDialogModule } from '@angular/material/dialog';
|
||||
FormFieldModule,
|
||||
WarnDialogModule,
|
||||
MatSelectModule,
|
||||
MatDialogModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
MatDialogModule,
|
||||
TranslateModule,
|
||||
],
|
||||
exports: [NotificationSMSProviderComponent],
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { PasswordDialogComponent } from './password-dialog.component';
|
||||
import { PasswordDialogComponent } from './password-dialog-sms-provider.component';
|
||||
|
||||
describe('PasswordDialogComponent', () => {
|
||||
let component: PasswordDialogComponent;
|
||||
+5
-5
@@ -2,14 +2,14 @@ import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-password-dialog',
|
||||
templateUrl: './password-dialog.component.html',
|
||||
styleUrls: ['./password-dialog.component.scss'],
|
||||
selector: 'cnsl-password-dialog-sms-provider',
|
||||
templateUrl: './password-dialog-sms-provider.component.html',
|
||||
styleUrls: ['./password-dialog-sms-provider.component.scss'],
|
||||
})
|
||||
export class PasswordDialogComponent {
|
||||
export class PasswordDialogSMSProviderComponent {
|
||||
public password: string = '';
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<PasswordDialogComponent>,
|
||||
public dialogRef: MatDialogRef<PasswordDialogSMSProviderComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
) {}
|
||||
|
||||
+31
-64
@@ -1,70 +1,37 @@
|
||||
<h2>{{ 'DESCRIPTIONS.SETTINGS.SMTP_PROVIDER.TITLE' | translate }}</h2>
|
||||
<p class="cnsl-secondary-text">{{ 'DESCRIPTIONS.SETTINGS.SMTP_PROVIDER.DESCRIPTION' | translate }}</p>
|
||||
<h2>{{ 'SMTP.LIST.TITLE' | translate }}</h2>
|
||||
<p class="cnsl-secondary-text">{{ 'SMTP.LIST.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<div class="spinner-wr">
|
||||
<mat-spinner diameter="30" *ngIf="smtpLoading" color="primary"></mat-spinner>
|
||||
<div class="cnsl-snmp-table-wrapper">
|
||||
<cnsl-smtp-table></cnsl-smtp-table>
|
||||
</div>
|
||||
|
||||
<cnsl-info-section
|
||||
*ngIf="!smtpLoading && !form.valid"
|
||||
class="info-section-warn"
|
||||
[fitWidth]="true"
|
||||
[type]="InfoSectionType.ALERT"
|
||||
>{{ 'SETTING.SMTP.REQUIREDWARN' | translate }}</cnsl-info-section
|
||||
>
|
||||
<h2>{{ 'SMTP.CREATE.TITLE' | translate }}</h2>
|
||||
<p class="cnsl-secondary-text">{{ 'SMTP.CREATE.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderAddress" formControlName="senderAddress" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Name" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderName" formControlName="senderName" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Reply-To Address">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderReplyToAddress" formControlName="replyToAddress" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-checkbox class="smtp-checkbox" formControlName="tls">
|
||||
{{ 'SETTING.SMTP.TLS' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
|
||||
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="User" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
|
||||
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<button
|
||||
class="set-password-btn"
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false || !hasSMTPConfig"
|
||||
(click)="setSMTPPassword()"
|
||||
type="button"
|
||||
mat-stroked-button
|
||||
data-e2e="add-smtp-password-button"
|
||||
>
|
||||
{{ 'SETTING.SMTP.SETPASSWORD' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="general-btn-container">
|
||||
<button
|
||||
class="save-button"
|
||||
[disabled]="form.disabled"
|
||||
(click)="savePolicy()"
|
||||
color="primary"
|
||||
type="submit"
|
||||
mat-raised-button
|
||||
data-e2e="save-smtp-settings-button"
|
||||
<div class="new-smtp-wrapper">
|
||||
<div *ngFor="let provider of providers">
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="['/instance', 'smtpprovider', provider.routerLinkElement, 'create']"
|
||||
*ngIf="provider.name !== 'generic'"
|
||||
>
|
||||
{{ 'ACTIONS.SAVE' | translate }}
|
||||
</button>
|
||||
<img class="smtp-logo" src="{{ provider.image }}" alt="{{ provider.name }}" />
|
||||
<div class="text-container">
|
||||
<span class="title">{{ provider.name | titlecase }} </span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="item card"
|
||||
[routerLink]="['/instance', 'smtpprovider', provider.routerLinkElement, 'create']"
|
||||
*ngIf="provider.name === 'generic'"
|
||||
>
|
||||
<div class="smtp-icon">
|
||||
<mat-icon class="icon" svgIcon="mdi_smtp" />
|
||||
</div>
|
||||
<div class="text-container">
|
||||
<span class="title">Generic SMTP</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
+108
-27
@@ -1,32 +1,113 @@
|
||||
.spinner-wr {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.smtp-form-field,
|
||||
.info-section-warn {
|
||||
max-width: 400px;
|
||||
display: block;
|
||||
}
|
||||
@mixin smtp-settings-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.info-section-warn {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.smtp-checkbox {
|
||||
max-width: 400px;
|
||||
display: block;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.set-password-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.general-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
.save-button {
|
||||
.cnsl-smtp-table-wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.new-smtp-wrapper {
|
||||
display: grid;
|
||||
row-gap: 1.5rem;
|
||||
column-gap: 1.5rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1000px) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1300px) {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
padding-top: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
padding-left: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
transition: box-shadow 0.1s ease-in;
|
||||
align-items: center;
|
||||
color: map-get($foreground, text);
|
||||
|
||||
.coming-soon-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1rem;
|
||||
transform: translateY(-50%);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.smtp-logo {
|
||||
margin-right: 1rem;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
|
||||
&.apple {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
||||
&.light {
|
||||
display: if($is-dark-theme, none, block);
|
||||
}
|
||||
}
|
||||
|
||||
.smtp-icon {
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
|
||||
.icon {
|
||||
font-size: 2.25rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
}
|
||||
}
|
||||
|
||||
&.coming-soon {
|
||||
filter: grayscale(1);
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component';
|
||||
|
||||
describe('NotificationSMTPProviderComponent', () => {
|
||||
describe('IdpSettingsComponent', () => {
|
||||
let component: NotificationSMTPProviderComponent;
|
||||
let fixture: ComponentFixture<NotificationSMTPProviderComponent>;
|
||||
|
||||
|
||||
+14
-157
@@ -1,22 +1,9 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { take } from 'rxjs';
|
||||
import {
|
||||
AddSMTPConfigRequest,
|
||||
AddSMTPConfigResponse,
|
||||
UpdateSMTPConfigPasswordRequest,
|
||||
UpdateSMTPConfigRequest,
|
||||
UpdateSMTPConfigResponse,
|
||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import { Component, Injector, Input, OnInit, Type } from '@angular/core';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { requiredValidator } from '../../form-field/validators/validators';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
|
||||
import { InfoSectionType } from '../../info-section/info-section.component';
|
||||
import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component';
|
||||
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||
import { SMTPKnownProviders } from '../../smtp-provider/known-smtp-providers-settings';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-notification-smtp-provider',
|
||||
@@ -25,151 +12,21 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||
})
|
||||
export class NotificationSMTPProviderComponent implements OnInit {
|
||||
@Input() public serviceType!: PolicyComponentServiceType;
|
||||
public service!: ManagementService | AdminService;
|
||||
|
||||
public smtpLoading: boolean = false;
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
public providers = SMTPKnownProviders;
|
||||
|
||||
public form!: UntypedFormGroup;
|
||||
|
||||
public InfoSectionType: any = InfoSectionType;
|
||||
|
||||
public hasSMTPConfig: boolean = false;
|
||||
|
||||
// show available providers
|
||||
|
||||
constructor(
|
||||
private service: AdminService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
private fb: UntypedFormBuilder,
|
||||
private authService: GrpcAuthService,
|
||||
) {
|
||||
this.form = this.fb.group({
|
||||
senderAddress: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
senderName: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
replyToAddress: [{ disabled: true, value: '' }],
|
||||
tls: [{ disabled: true, value: true }, [requiredValidator]],
|
||||
hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
user: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
});
|
||||
}
|
||||
constructor(private injector: Injector) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fetchData();
|
||||
this.authService
|
||||
.isAllowed(['iam.write'])
|
||||
.pipe(take(1))
|
||||
.subscribe((allowed) => {
|
||||
if (allowed) {
|
||||
this.form.enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private fetchData(): void {
|
||||
this.smtpLoading = true;
|
||||
this.service
|
||||
.getSMTPConfig()
|
||||
.then((smtpConfig) => {
|
||||
this.smtpLoading = false;
|
||||
if (smtpConfig.smtpConfig) {
|
||||
this.hasSMTPConfig = true;
|
||||
this.form.patchValue(smtpConfig.smtpConfig);
|
||||
this.form.patchValue({ ['hostAndPort']: smtpConfig.smtpConfig.host });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.smtpLoading = false;
|
||||
if (error && error.code === 5) {
|
||||
console.log(error);
|
||||
this.hasSMTPConfig = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> {
|
||||
if (this.hasSMTPConfig) {
|
||||
const req = new UpdateSMTPConfigRequest();
|
||||
req.setHost(this.hostAndPort?.value ?? '');
|
||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||
req.setSenderName(this.senderName?.value ?? '');
|
||||
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
req.setUser(this.user?.value ?? '');
|
||||
|
||||
return this.service.updateSMTPConfig(req);
|
||||
} else {
|
||||
const req = new AddSMTPConfigRequest();
|
||||
req.setHost(this.hostAndPort?.value ?? '');
|
||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||
req.setSenderName(this.senderName?.value ?? '');
|
||||
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
req.setUser(this.user?.value ?? '');
|
||||
|
||||
return this.service.addSMTPConfig(req);
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
this.service = this.injector.get(ManagementService as Type<ManagementService>);
|
||||
break;
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
this.service = this.injector.get(AdminService as Type<AdminService>);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public savePolicy(): void {
|
||||
this.updateData()
|
||||
.then(() => {
|
||||
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||
setTimeout(() => {
|
||||
this.fetchData();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public setSMTPPassword(): void {
|
||||
const dialogRef = this.dialog.open(PasswordDialogComponent, {
|
||||
width: '400px',
|
||||
data: {
|
||||
i18nTitle: 'SETTING.SMTP.SETPASSWORD',
|
||||
i18nLabel: 'SETTING.SMTP.PASSWORD',
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((password: string) => {
|
||||
if (password) {
|
||||
const passwordReq = new UpdateSMTPConfigPasswordRequest();
|
||||
passwordReq.setPassword(password);
|
||||
|
||||
this.service
|
||||
.updateSMTPConfigPassword(passwordReq)
|
||||
.then(() => {
|
||||
this.toast.showInfo('SETTING.SMTP.PASSWORDSET', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get senderAddress(): AbstractControl | null {
|
||||
return this.form.get('senderAddress');
|
||||
}
|
||||
|
||||
public get senderName(): AbstractControl | null {
|
||||
return this.form.get('senderName');
|
||||
}
|
||||
|
||||
public get replyToAddress(): AbstractControl | null {
|
||||
return this.form.get('replyToAddress');
|
||||
}
|
||||
|
||||
public get tls(): AbstractControl | null {
|
||||
return this.form.get('tls');
|
||||
}
|
||||
|
||||
public get user(): AbstractControl | null {
|
||||
return this.form.get('user');
|
||||
}
|
||||
|
||||
public get hostAndPort(): AbstractControl | null {
|
||||
return this.form.get('hostAndPort');
|
||||
}
|
||||
}
|
||||
|
||||
+14
-25
@@ -1,43 +1,32 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
|
||||
import { CardModule } from '../../card/card.module';
|
||||
import { FormFieldModule } from '../../form-field/form-field.module';
|
||||
import { InfoSectionModule } from '../../info-section/info-section.module';
|
||||
import { InputModule } from '../../input/input.module';
|
||||
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
|
||||
import { PasswordDialogComponent } from '../notification-sms-provider/password-dialog/password-dialog.component';
|
||||
import { NotificationSMTPProviderComponent } from './notification-smtp-provider.component';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { InputModule } from '../../input/input.module';
|
||||
import { FormFieldModule } from '../../form-field/form-field.module';
|
||||
import { SMTPTableModule } from '../../smtp-table/smtp-table.module';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
@NgModule({
|
||||
declarations: [NotificationSMTPProviderComponent, PasswordDialogComponent],
|
||||
declarations: [NotificationSMTPProviderComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardModule,
|
||||
InfoSectionModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HasRolePipeModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
InputModule,
|
||||
MatIconModule,
|
||||
FormFieldModule,
|
||||
WarnDialogModule,
|
||||
MatSelectModule,
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
CardModule,
|
||||
MatIconModule,
|
||||
SMTPTableModule,
|
||||
RouterModule,
|
||||
HasRolePipeModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSelectModule,
|
||||
TranslateModule,
|
||||
MatDialogModule,
|
||||
],
|
||||
exports: [NotificationSMTPProviderComponent],
|
||||
})
|
||||
|
||||
@@ -14,7 +14,6 @@ import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.modu
|
||||
import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module';
|
||||
import { NotificationPolicyModule } from '../policies/notification-policy/notification-policy.module';
|
||||
import { NotificationSMSProviderModule } from '../policies/notification-sms-provider/notification-sms-provider.module';
|
||||
import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module';
|
||||
import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module';
|
||||
import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module';
|
||||
import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module';
|
||||
@@ -28,7 +27,9 @@ import FailedEventsModule from '../failed-events/failed-events.module';
|
||||
import IamViewsModule from '../iam-views/iam-views.module';
|
||||
import EventsModule from '../events/events.module';
|
||||
import { OrgTableModule } from '../org-table/org-table.module';
|
||||
import { NotificationSMTPProviderModule } from '../policies/notification-smtp-provider/notification-smtp-provider.module';
|
||||
import { FeaturesComponent } from 'src/app/components/features/features.component';
|
||||
import OrgListModule from 'src/app/pages/org-list/org-list.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SettingsListComponent],
|
||||
@@ -44,12 +45,14 @@ import { FeaturesComponent } from 'src/app/components/features/features.componen
|
||||
LanguageSettingsModule,
|
||||
NotificationPolicyModule,
|
||||
IdpSettingsModule,
|
||||
NotificationSMTPProviderModule,
|
||||
PrivacyPolicyModule,
|
||||
MessageTextsPolicyModule,
|
||||
SecurityPolicyModule,
|
||||
DomainsModule,
|
||||
LoginTextsPolicyModule,
|
||||
OrgTableModule,
|
||||
OrgListModule,
|
||||
DomainPolicyModule,
|
||||
TranslateModule,
|
||||
HasRolePipeModule,
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
export interface AmazonRegionsEndpoints {
|
||||
'US East (Ohio)': string;
|
||||
'US East (N. Virginia)': string;
|
||||
'US West (N. California)': string;
|
||||
'US West (Oregon)': string;
|
||||
'Asia Pacific (Mumbai)': string;
|
||||
'Asia Pacific (Osaka)': string;
|
||||
'Asia Pacific (Seoul)': string;
|
||||
'Asia Pacific (Singapore)': string;
|
||||
'Asia Pacific (Sydney)': string;
|
||||
'Asia Pacific (Tokyo)': string;
|
||||
'Canada (Central)': string;
|
||||
'Europe (Frankfurt)': string;
|
||||
'Europe (London)': string;
|
||||
'Europe (Paris)': string;
|
||||
'Europe (Stockholm)': string;
|
||||
'South America (São Paulo)': string;
|
||||
}
|
||||
|
||||
const amazonEndpoints = {
|
||||
'US East (Ohio)': 'email-smtp.us-east-2.amazonaws.com',
|
||||
'US East (N. Virginia)': 'email-smtp.us-east-1.amazonaws.com',
|
||||
'US West (N. California)': 'email-smtp.us-west-1.amazonaws.com',
|
||||
'US West (Oregon)': 'email-smtp.us-west-2.amazonaws.com',
|
||||
'Asia Pacific (Mumbai)': 'email-smtp.ap-south-1.amazonaws.com',
|
||||
'Asia Pacific (Osaka)': 'email-smtp.ap-northeast-3.amazonaws.com',
|
||||
'Asia Pacific (Seoul)': 'email-smtp.ap-northeast-2.amazonaws.com',
|
||||
'Asia Pacific (Singapore)': 'email-smtp.ap-southeast-1.amazonaws.com',
|
||||
'Asia Pacific (Sydney)': 'email-smtp.ap-southeast-2.amazonaws.com',
|
||||
'Asia Pacific (Tokyo)': 'email-smtp.ap-northeast-1.amazonaws.com',
|
||||
'Canada (Central)': 'email-smtp.ca-central-1.amazonaws.com',
|
||||
'Europe (Frankfurt)': 'email-smtp.eu-central-1.amazonaws.com',
|
||||
'Europe (Ireland)': 'email-smtp.eu-west-1.amazonaws.com',
|
||||
'Europe (London)': 'email-smtp.eu-west-2.amazonaws.com',
|
||||
'Europe (Paris)': 'email-smtp.eu-west-3.amazonaws.com',
|
||||
'Europe (Stockholm)': 'email-smtp.eu-north-1.amazonaws.com',
|
||||
'South America (São Paulo)': 'email-smtp.sa-east-1.amazonaws.com',
|
||||
};
|
||||
|
||||
export interface ProviderDefaultSettings {
|
||||
name: string;
|
||||
regions?: AmazonRegionsEndpoints;
|
||||
multiHostsLabel?: string;
|
||||
requiredTls: boolean;
|
||||
host?: string;
|
||||
unencryptedPort?: number;
|
||||
encryptedPort?: number;
|
||||
user: {
|
||||
value: string;
|
||||
placeholder: string;
|
||||
};
|
||||
password: {
|
||||
value: string;
|
||||
placeholder: string;
|
||||
};
|
||||
image?: string;
|
||||
routerLinkElement: string;
|
||||
}
|
||||
|
||||
export const AmazonSESDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'amazon SES',
|
||||
regions: amazonEndpoints,
|
||||
multiHostsLabel: 'Choose your region',
|
||||
requiredTls: true,
|
||||
encryptedPort: 587,
|
||||
user: { value: '', placeholder: 'your Amazon SES credentials for this region' },
|
||||
password: { value: '', placeholder: 'your Amazon SES credentials for this region' },
|
||||
image: './assets/images/smtp/aws-ses.svg',
|
||||
routerLinkElement: 'aws-ses',
|
||||
};
|
||||
|
||||
export const GoogleDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'google',
|
||||
requiredTls: true,
|
||||
host: 'smtp.gmail.com',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 587,
|
||||
user: { value: '', placeholder: 'your complete Google Workspace email address' },
|
||||
password: { value: '', placeholder: 'your complete Google Workspace password' },
|
||||
image: './assets/images/smtp/google.png',
|
||||
routerLinkElement: 'google',
|
||||
};
|
||||
|
||||
export const MailgunDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'mailgun',
|
||||
requiredTls: false,
|
||||
host: 'smtp.mailgun.org',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 465,
|
||||
user: { value: '', placeholder: 'postmaster@YOURDOMAIN' },
|
||||
password: { value: '', placeholder: 'Your mailgun smtp password' },
|
||||
image: './assets/images/smtp/mailgun.svg',
|
||||
routerLinkElement: 'mailgun',
|
||||
};
|
||||
|
||||
export const MailjetDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'mailjet',
|
||||
requiredTls: false,
|
||||
host: 'in-v3.mailjet.com',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 465,
|
||||
user: { value: '', placeholder: 'Your Mailjet API key' },
|
||||
password: { value: '', placeholder: 'Your Mailjet Secret key' },
|
||||
image: './assets/images/smtp/mailjet.svg',
|
||||
routerLinkElement: 'mailjet',
|
||||
};
|
||||
|
||||
export const PostmarkDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'postmark',
|
||||
requiredTls: false,
|
||||
host: 'smtp.postmarkapp.com',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 587,
|
||||
user: { value: '', placeholder: 'Your Server API token' },
|
||||
password: { value: '', placeholder: 'Your Server API token' },
|
||||
image: './assets/images/smtp/postmark.png',
|
||||
routerLinkElement: 'postmark',
|
||||
};
|
||||
|
||||
export const SendgridDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'sendgrid',
|
||||
requiredTls: false,
|
||||
host: 'smtp.sendgrid.net',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 465,
|
||||
user: { value: 'apikey', placeholder: '' },
|
||||
password: { value: '', placeholder: ' Your SendGrid API Key' },
|
||||
image: './assets/images/smtp/sendgrid.png',
|
||||
routerLinkElement: 'sendgrid',
|
||||
};
|
||||
|
||||
export const MailchimpDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'mailchimp',
|
||||
requiredTls: false,
|
||||
host: 'smtp.mandrillapp.com',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 465,
|
||||
user: { value: '', placeholder: 'Your Mailchimp primary contact email' },
|
||||
password: { value: '', placeholder: 'Your Mailchimp Transactional API key' },
|
||||
image: './assets/images/smtp/mailchimp.svg',
|
||||
routerLinkElement: 'mailchimp',
|
||||
};
|
||||
|
||||
export const BrevoDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'brevo',
|
||||
requiredTls: false,
|
||||
host: 'smtp-relay.sendinblue.com',
|
||||
unencryptedPort: 587,
|
||||
encryptedPort: 465,
|
||||
user: { value: '', placeholder: 'Your SMTP login email address' },
|
||||
password: { value: '', placeholder: 'Your SMTP key' },
|
||||
image: './assets/images/smtp/brevo.svg',
|
||||
routerLinkElement: 'brevo',
|
||||
};
|
||||
|
||||
export const GenericDefaultSettings: ProviderDefaultSettings = {
|
||||
name: 'generic',
|
||||
requiredTls: false,
|
||||
user: { value: '', placeholder: 'your SMTP user' },
|
||||
password: { value: '', placeholder: 'your SMTP password' },
|
||||
routerLinkElement: 'generic',
|
||||
};
|
||||
|
||||
export const SMTPKnownProviders = [
|
||||
AmazonSESDefaultSettings,
|
||||
BrevoDefaultSettings,
|
||||
// GoogleDefaultSettings,
|
||||
MailgunDefaultSettings,
|
||||
MailchimpDefaultSettings,
|
||||
MailjetDefaultSettings,
|
||||
PostmarkDefaultSettings,
|
||||
SendgridDefaultSettings,
|
||||
GenericDefaultSettings,
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SMTPProviderComponent } from './smtp-provider.component';
|
||||
|
||||
const types = [
|
||||
{ path: 'aws-ses', component: SMTPProviderComponent },
|
||||
{ path: 'generic', component: SMTPProviderComponent },
|
||||
{ path: 'google', component: SMTPProviderComponent },
|
||||
{ path: 'mailgun', component: SMTPProviderComponent },
|
||||
{ path: 'postmark', component: SMTPProviderComponent },
|
||||
{ path: 'sendgrid', component: SMTPProviderComponent },
|
||||
{ path: 'mailjet', component: SMTPProviderComponent },
|
||||
{ path: 'mailchimp', component: SMTPProviderComponent },
|
||||
{ path: 'brevo', component: SMTPProviderComponent },
|
||||
];
|
||||
|
||||
const routes: Routes = types.map((value) => {
|
||||
return {
|
||||
path: value.path,
|
||||
children: [
|
||||
{
|
||||
path: 'create',
|
||||
component: value.component,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
routes.push({
|
||||
path: ':id',
|
||||
component: SMTPProviderComponent,
|
||||
});
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class SMTPProvidersRoutingModule {}
|
||||
@@ -0,0 +1,144 @@
|
||||
<cnsl-create-layout
|
||||
title="{{
|
||||
id ? ('SMTP.DETAIL.TITLE' | translate) : ('SMTP.CREATE.STEPS.TITLE' | translate: { value: providerDefaultSetting.name })
|
||||
}}"
|
||||
[createSteps]="2"
|
||||
[currentCreateStep]="currentCreateStep"
|
||||
(closed)="close()"
|
||||
>
|
||||
<div class="wizard-header">
|
||||
<img
|
||||
class="smtp-logo"
|
||||
src="{{ providerDefaultSetting.image }}"
|
||||
alt="{{ providerDefaultSetting.name }}"
|
||||
*ngIf="providerDefaultSetting.name !== 'generic'"
|
||||
/>
|
||||
<div class="smtp-icon" *ngIf="providerDefaultSetting.name === 'generic'">
|
||||
<mat-icon class="icon" svgIcon="mdi_smtp" alt="providerDefaultSetting.name" />
|
||||
</div>
|
||||
<h2>
|
||||
{{
|
||||
!id
|
||||
? ('SMTP.CREATE.STEPS.CREATE_DESC_TITLE' | translate: { value: providerDefaultSetting.name | titlecase })
|
||||
: ('SMTP.CREATE.STEPS.CURRENT_DESC_TITLE' | translate)
|
||||
}}
|
||||
</h2>
|
||||
</div>
|
||||
<mat-progress-bar class="progress-bar" color="primary" *ngIf="smtpLoading" mode="indeterminate"></mat-progress-bar>
|
||||
|
||||
<mat-horizontal-stepper
|
||||
class="stepper {{ 'last-edited-step-' + stepper.selectedIndex }}"
|
||||
linear
|
||||
#stepper
|
||||
labelPosition="bottom"
|
||||
(selectionChange)="changeStep($event)"
|
||||
>
|
||||
<mat-step [editable]="true">
|
||||
<ng-template matStepLabel>{{ 'SMTP.CREATE.STEPS.PROVIDER_SETTINGS' | translate }}</ng-template>
|
||||
<form [formGroup]="firstFormGroup" autocomplete="off">
|
||||
<mat-checkbox class="smtp-checkbox" formControlName="tls" (change)="toggleTLS($event)" data-e2e="tls-checkbox">
|
||||
{{ 'SETTING.SMTP.TLS' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" *ngIf="providerDefaultSetting.regions">
|
||||
<cnsl-label>{{ providerDefaultSetting.multiHostsLabel }}</cnsl-label>
|
||||
<mat-select formControlName="region">
|
||||
<mat-option *ngFor="let region of providerDefaultSetting.regions | keyvalue" [value]="region.value">
|
||||
{{ region.key }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Description">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.DESCRIPTION' | translate }}</cnsl-label>
|
||||
<input cnslInput name="description" formControlName="description" placeholder="description" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Host And Port">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
|
||||
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="host:port" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="User">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
|
||||
<input
|
||||
id="smtp-user"
|
||||
cnslInput
|
||||
name="smtp-user"
|
||||
autocomplete="smtp-user"
|
||||
formControlName="user"
|
||||
placeholder="{{ providerDefaultSetting.user.placeholder }}"
|
||||
required
|
||||
/>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Password">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.PASSWORD' | translate }}</cnsl-label>
|
||||
<input
|
||||
id="smtp-password"
|
||||
cnslInput
|
||||
name="smtp-password"
|
||||
autocomplete="off webauthn"
|
||||
formControlName="password"
|
||||
placeholder="{{ hasSMTPConfig ? '****************' : providerDefaultSetting.password.placeholder }}"
|
||||
type="password"
|
||||
required="{{ !hasSMTPConfig }}"
|
||||
/>
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
|
||||
<div class="smtp-create-actions">
|
||||
<button
|
||||
mat-raised-button
|
||||
[disabled]="firstFormGroup.invalid"
|
||||
color="primary"
|
||||
matStepperNext
|
||||
data-e2e="continue-button"
|
||||
>
|
||||
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [editable]="true">
|
||||
<form [formGroup]="secondFormGroup">
|
||||
<ng-template matStepLabel>{{ 'SMTP.CREATE.STEPS.SENDER_SETTINGS' | translate }}</ng-template>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Address">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderAddress" formControlName="senderAddress" placeholder="[email protected]" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Name">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderName" formControlName="senderName" placeholder="Zitadel" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Reply-To Address">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderReplyToAddress" formControlName="replyToAddress" placeholder="[email protected]" />
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
|
||||
<div class="smtp-create-actions">
|
||||
<button mat-stroked-button matStepperPrevious class="bck-button">{{ 'ACTIONS.BACK' | translate }}</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
class="create-button"
|
||||
color="primary"
|
||||
data-e2e="create-button"
|
||||
(click)="savePolicy()"
|
||||
[disabled]="
|
||||
firstFormGroup.invalid || secondFormGroup.invalid || (['iam.policy.write'] | hasRole | async) === false
|
||||
"
|
||||
>
|
||||
{{ !hasSMTPConfig ? ('ACTIONS.CREATE' | translate) : ('ACTIONS.SAVE' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<ng-template matStepperIcon="edit">
|
||||
<mat-icon>check</mat-icon>
|
||||
</ng-template>
|
||||
</mat-horizontal-stepper>
|
||||
</cnsl-create-layout>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { SMTPProviderComponent } from './smtp-provider.component';
|
||||
|
||||
describe('SMTPProviderSendgridComponent', () => {
|
||||
let component: SMTPProviderComponent;
|
||||
let fixture: ComponentFixture<SMTPProviderComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SMTPProviderComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SMTPProviderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,278 @@
|
||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { Options } from 'src/app/proto/generated/zitadel/idp_pb';
|
||||
import { requiredValidator } from '../form-field/validators/validators';
|
||||
|
||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||
import {
|
||||
AddSMTPConfigRequest,
|
||||
AddSMTPConfigResponse,
|
||||
UpdateSMTPConfigRequest,
|
||||
UpdateSMTPConfigResponse,
|
||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||
import {
|
||||
AmazonSESDefaultSettings,
|
||||
BrevoDefaultSettings,
|
||||
GenericDefaultSettings,
|
||||
GoogleDefaultSettings,
|
||||
MailchimpDefaultSettings,
|
||||
MailgunDefaultSettings,
|
||||
MailjetDefaultSettings,
|
||||
PostmarkDefaultSettings,
|
||||
ProviderDefaultSettings,
|
||||
SendgridDefaultSettings,
|
||||
} from './known-smtp-providers-settings';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-smtp-provider',
|
||||
templateUrl: './smtp-provider.component.html',
|
||||
styleUrls: ['./smtp-provider.scss'],
|
||||
})
|
||||
export class SMTPProviderComponent {
|
||||
public showOptional: boolean = false;
|
||||
public options: Options = new Options().setIsCreationAllowed(true).setIsLinkingAllowed(true);
|
||||
public id: string = '';
|
||||
public providerDefaultSetting: ProviderDefaultSettings = GenericDefaultSettings;
|
||||
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
|
||||
|
||||
public readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
|
||||
public smtpLoading: boolean = false;
|
||||
public hasSMTPConfig: boolean = false;
|
||||
|
||||
public updateClientSecret: boolean = false;
|
||||
|
||||
// stepper
|
||||
public currentCreateStep: number = 1;
|
||||
public requestRedirectValuesSubject$: Subject<void> = new Subject();
|
||||
public firstFormGroup!: UntypedFormGroup;
|
||||
public secondFormGroup!: UntypedFormGroup;
|
||||
|
||||
constructor(
|
||||
private service: AdminService,
|
||||
private _location: Location,
|
||||
private fb: UntypedFormBuilder,
|
||||
private toast: ToastService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
) {
|
||||
this.route.parent?.url.subscribe((urlPath) => {
|
||||
const providerName = urlPath[urlPath.length - 1].path;
|
||||
switch (providerName) {
|
||||
case 'aws-ses':
|
||||
this.providerDefaultSetting = AmazonSESDefaultSettings;
|
||||
break;
|
||||
case 'google':
|
||||
this.providerDefaultSetting = GoogleDefaultSettings;
|
||||
break;
|
||||
case 'mailgun':
|
||||
this.providerDefaultSetting = MailgunDefaultSettings;
|
||||
break;
|
||||
case 'mailjet':
|
||||
this.providerDefaultSetting = MailjetDefaultSettings;
|
||||
break;
|
||||
case 'postmark':
|
||||
this.providerDefaultSetting = PostmarkDefaultSettings;
|
||||
break;
|
||||
case 'sendgrid':
|
||||
this.providerDefaultSetting = SendgridDefaultSettings;
|
||||
break;
|
||||
case 'mailchimp':
|
||||
this.providerDefaultSetting = MailchimpDefaultSettings;
|
||||
break;
|
||||
case 'brevo':
|
||||
this.providerDefaultSetting = BrevoDefaultSettings;
|
||||
break;
|
||||
}
|
||||
|
||||
this.firstFormGroup = this.fb.group({
|
||||
description: [this.providerDefaultSetting.name],
|
||||
tls: [{ value: this.providerDefaultSetting.requiredTls, disabled: this.providerDefaultSetting.requiredTls }],
|
||||
region: [''],
|
||||
hostAndPort: [
|
||||
this.providerDefaultSetting?.host
|
||||
? `${this.providerDefaultSetting?.host}:${this.providerDefaultSetting?.unencryptedPort}`
|
||||
: '',
|
||||
],
|
||||
user: [this.providerDefaultSetting?.user.value || ''],
|
||||
password: [this.providerDefaultSetting?.password.value || ''],
|
||||
});
|
||||
|
||||
this.secondFormGroup = this.fb.group({
|
||||
senderAddress: ['', [requiredValidator]],
|
||||
senderName: ['', [requiredValidator]],
|
||||
replyToAddress: [''],
|
||||
});
|
||||
|
||||
this.region?.valueChanges.subscribe((region: string) => {
|
||||
this.hostAndPort?.setValue(
|
||||
`${region}:${
|
||||
this.tls ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort
|
||||
}`,
|
||||
);
|
||||
});
|
||||
|
||||
if (!this.router.url.endsWith('/create')) {
|
||||
this.id = this.route.snapshot.paramMap.get('id') || '';
|
||||
if (this.id) {
|
||||
this.fetchData(this.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public changeStep(event: StepperSelectionEvent): void {
|
||||
this.currentCreateStep = event.selectedIndex + 1;
|
||||
|
||||
if (event.selectedIndex >= 2) {
|
||||
this.requestRedirectValuesSubject$.next();
|
||||
}
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._location.back();
|
||||
}
|
||||
|
||||
public toggleTLS(event: MatCheckboxChange) {
|
||||
if (this.providerDefaultSetting.host) {
|
||||
this.hostAndPort?.setValue(
|
||||
`${this.providerDefaultSetting?.host}:${
|
||||
event.checked ? this.providerDefaultSetting?.encryptedPort : this.providerDefaultSetting?.unencryptedPort
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private fetchData(id: string): void {
|
||||
this.smtpLoading = true;
|
||||
this.service
|
||||
.getSMTPConfigById(id)
|
||||
.then((data) => {
|
||||
this.smtpLoading = false;
|
||||
if (data.smtpConfig) {
|
||||
this.hasSMTPConfig = true;
|
||||
this.firstFormGroup.patchValue({
|
||||
['description']: data.smtpConfig.description,
|
||||
['tls']: data.smtpConfig.tls,
|
||||
['hostAndPort']: data.smtpConfig.host,
|
||||
['user']: data.smtpConfig.user,
|
||||
});
|
||||
this.secondFormGroup.patchValue({
|
||||
['senderAddress']: data.smtpConfig.senderAddress,
|
||||
['senderName']: data.smtpConfig.senderName,
|
||||
['replyToAddress']: data.smtpConfig.replyToAddress,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.smtpLoading = false;
|
||||
if (error && error.code === 5) {
|
||||
this.hasSMTPConfig = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateData(): Promise<UpdateSMTPConfigResponse.AsObject | AddSMTPConfigResponse> {
|
||||
if (this.hasSMTPConfig) {
|
||||
const req = new UpdateSMTPConfigRequest();
|
||||
req.setId(this.id);
|
||||
req.setDescription(this.description?.value || '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
|
||||
if (this.hostAndPort && this.hostAndPort.value) {
|
||||
req.setHost(this.hostAndPort.value);
|
||||
}
|
||||
if (this.user && this.user.value) {
|
||||
req.setUser(this.user.value);
|
||||
}
|
||||
if (this.password && this.password.value) {
|
||||
req.setPassword(this.password.value);
|
||||
}
|
||||
if (this.senderAddress && this.senderAddress.value) {
|
||||
req.setSenderAddress(this.senderAddress.value);
|
||||
}
|
||||
if (this.senderName && this.senderName.value) {
|
||||
req.setSenderName(this.senderName.value);
|
||||
}
|
||||
if (this.replyToAddress && this.replyToAddress.value) {
|
||||
req.setReplyToAddress(this.replyToAddress.value);
|
||||
}
|
||||
return this.service.updateSMTPConfig(req);
|
||||
} else {
|
||||
const req = new AddSMTPConfigRequest();
|
||||
req.setDescription(this.description?.value ?? '');
|
||||
req.setHost(this.hostAndPort?.value ?? '');
|
||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||
req.setSenderName(this.senderName?.value ?? '');
|
||||
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
req.setUser(this.user?.value ?? '');
|
||||
req.setPassword(this.password?.value ?? '');
|
||||
return this.service.addSMTPConfig(req);
|
||||
}
|
||||
}
|
||||
|
||||
public savePolicy(): void {
|
||||
this.updateData()
|
||||
.then(() => {
|
||||
this.toast.showInfo('SETTING.SMTP.SAVED', true);
|
||||
setTimeout(() => {
|
||||
this.close();
|
||||
}, 2000);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (`${error}`.includes('No changes')) {
|
||||
this.toast.showInfo('SETTING.SMTP.NOCHANGES', true);
|
||||
setTimeout(() => {
|
||||
this.close();
|
||||
}, 2000);
|
||||
} else {
|
||||
this.toast.showError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get description(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('description');
|
||||
}
|
||||
|
||||
public get tls(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('tls');
|
||||
}
|
||||
|
||||
public get region(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('region');
|
||||
}
|
||||
|
||||
public get hostAndPort(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('hostAndPort');
|
||||
}
|
||||
|
||||
public get user(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('user');
|
||||
}
|
||||
|
||||
public get password(): AbstractControl | null {
|
||||
return this.firstFormGroup.get('password');
|
||||
}
|
||||
|
||||
public get senderAddress(): AbstractControl | null {
|
||||
return this.secondFormGroup.get('senderAddress');
|
||||
}
|
||||
|
||||
public get senderName(): AbstractControl | null {
|
||||
return this.secondFormGroup.get('senderName');
|
||||
}
|
||||
|
||||
public get replyToAddress(): AbstractControl | null {
|
||||
return this.secondFormGroup.get('replyToAddress');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { CommonModule, TitleCasePipe } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { CardModule } from '../card/card.module';
|
||||
import { CreateLayoutModule } from '../create-layout/create-layout.module';
|
||||
import { InfoSectionModule } from '../info-section/info-section.module';
|
||||
import { ProviderOptionsModule } from '../provider-options/provider-options.module';
|
||||
import { StringListModule } from '../string-list/string-list.module';
|
||||
import { SMTPProvidersRoutingModule } from './smtp-provider-routing.module';
|
||||
import { SMTPProviderComponent } from './smtp-provider.component';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SMTPProviderComponent],
|
||||
imports: [
|
||||
SMTPProvidersRoutingModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CreateLayoutModule,
|
||||
StringListModule,
|
||||
InfoSectionModule,
|
||||
InputModule,
|
||||
MatButtonModule,
|
||||
MatProgressBarModule,
|
||||
MatSelectModule,
|
||||
MatIconModule,
|
||||
MatChipsModule,
|
||||
CardModule,
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
MatStepperModule,
|
||||
TranslateModule,
|
||||
ProviderOptionsModule,
|
||||
HasRolePipeModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
})
|
||||
export default class SMTPProviderModule {}
|
||||
@@ -0,0 +1,71 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin smtp-provider-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.stepper {
|
||||
background: inherit !important;
|
||||
margin: 0 -1.5rem;
|
||||
}
|
||||
|
||||
.smtp-create-actions {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.bck-button {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.create-button {
|
||||
padding: 0.5rem 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.smtp-form-field,
|
||||
.info-section-warn {
|
||||
max-width: 400px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wizard-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.smtp-logo {
|
||||
margin-right: 1rem;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.smtp-icon {
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
|
||||
.icon {
|
||||
font-size: 2.25rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.left,
|
||||
.right {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<cnsl-refresh-table
|
||||
[loading]="loading$ | async"
|
||||
(refreshed)="refreshPage()"
|
||||
[dataSize]="dataSource.data.length"
|
||||
[emitRefreshOnPreviousRoutes]="[
|
||||
'/instance/smtpprovider/aws-ses/create',
|
||||
'/instance/smtpprovider/generic/create',
|
||||
'/instance/smtpprovider/google/create',
|
||||
'/instance/smtpprovider/mailgun/create',
|
||||
'/instance/smtpprovider/mailjet/create',
|
||||
'/instance/smtpprovider/postmark/create',
|
||||
'/instance/smtpprovider/sendgrid/create',
|
||||
'/instance/smtpprovider/mailchimp/create',
|
||||
'/instance/smtpprovider/brevo/create'
|
||||
]"
|
||||
[timestamp]="configsResult?.details?.viewTimestamp"
|
||||
[selection]="selection"
|
||||
[hideRefresh]="true"
|
||||
>
|
||||
<div class="table-wrapper">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="activated">
|
||||
<th class="availability" mat-header-cell *matHeaderCellDef>
|
||||
<span>{{ 'SMTP.LIST.ACTIVATED' | translate }}</span>
|
||||
</th>
|
||||
<td class="pointer availability" mat-cell *matCellDef="let config">
|
||||
<i
|
||||
matTooltip="{{ 'SMTP.LIST.ACTIVATED' | translate }}"
|
||||
*ngIf="isActive(config.state)"
|
||||
class="smtp-available las la-check-circle"
|
||||
data-e2e="active-provider"
|
||||
></i>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.DESCRIPTION' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.description }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="tls">
|
||||
<th class="availability" mat-header-cell *matHeaderCellDef>
|
||||
<span>TLS</span>
|
||||
</th>
|
||||
<td class="pointer availability" mat-cell *matCellDef="let config">
|
||||
<i *ngIf="config.tls" class="las la-lock"></i>
|
||||
<i *ngIf="!config.tls" class="las la-unlock"></i>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="host">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.host }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="senderAddress">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</th>
|
||||
<td class="pointer" mat-cell *matCellDef="let config">
|
||||
<span>{{ config?.senderAddress }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th class="smtp-table-actions" mat-header-cell *matHeaderCellDef></th>
|
||||
<td class="smtp-table-actions" mat-cell *matCellDef="let config">
|
||||
<cnsl-table-actions>
|
||||
<button
|
||||
actions
|
||||
*ngIf="!isActive(config.state)"
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'SMTP.LIST.ACTIVATE' | translate }}"
|
||||
data-e2e="activate-provider-button"
|
||||
(click)="activateSMTPConfig(config.id); $event.stopPropagation()"
|
||||
>
|
||||
<i class="las la-check-circle"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
actions
|
||||
*ngIf="isActive(config.state)"
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'SMTP.LIST.DEACTIVATE' | translate }}"
|
||||
data-e2e="deactivate-provider-button"
|
||||
(click)="deactivateSMTPConfig(config.id); $event.stopPropagation()"
|
||||
>
|
||||
<i class="las la-times-circle"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
actions
|
||||
[disabled]="(['iam.write'] | hasRole | async) === false"
|
||||
mat-icon-button
|
||||
color="warn"
|
||||
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
|
||||
data-e2e="delete-provider-button"
|
||||
(click)="deleteSMTPConfig(config.id, config.senderName); $event.stopPropagation()"
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</cnsl-table-actions>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="highlight" (click)="navigateToProvider(row)" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
|
||||
<i class="las la-exclamation"></i>
|
||||
<span>{{ 'SMTP.LIST.EMPTY' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<cnsl-paginator
|
||||
#paginator
|
||||
class="paginator"
|
||||
[timestamp]="configsResult?.details?.viewTimestamp"
|
||||
[length]="configsResult?.details?.totalResult || 0"
|
||||
[pageSize]="10"
|
||||
[pageSizeOptions]="[10, 20, 50, 100]"
|
||||
(page)="changePage($event)"
|
||||
></cnsl-paginator>
|
||||
</cnsl-refresh-table>
|
||||
@@ -0,0 +1,81 @@
|
||||
@mixin smtp-table-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$accent: map-get($theme, accent);
|
||||
$primary-color: map-get($primary, 500);
|
||||
$warn-color: map-get($warn, 500);
|
||||
$accent-color: map-get($accent, 500);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$back: map-get($background, background);
|
||||
|
||||
.smtp-table-provider-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.smtp-logo {
|
||||
margin-right: 1rem;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.dark {
|
||||
display: if($is-dark-theme, block, none);
|
||||
}
|
||||
|
||||
&.light {
|
||||
display: if($is-dark-theme, none, block);
|
||||
}
|
||||
}
|
||||
|
||||
.smtp-icon {
|
||||
font-size: 1.75rem;
|
||||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.smtp-margin-right {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
td {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
outline: none;
|
||||
|
||||
&.disabled,
|
||||
&.disabled * {
|
||||
opacity: 0.8;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.availability {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.smtp-available {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.smtp-not-available {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.smtp-table-actions {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.la-unlock {
|
||||
color: orangered;
|
||||
}
|
||||
|
||||
.la-lock {
|
||||
color: darkorange;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { IdpTableComponent } from './smtp-table.component';
|
||||
|
||||
describe('UserTableComponent', () => {
|
||||
let component: IdpTableComponent;
|
||||
let fixture: ComponentFixture<IdpTableComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IdpTableComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IdpTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { SMTPConfigState } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||
import { ListQuery } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { LoginPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
|
||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||
import { ListSMTPConfigsRequest, ListSMTPConfigsResponse } from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import { SMTPConfig } from 'src/app/proto/generated/zitadel/settings_pb';
|
||||
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-smtp-table',
|
||||
templateUrl: './smtp-table.component.html',
|
||||
styleUrls: ['./smtp-table.component.scss'],
|
||||
})
|
||||
export class SMTPTableComponent implements OnInit {
|
||||
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
|
||||
public dataSource: MatTableDataSource<SMTPConfig.AsObject> = new MatTableDataSource<SMTPConfig.AsObject>();
|
||||
public selection: SelectionModel<SMTPConfig.AsObject> = new SelectionModel<SMTPConfig.AsObject>(true, []);
|
||||
public configsResult?: ListSMTPConfigsResponse.AsObject;
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
public displayedColumns: string[] = ['activated', 'description', 'tls', 'host', 'senderAddress', 'actions'];
|
||||
@Output() public changedSelection: EventEmitter<Array<SMTPConfig.AsObject>> = new EventEmitter();
|
||||
|
||||
public loginPolicy!: LoginPolicy.AsObject;
|
||||
|
||||
constructor(
|
||||
private adminService: AdminService,
|
||||
public translate: TranslateService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog,
|
||||
private router: Router,
|
||||
) {
|
||||
this.selection.changed.subscribe(() => {
|
||||
this.changedSelection.emit(this.selection.selected);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getData(10, 0);
|
||||
}
|
||||
|
||||
public isActive(state: number) {
|
||||
return state === SMTPConfigState.SMTP_CONFIG_ACTIVE;
|
||||
}
|
||||
|
||||
public isAllSelected(): boolean {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.dataSource.data.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
public masterToggle(): void {
|
||||
this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row));
|
||||
}
|
||||
|
||||
public changePage(event: PageEvent): void {
|
||||
this.getData(event.pageSize, event.pageIndex * event.pageSize);
|
||||
}
|
||||
|
||||
public activateSMTPConfig(id: string): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.CONTINUE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'SMTP.LIST.DIALOG.ACTIVATE_WARN_TITLE',
|
||||
descriptionKey: 'SMTP.LIST.DIALOG.ACTIVATE_WARN_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.adminService
|
||||
.activateSMTPConfig(id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('SMTP.LIST.DIALOG.ACTIVATED', true);
|
||||
this.refreshPage();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public deactivateSMTPConfig(id: string): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.CONTINUE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'SMTP.LIST.DIALOG.DEACTIVATE_WARN_TITLE',
|
||||
descriptionKey: 'SMTP.LIST.DIALOG.DEACTIVATE_WARN_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.adminService
|
||||
.deactivateSMTPConfig(id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('SMTP.LIST.DIALOG.DEACTIVATED', true);
|
||||
this.refreshPage();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public deleteSMTPConfig(id: string, senderName: string): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'SMTP.LIST.DIALOG.DELETE_TITLE',
|
||||
descriptionKey: 'SMTP.LIST.DIALOG.DELETE_DESCRIPTION',
|
||||
confirmationKey: 'SMTP.LIST.DIALOG.SENDER',
|
||||
confirmation: senderName,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.adminService
|
||||
.removeSMTPConfig(id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('SMTP.LIST.DIALOG.DELETED', true);
|
||||
this.refreshPage();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getData(limit: number, offset: number): Promise<void> {
|
||||
this.loadingSubject.next(true);
|
||||
|
||||
const req = new ListSMTPConfigsRequest();
|
||||
const lq = new ListQuery();
|
||||
lq.setOffset(offset);
|
||||
lq.setLimit(limit);
|
||||
req.setQuery(lq);
|
||||
this.adminService
|
||||
.listSMTPConfigs()
|
||||
.then((resp) => {
|
||||
this.configsResult = resp;
|
||||
if (resp.resultList) {
|
||||
this.dataSource.data = resp.resultList;
|
||||
}
|
||||
this.loadingSubject.next(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
this.loadingSubject.next(false);
|
||||
});
|
||||
}
|
||||
|
||||
public refreshPage(): void {
|
||||
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
|
||||
}
|
||||
|
||||
public get createRouterLink(): RouterLink | any {
|
||||
return ['/instance', 'idp', 'create'];
|
||||
}
|
||||
|
||||
public routerLinkForRow(row: SMTPConfig.AsObject): any {
|
||||
return ['/instance', 'smtpprovider', row.id];
|
||||
}
|
||||
|
||||
public get displayedColumnsWithActions(): string[] {
|
||||
return ['actions', ...this.displayedColumns];
|
||||
}
|
||||
|
||||
public navigateToProvider(row: SMTPConfig.AsObject) {
|
||||
this.router.navigate(this.routerLinkForRow(row));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||
import { TruncatePipeModule } from 'src/app/pipes/truncate-pipe/truncate-pipe.module';
|
||||
|
||||
import { PaginatorModule } from '../paginator/paginator.module';
|
||||
import { TableActionsModule } from '../table-actions/table-actions.module';
|
||||
import { SMTPTableComponent } from './smtp-table.component';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SMTPTableComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
TableActionsModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
LocalizedDatePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
MatTableModule,
|
||||
PaginatorModule,
|
||||
RouterModule,
|
||||
RefreshTableModule,
|
||||
HasRoleModule,
|
||||
HasRolePipeModule,
|
||||
TruncatePipeModule,
|
||||
],
|
||||
exports: [SMTPTableComponent],
|
||||
})
|
||||
export class SMTPTableModule {}
|
||||
Reference in New Issue
Block a user