SMTP config

This commit is contained in:
Adina Țeudan 2021-03-29 17:42:09 +03:00
parent 7e21b82805
commit 49f06fba86
19 changed files with 481 additions and 25 deletions

View File

@ -16,6 +16,7 @@ import { LicenseInformationScreenComponent } from './screens/license-information
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { RouterModule } from '@angular/router';
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component';
const routes = [
{ path: '', redirectTo: 'project-templates', pathMatch: 'full' },
@ -124,6 +125,14 @@ const routes = [
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'smtp-config',
component: SmtpConfigScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
];

View File

@ -26,13 +26,16 @@ import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color
import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { AdminDialogService } from './services/admin-dialog-service.service';
import { SmtpConfigScreenComponent } from './screens/smtp-config/smtp-config-screen.component';
import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
const dialogs = [
AddEditRuleSetDialogComponent,
AddEditDictionaryDialogComponent,
AddEditFileAttributeDialogComponent,
ConfirmDeleteFileAttributeDialogComponent,
EditColorDialogComponent
EditColorDialogComponent,
SmtpAuthDialogComponent
];
const screens = [
@ -55,6 +58,7 @@ const components = [
TabsComponent,
ComboChartComponent,
ComboSeriesVerticalComponent,
SmtpConfigScreenComponent,
...dialogs,
...screens
];

View File

@ -44,6 +44,15 @@
*ngIf="root && userPreferenceService.areDevFeaturesEnabled"
></a>
<a
class="breadcrumb"
[routerLink]="'/ui/admin/smtp-config'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="configurations"
*ngIf="root && userPreferenceService.areDevFeaturesEnabled"
></a>
<ng-container *ngIf="appStateService.activeRuleSet">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a

View File

@ -20,11 +20,11 @@
/>
</div>
<div class="red-input-group ignore-label-styles">
<div class="red-input-group">
<mat-slide-toggle formControlName="readonly" color="primary">{{ 'add-edit-file-attribute.form.read-only' | translate }}</mat-slide-toggle>
</div>
<div class="red-input-group ignore-label-styles">
<div class="red-input-group">
<mat-slide-toggle formControlName="visible" color="primary">{{ 'add-edit-file-attribute.form.visible' | translate }}</mat-slide-toggle>
</div>
</div>

View File

@ -0,0 +1,26 @@
<section class="dialog">
<div class="dialog-header heading-l" translate="smtp-auth-config.title"></div>
<form (submit)="save()" [formGroup]="authForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="smtp-auth-config.form.username"></label>
<input formControlName="user" name="user" type="text" placeholder="{{ 'smtp-auth-config.form.username-placeholder' | translate }}" />
</div>
<div class="red-input-group required w-300">
<label translate="smtp-auth-config.form.password"></label>
<input formControlName="password" name="password" type="password" />
</div>
</div>
<div class="dialog-actions">
<button [disabled]="authForm.invalid" color="primary" mat-flat-button type="submit">
{{ 'smtp-auth-config.actions.save' | translate }}
</button>
<div class="all-caps-label cancel" mat-dialog-close translate="smtp-auth-config.actions.cancel"></div>
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
</section>

View File

@ -0,0 +1,32 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from '../../../../services/user.service';
import { SMTPConfigurationModel } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-smtp-auth-dialog',
templateUrl: './smtp-auth-dialog.component.html',
styleUrls: ['./smtp-auth-dialog.component.scss']
})
export class SmtpAuthDialogComponent implements OnInit {
public authForm: FormGroup;
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService,
public dialogRef: MatDialogRef<SmtpAuthDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: SMTPConfigurationModel
) {
this.authForm = this._formBuilder.group({
user: [data?.user || this._userService.user.email, [Validators.required]],
password: [data?.password, Validators.required]
});
}
ngOnInit(): void {}
public save() {
this.dialogRef.close(this.authForm.getRawValue());
}
}

View File

