diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 38a808796..07bd55eb1 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -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] + } } ]; diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index 2ee36ea0a..ddb76c6fd 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -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 ]; diff --git a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html b/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html index 65f59c17a..0bb7f56fd 100644 --- a/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html +++ b/apps/red-ui/src/app/modules/admin/components/breadcrumbs/admin-breadcrumbs.component.html @@ -44,6 +44,15 @@ *ngIf="root && userPreferenceService.areDevFeaturesEnabled" > + + - + {{ 'add-edit-file-attribute.form.read-only' | translate }} - + {{ 'add-edit-file-attribute.form.visible' | translate }} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html new file mode 100644 index 000000000..92566a824 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + {{ 'smtp-auth-config.actions.save' | translate }} + + + + + + + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts new file mode 100644 index 000000000..f6a2c4e95 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/smtp-auth-dialog/smtp-auth-dialog.component.ts @@ -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, + @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()); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.html new file mode 100644 index 000000000..2d5775a1f --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'smtp-config-screen.actions.save' | translate }} + + + + + + + + + + + diff --git a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.scss new file mode 100644 index 000000000..32d693e2d --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.scss @@ -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; +} diff --git a/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts new file mode 100644 index 000000000..e0405f044 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/smtp-config/smtp-config-screen.component.ts @@ -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; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/services/admin-dialog-service.service.ts b/apps/red-ui/src/app/modules/admin/services/admin-dialog-service.service.ts index 863217838..d43997a39 100644 --- a/apps/red-ui/src/app/modules/admin/services/admin-dialog-service.service.ts +++ b/apps/red-ui/src/app/modules/admin/services/admin-dialog-service.service.ts @@ -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 { + const ref = this._dialog.open(SmtpAuthDialogComponent, { + ...dialogConfig, + data: smtpConfig, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (cb) { + cb(result); + } + }); + + return ref; + } } diff --git a/apps/red-ui/src/app/services/notification.service.ts b/apps/red-ui/src/app/services/notification.service.ts index 657215df9..c4486493b 100644 --- a/apps/red-ui/src/app/services/notification.service.ts +++ b/apps/red-ui/src/app/services/notification.service.ts @@ -31,7 +31,7 @@ export class NotificationService { message: string, title?: string, notificationType: NotificationType = NotificationType.INFO, - options?: Partial & { actions: Action[] } + options?: Partial & { actions?: Action[] } ): ActiveToast { switch (notificationType) { case NotificationType.ERROR: diff --git a/apps/red-ui/src/app/services/user.service.ts b/apps/red-ui/src/app/services/user.service.ts index 0042f9345..eeb7c93e2 100644 --- a/apps/red-ui/src/app/services/user.service.ts +++ b/apps/red-ui/src/app/services/user.service.ts @@ -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; } diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 734884720..9a143acbd 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -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": { diff --git a/apps/red-ui/src/assets/styles/red-input.scss b/apps/red-ui/src/assets/styles/red-input.scss index 8c82292de..313f74129 100644 --- a/apps/red-ui/src/assets/styles/red-input.scss +++ b/apps/red-ui/src/assets/styles/red-input.scss @@ -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; diff --git a/apps/red-ui/src/assets/styles/red-mixins.scss b/apps/red-ui/src/assets/styles/red-mixins.scss index b9463a46c..3928ec4c5 100644 --- a/apps/red-ui/src/assets/styles/red-mixins.scss +++ b/apps/red-ui/src/assets/styles/red-mixins.scss @@ -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; +} diff --git a/apps/red-ui/src/assets/styles/red-page-layout.scss b/apps/red-ui/src/assets/styles/red-page-layout.scss index d48a37437..030b1072c 100644 --- a/apps/red-ui/src/assets/styles/red-page-layout.scss +++ b/apps/red-ui/src/assets/styles/red-page-layout.scss @@ -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; diff --git a/apps/red-ui/src/assets/styles/red-text-styles.scss b/apps/red-ui/src/assets/styles/red-text-styles.scss index 8cf7aed66..a2c082e8d 100644 --- a/apps/red-ui/src/assets/styles/red-text-styles.scss +++ b/apps/red-ui/src/assets/styles/red-text-styles.scss @@ -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 { diff --git a/apps/red-ui/src/assets/styles/red-toasts.scss b/apps/red-ui/src/assets/styles/red-toasts.scss index b4f5d9e0e..a98a82c49 100644 --- a/apps/red-ui/src/assets/styles/red-toasts.scss +++ b/apps/red-ui/src/assets/styles/red-toasts.scss @@ -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;