@ -0,0 +1,139 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs [root]="true" class="flex-1"></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-circle-button
*ngIf="permissionsService.isUser()"
[routerLink]="['/ui/projects/']"
class="ml-6"
icon="red:close"
tooltip="common.close"
tooltipPosition="before"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner">
<div class="left-container">
<div class="overlay-shadow"></div>
<div class="dialog">
<div class="dialog-header">
<div class="heading-l" translate="smtp-config-screen.title"></div>
<div translate="smtp-config-screen.subtitle"></div>
</div>
<form [formGroup]="configForm" (submit)="save()">
<div class="dialog-content">
<div class="dialog-content-left">
<div class="red-input-group required">
<label translate="smtp-config-screen.form.host"></label>
<input
formControlName="host"
name="host"
type="text"
placeholder="{{ 'smtp-config-screen.form.host-placeholder' | translate }}"
/>
</div>
<div class="red-input-group w-100">
<label translate="smtp-config-screen.form.port"></label>
<input
formControlName="port"
name="port"
type="number"
placeholder="{{ 'smtp-config-screen.form.port-placeholder' | translate }}"
/>
</div>
<div class="red-input-group required">
<label translate="smtp-config-screen.form.from"></label>
<input
formControlName="from"
name="from"
type="email"
placeholder="{{ 'smtp-config-screen.form.from-placeholder' | translate }}"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.from-display-name"></label>
<input
formControlName="fromDisplayName"
name="fromDisplayName"
type="text"
placeholder="{{ 'smtp-config-screen.form.from-display-name-placeholder' | translate }}"
/>
<span class="hint" translate="smtp-config-screen.form.from-display-name-hint"></span>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.reply-to"></label>
<input
formControlName="replyTo"
name="replyTo"
type="text"
placeholder="{{ 'smtp-config-screen.form.reply-to-placeholder' | translate }}"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.reply-to-display-name"></label>
<input
formControlName="replyToDisplayName"
name="replyToDisplayName"
type="text"
placeholder="{{ 'smtp-config-screen.form.reply-to-display-name-placeholder' | translate }}"
/>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.envelope-from"></label>
<input
formControlName="envelopeFrom"
name="envelopeFrom"
type="text"
placeholder="{{ 'smtp-config-screen.form.envelope-from-placeholder' | translate }}"
/>
<span class="hint" translate="smtp-config-screen.form.envelope-from-hint"></span>
</div>
</div>
<div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.ssl"></label>
<mat-slide-toggle formControlName="ssl" color="primary"></mat-slide-toggle>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.starttls"></label>
<mat-slide-toggle formControlName="starttls" color="primary"></mat-slide-toggle>
</div>
<div class="red-input-group">
<label translate="smtp-config-screen.form.auth"></label>
<mat-slide-toggle formControlName="auth" color="primary"></mat-slide-toggle>
</div>
<div
class="link-action"
*ngIf="configForm.get('auth').value"
(click)="openAuthConfigDialog(true)"
translate="smtp-config-screen.form.change-credentials"
></div>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="configForm.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'smtp-config-screen.actions.save' | translate }}
</button>
<redaction-icon-button
[disabled]="configForm.invalid"
text="smtp-config-screen.actions.test-connection"
(action)="testConnection()"
type="show-bg"
></redaction-icon-button>
</div>
</form>
</div>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>

View File

@ -0,0 +1,44 @@
@import '../../../../../assets/styles/red-variables';
@import '../../../../../assets/styles/red-mixins';
.left-container {
width: 100vw;
background-color: $grey-2;
display: flex;
justify-content: center;
@include scroll-bar;
overflow: auto;
.dialog {
border-radius: 8px;
margin-top: 40px;
margin-bottom: 70px;
background-color: $white;
max-width: 650px;
height: fit-content;
box-shadow: 0 1px 5px 0 rgba(40, 50, 65, 0.19);
position: unset;
.heading-l {
margin-bottom: 16px;
}
.dialog-content {
display: flex;
.dialog-content-left {
min-width: 300px;
margin-right: 64px;
}
.link-action {
margin-top: 8px;
}
}
}
}
.w-100 {
min-width: 100px;
width: 100px;
}

View File

@ -0,0 +1,107 @@
import { Component, OnInit } from '@angular/core';
import { PermissionsService } from '../../../../services/permissions.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog-service.service';
import { SmtpConfigurationControllerService, SMTPConfigurationModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../../services/notification.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'redaction-smtp-config-screen',
templateUrl: './smtp-config-screen.component.html',
styleUrls: ['./smtp-config-screen.component.scss']
})
export class SmtpConfigScreenComponent implements OnInit {
public viewReady = false;
public configForm: FormGroup;
private _initialValue: SMTPConfigurationModel;
constructor(
public readonly permissionsService: PermissionsService,
private readonly _smtpConfigService: SmtpConfigurationControllerService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
private readonly _notificationService: NotificationService,
private readonly _translateService: TranslateService
) {
this.configForm = this._formBuilder.group({
host: [undefined, Validators.required],
port: [25],
from: [undefined, [Validators.required, Validators.email]],
fromDisplayName: [undefined],
replyTo: [undefined],
replyToDisplayName: [undefined],
envelopeFrom: [undefined],
ssl: [false],
starttls: [false],
auth: [false],
user: [undefined],
password: [undefined]
});
this.configForm.controls.auth.valueChanges.subscribe((auth) => {
if (auth) {
this.openAuthConfigDialog();
}
});
}
async ngOnInit() {
await this._loadData();
}
private async _loadData() {
try {
this._initialValue = await this._smtpConfigService.getCurrentSMTPConfiguration().toPromise();
this.configForm.patchValue(this._initialValue, { emitEvent: false });
} catch (e) {
} finally {
this.viewReady = true;
}
}
public get changed(): boolean {
if (!this._initialValue) return true;
for (const key of Object.keys(this.configForm.getRawValue())) {
if (this._initialValue[key] !== this.configForm.get(key).value) {
return true;
}
}
return false;
}
public async save() {
this.viewReady = false;
await this._smtpConfigService.updateSMTPConfiguration(this.configForm.getRawValue()).toPromise();
this._initialValue = this.configForm.getRawValue();
this.viewReady = true;
}
public openAuthConfigDialog(skipDisableOnCancel?: boolean) {
this._dialogService.openSMTPAuthConfigDialog(this.configForm.getRawValue(), (authConfig) => {
if (authConfig) {
this.configForm.patchValue(authConfig);
} else if (!skipDisableOnCancel) {
this.configForm.patchValue({ auth: false }, { emitEvent: false });
}
});
}
public async testConnection() {
this.viewReady = false;
try {
const res = await this._smtpConfigService.testSMTPConfiguration(this.configForm.getRawValue()).toPromise();
this._notificationService.showToastNotification(
this._translateService.instant('smtp-config-screen.test.success'),
undefined,
NotificationType.SUCCESS
);
} catch (e) {
this._notificationService.showToastNotification(this._translateService.instant('smtp-config-screen.test.error'), undefined, NotificationType.ERROR);
} finally {
this.viewReady = true;
}
}
}

View File

@ -5,22 +5,22 @@ import {
DictionaryControllerService,
FileAttributeConfig,
FileManagementControllerService,
FileStatus,
ManualRedactionControllerService,
RuleSetControllerService,
RuleSetModel,
SMTPConfigurationModel,
TypeValue
} from '@redaction/red-ui-http';
import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEditDictionaryDialogComponent } from '../dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
import { AddEditRuleSetDialogComponent } from '../dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
import { NotificationService, NotificationType } from '../../../services/notification.service';
import { ConfirmationDialogComponent, ConfirmationDialogInput } from '../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { ProjectWrapper } from '../../../state/model/project.wrapper';
import { NotificationService } from '../../../services/notification.service';
import { ConfirmationDialogComponent } from '../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { AppStateService } from '../../../state/app-state.service';
import { ConfirmDeleteFileAttributeDialogComponent } from '../dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component';
import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
const dialogConfig = {
width: '662px',
@ -152,4 +152,20 @@ export class AdminDialogService {
return ref;
}
public openSMTPAuthConfigDialog(smtpConfig: SMTPConfigurationModel, cb?: Function): MatDialogRef<SmtpAuthDialogComponent> {
const ref = this._dialog.open(SmtpAuthDialogComponent, {
...dialogConfig,
data: smtpConfig,
autoFocus: true
});
ref.afterClosed().subscribe((result) => {
if (cb) {
cb(result);
}
});
return ref;
}
}

View File

@ -31,7 +31,7 @@ export class NotificationService {
message: string,
title?: string,
notificationType: NotificationType = NotificationType.INFO,
options?: Partial<IndividualConfig> & { actions: Action[] }
options?: Partial<IndividualConfig> & { actions?: Action[] }
): ActiveToast<any> {
switch (notificationType) {
case NotificationType.ERROR:

View File

@ -14,6 +14,10 @@ export class UserWrapper {
: this._currentUser.username;
}
get email() {
return this._currentUser.email;
}
get isManager() {
return this.roles.indexOf('RED_MANAGER') >= 0;
}

View File

@ -878,6 +878,52 @@
"previous": "Prev",
"next": "Next"
},
"configurations": "Configurations",
"smtp-config-screen": {
"title": "Configure SMTP Account",
"subtitle": "SMTP (Simple Mail Transfer Protocol) enables you to send your emails through the specified server settings.",
"actions": {
"save": "Save Configurations",
"test-connection": "Test Connection"
},
"form": {
"host": "Host",
"host-placeholder": "SMTP Host",
"port": "Port",
"from": "From",
"from-placeholder": "Sender Email Address",
"from-display-name": "Name for Sender",
"from-display-name-placeholder": "Display Name for Sender Email Address",
"from-display-name-hint": "Info text regarding the name for sender.",
"reply-to": "Reply To",
"reply-to-placeholder": "Reply To Email Address",
"reply-to-display-name": "Name for Reply To",
"reply-to-display-name-placeholder": "Display Name for Reply To Email Address",
"envelope-from": "Envelope From",
"envelope-from-placeholder": "Sender Envelope Email Address",
"envelope-from-hint": "Info text regarding envelope from field.",
"ssl": "Enable SSL",
"starttls": "Enable StartTLS",
"auth": "Enable Authentication",
"change-credentials": "Change Credentials"
},
"test": {
"success": "Test email was sent successfully!",
"error": "Test email could not be sent! Please revise the email address."
}
},
"smtp-auth-config": {
"title": "Enable Authentication",
"form": {
"username": "Username",
"username-placeholder": "Login Username",
"password": "Password"
},
"actions": {
"save": "Save Credentials",
"cancel": "Cancel"
}
},
"default-colors": "Default Colors",
"default-colors-screen": {
"table-header": {

View File

@ -3,7 +3,7 @@
form {
.red-input-group:not(first-of-type) {
margin-top: 24px;
margin-top: 14px;
}
}
@ -117,6 +117,14 @@ form {
color: $grey-1;
opacity: 0.7;
}
&.ng-invalid.ng-touched {
border-color: rgba($red-1, 0.3);
&:focus {
border-color: $red-1;
}
}
}
.hex-color-input {
@ -146,7 +154,7 @@ form {
}
}
&:not(.ignore-label-styles) label {
label:not(.mat-slide-toggle-label) {
opacity: 0.7;
font-size: 11px;
letter-spacing: 0;

View File

@ -44,3 +44,7 @@
@mixin inset-shadow {
box-shadow: inset 0 4px 3px -2px $grey-4;
}
@mixin drop-shadow {
box-shadow: 0 4px 3px 2px $grey-4;
}

View File

@ -81,6 +81,14 @@ body {
transition: height ease-in-out 0.2s;
.left-container {
.overlay-shadow {
@include inset-shadow;
position: fixed;
width: 100%;
height: 4px;
z-index: 1;
}
overflow: hidden;
transition: width ease-in-out 0.2s, min-width ease-in-out 0.2s;
position: relative;
@ -89,13 +97,6 @@ body {
width: calc(100vw - 60px) !important;
}
.overlay-shadow {
@include inset-shadow;
position: absolute;
width: 100%;
height: 4px;
}
.empty-state {
display: flex;
flex-direction: column;

View File

@ -77,10 +77,15 @@ pre {
line-height: 14px;
}
.medium-label {
.link-action {
font-size: 11px;
font-weight: 500;
line-height: 14px;
text-decoration: underline;
cursor: pointer;
&:hover {
text-decoration: none;
}
}
.large-label {

View File

@ -1,5 +1,7 @@
@import 'red-variables';
$toast-width: 400px;
.toast-container .ngx-toastr {
padding: 11px 16px;
border-radius: 8px;
@ -7,7 +9,7 @@
font-family: Inter, sans-serif;
font-size: 13px;
line-height: 18px;
width: 400px;
width: $toast-width;
display: flex;
justify-content: space-between;
align-items: center;
@ -40,16 +42,16 @@
}
}
.toast-top-right {
top: 128px;
right: calc(50% - #{$toast-width} / 2);
}
.toast-file-preview {
top: 160px;
right: 405px;
}
.toast-top-right,
.toast-top-left {
top: 100px;
}
.toast-close-button {
position: initial;
opacity: 1;