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 ddb76c6fd..62f42167a 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -28,6 +28,8 @@ 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'; +import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-edit-user-dialog.component'; +import { ConfirmDeleteUserDialogComponent } from './dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component'; const dialogs = [ AddEditRuleSetDialogComponent, @@ -35,7 +37,9 @@ const dialogs = [ AddEditFileAttributeDialogComponent, ConfirmDeleteFileAttributeDialogComponent, EditColorDialogComponent, - SmtpAuthDialogComponent + SmtpAuthDialogComponent, + AddEditUserDialogComponent, + ConfirmDeleteUserDialogComponent ]; const screens = [ @@ -49,7 +53,8 @@ const screens = [ FileAttributesListingScreenComponent, LicenseInformationScreenComponent, UserListingScreenComponent, - WatermarkScreenComponent + WatermarkScreenComponent, + SmtpConfigScreenComponent ]; const components = [ @@ -58,7 +63,6 @@ const components = [ TabsComponent, ComboChartComponent, ComboSeriesVerticalComponent, - SmtpConfigScreenComponent, ...dialogs, ...screens ]; diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html new file mode 100644 index 000000000..f06139aa1 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.html @@ -0,0 +1,56 @@ + + + {{ (user ? 'add-edit-user.title.edit' : 'add-edit-user.title.new') | translate }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'roles.' + role | translate }} + + + + + + + + {{ (user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate }} + + + + + + + + + + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss new file mode 100644 index 000000000..65fd1e545 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.scss @@ -0,0 +1,7 @@ +.roles-wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-row-gap: 8px; + margin-top: 8px; + width: 300px; +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts new file mode 100644 index 000000000..4a18b6705 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/add-edit-user-dialog/add-edit-user-dialog.component.ts @@ -0,0 +1,124 @@ +import { Component, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { User, UserControllerService } from '@redaction/red-ui-http'; +import { UserService } from '../../../../services/user.service'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { AdminDialogService } from '../../services/admin-dialog-service.service'; + +@Component({ + selector: 'redaction-add-edit-user-dialog', + templateUrl: './add-edit-user-dialog.component.html', + styleUrls: ['./add-edit-user-dialog.component.scss'] +}) +export class AddEditUserDialogComponent { + public userForm: FormGroup; + public ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN']; + + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _userControllerService: UserControllerService, + private readonly _userService: UserService, + private readonly _dialogService: AdminDialogService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public user: User + ) { + this.userForm = this._formBuilder.group({ + firstName: [this.user?.firstName, Validators.required], + lastName: [this.user?.lastName, Validators.required], + email: [{ value: this.user?.email, disabled: !!user }, [Validators.required, Validators.email]], + // password: [this.user?.password, Validators.required], + admin: [user && this._userService.isAdmin(user)], + roles: [user?.roles || []] + }); + } + + public get changed(): boolean { + if (!this.user) return true; + + for (const key of Object.keys(this.userForm.getRawValue())) { + if (this.user[key] !== this.userForm.get(key).value) { + return true; + } + } + + return false; + } + + private async _updateProfile() { + const profileKeys = ['firstName', 'lastName']; + let profileChanged = false; + for (const key of profileKeys) { + if (this.userForm.get(key).value !== this.user[key]) { + profileChanged = true; + } + } + if (!profileChanged) { + return; + } + await this._userControllerService.updateProfile(this.userForm.getRawValue()).toPromise(); + } + + private async _updateRoles() { + const newAdminValue = this.userForm.get('admin').value; + if (newAdminValue === this._userService.isAdmin(this.user)) { + return; + } + let roles = [...this.user.roles]; + if (newAdminValue) { + roles.push('RED_ADMIN'); + } else { + roles = roles.filter((role) => role !== 'RED_ADMIN'); + } + await this._userControllerService.addRoleToUsers(roles, this.user.userId).toPromise(); + } + + private async _update() { + await this._updateProfile(); + await this._updateRoles(); + } + + private async _create() { + const roles = ['RED_USER']; + if (this.userForm.get('admin').value) { + roles.push('RED_ADMIN'); + } + await this._userControllerService.createUser({ ...this.userForm.getRawValue(), roles: [] }, 'body').toPromise(); + } + + public async save() { + if (this.user) { + await this._update(); + } else { + await this._create(); + } + this.dialogRef.close(true); + } + + public async delete() { + this._dialogService.openConfirmDeleteUserDialog(this.user); + // this.dialogRef.close(); + } + + public changeRoleValue(role: string, { checked }: MatCheckboxChange) { + const roles = [...this.activeRoles]; + if (checked) { + roles.push(role); + } else { + roles.splice(roles.indexOf(role), 1); + } + this.userForm.patchValue({ roles }); + } + + public get activeRoles(): string[] { + return this.userForm.get('roles').value; + } + + public getChecked(role: string) { + return this.activeRoles.indexOf(role) !== -1; + } + + public getDisabled(role: string) { + return this.getChecked(role) && role === 'RED_USER' && this.getChecked('RED_ADMIN'); + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss index 910d55c69..ddf782c9b 100644 --- a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss @@ -8,6 +8,10 @@ margin-bottom: 24px; } -mat-checkbox:not(:last-of-type) { - margin-bottom: 6px; +mat-checkbox { + width: 100%; + + &:not(:last-of-type) { + margin-bottom: 6px; + } } diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.html b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.html new file mode 100644 index 000000000..bd83c23e7 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.html @@ -0,0 +1,19 @@ + + + + + + + + {{ 'confirm-delete-user.checkbox-' + (idx + 1) | translate: { count: 20 } }} + + + + + + {{ 'confirm-delete-user.delete' | translate }} + + + + + diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.scss b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.scss new file mode 100644 index 000000000..ddf782c9b --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.scss @@ -0,0 +1,17 @@ +@import '../../../../../assets/styles/red-variables'; + +.dialog-header { + color: $primary; +} + +.heading { + margin-bottom: 24px; +} + +mat-checkbox { + width: 100%; + + &:not(:last-of-type) { + margin-bottom: 6px; + } +} diff --git a/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.ts b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.ts new file mode 100644 index 000000000..8b288077f --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { User, UserControllerService } from '@redaction/red-ui-http'; + +@Component({ + selector: 'redaction-confirm-delete-user-dialog', + templateUrl: './confirm-delete-user-dialog.component.html', + styleUrls: ['./confirm-delete-user-dialog.component.scss'] +}) +export class ConfirmDeleteUserDialogComponent implements OnInit { + public checkboxes = [{ value: false }, { value: false }]; + public showToast = false; + + constructor( + @Inject(MAT_DIALOG_DATA) public user: User, + private readonly _userControllerService: UserControllerService, + public dialogRef: MatDialogRef + ) {} + + ngOnInit(): void {} + + async deleteUser() { + if (this.valid) { + await this._userControllerService.deleteUser(this.user.userId).toPromise(); + this.dialogRef.close(true); + } else { + this.showToast = true; + } + } + + public get valid() { + return this.checkboxes[0].value && this.checkboxes[1].value; + } + + public cancel() { + this.dialogRef.close(); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html index 3d60e39bf..a5d86d764 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.html @@ -2,9 +2,15 @@ - - + + + + + + + @@ -36,9 +47,32 @@ - + + + {{ user.email || '-' }} + + + + {{ getDisplayRoles(user) }} + + + + + + + - {{ user.email || '-' }} @@ -47,3 +81,5 @@ + + diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.scss index 94fa2e029..dc3ae3d31 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.scss @@ -3,18 +3,22 @@ cdk-virtual-scroll-viewport { ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 1fr 1fr 11px; + grid-template-columns: 2fr 1fr 1fr 1fr auto 11px; .table-item { - > div { + > div:not(.scrollbar-placeholder) { padding: 0 24px; + + &.center { + align-items: center; + } } } } &.has-scrollbar:hover { ::ng-deep.cdk-virtual-scroll-content-wrapper { - grid-template-columns: 1fr 1fr; + grid-template-columns: 2fr 1fr 1fr 1fr auto; } } } @@ -27,6 +31,13 @@ } .page-header .actions { - width: calc(353px - 24px); justify-content: flex-end; + + redaction-search-input:not(:last-child) { + margin-right: 16px; + } + + redaction-icon-button:not(:last-child) { + margin-right: 6px; + } } diff --git a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts index c9980cc1b..1bb07f820 100644 --- a/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/user-listing/user-listing-screen.component.ts @@ -1,33 +1,75 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { PermissionsService } from '../../../../services/permissions.service'; import { UserService } from '../../../../services/user.service'; -import { User } from '@redaction/red-ui-http'; +import { User, UserControllerService } from '@redaction/red-ui-http'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '../../../../utils/debounce'; +import { AdminDialogService } from '../../services/admin-dialog-service.service'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'redaction-user-listing-screen', templateUrl: './user-listing-screen.component.html', styleUrls: ['./user-listing-screen.component.scss'] }) -export class UserListingScreenComponent { +export class UserListingScreenComponent implements OnInit { + public viewReady = false; + public users: User[]; - public displayedUsers: User[]; + public displayedUsers: User[] = []; public searchForm: FormGroup; - constructor(public readonly permissionsService: PermissionsService, private readonly userService: UserService, private readonly _formBuilder: FormBuilder) { - this.users = this.userService.allUsers; - this.displayedUsers = [...this.users]; - + constructor( + public readonly permissionsService: PermissionsService, + public readonly userService: UserService, + private readonly _formBuilder: FormBuilder, + private readonly _translateService: TranslateService, + private readonly _adminDialogService: AdminDialogService, + private readonly _userControllerService: UserControllerService + ) { this.searchForm = this._formBuilder.group({ query: [''] }); - this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + this.searchForm.valueChanges.subscribe(() => this._executeSearch()); + } + + public async ngOnInit() { + await this._loadData(); } @debounce(200) - private _executeSearch(value: { query: string }) { - this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.query.toLowerCase())); + private _executeSearch() { + const value = this.searchForm.get('query').value; + this.displayedUsers = this.users.filter((user) => this.userService.getName(user).toLowerCase().includes(value.toLowerCase())); + } + + public openAddEditUserDialog($event: MouseEvent, user?: User) { + $event.stopPropagation(); + this._adminDialogService.openAddEditUserDialog(user, async () => { + await this._loadData(); + }); + } + + public openDeleteUserDialog($event: MouseEvent, user: User) { + $event.stopPropagation(); + } + + private async _loadData() { + this.viewReady = false; + this.users = (await this._userControllerService.getAllUsers({}).toPromise()).users; + this._executeSearch(); + this.viewReady = true; + } + + public getDisplayRoles(user: User) { + return user.roles.map((role) => this._translateService.instant('roles.' + role)).join(', ') || this._translateService.instant('roles.NO_ROLE'); + } + + public async changeActive(user: User) { + this.viewReady = false; + user.roles = this.userService.isActive(user) ? [] : ['RED_USER']; + await this._userControllerService.addRoleToUsers(user.roles, user.userId).toPromise(); + await this._loadData(); } } 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 d43997a39..850320795 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 @@ -9,7 +9,8 @@ import { RuleSetControllerService, RuleSetModel, SMTPConfigurationModel, - TypeValue + TypeValue, + User } 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'; @@ -21,6 +22,8 @@ import { ConfirmDeleteFileAttributeDialogComponent } from '../dialogs/confirm-de 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'; +import { AddEditUserDialogComponent } from '../dialogs/add-edit-user-dialog/add-edit-user-dialog.component'; +import { ConfirmDeleteUserDialogComponent } from '../dialogs/confirm-delete-user-dialog/confirm-delete-user-dialog.component'; const dialogConfig = { width: '662px', @@ -168,4 +171,36 @@ export class AdminDialogService { return ref; } + + public openAddEditUserDialog(user?: User, cb?: Function): MatDialogRef { + const ref = this._dialog.open(AddEditUserDialogComponent, { + ...dialogConfig, + data: user, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (result && cb) { + cb(result); + } + }); + + return ref; + } + + public openConfirmDeleteUserDialog(user?: User, cb?: Function): MatDialogRef { + const ref = this._dialog.open(ConfirmDeleteUserDialogComponent, { + ...dialogConfig, + data: user, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (result && cb) { + cb(result); + } + }); + + return ref; + } } diff --git a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.html b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.html index 8bac6c9dc..21a46b5db 100644 --- a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.html +++ b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.html @@ -2,7 +2,7 @@ {{ initials }} - + {{ displayName || ('initials-avatar.unassigned' | translate) }} diff --git a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.scss b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.scss index f7cc321aa..b4ab89ac3 100644 --- a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.scss +++ b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.scss @@ -7,6 +7,10 @@ .username { margin-left: 6px; + + &.disabled { + opacity: 0.7; + } } } diff --git a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.ts b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.ts index eed1e9c6b..58bbfad9f 100644 --- a/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/initials-avatar/initials-avatar.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { UserService } from '../../../../services/user.service'; import { User } from '@redaction/red-ui-http'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'redaction-initials-avatar', @@ -9,11 +10,12 @@ import { User } from '@redaction/red-ui-http'; }) export class InitialsAvatarComponent implements OnInit, OnChanges { @Input() public userId: string; + @Input() public user: User; @Input() public color = 'lightgray'; @Input() public size: 'small' | 'large' = 'small'; @Input() public withName = false; - - public _user: User; + @Input() public showYou = false; + @Input() public alwaysShowName = false; public displayName: string; @@ -21,17 +23,23 @@ export class InitialsAvatarComponent implements OnInit, OnChanges { public colorClass: string; - constructor(private readonly _userService: UserService) {} + constructor(private readonly _userService: UserService, private readonly _translateService: TranslateService) {} ngOnInit(): void {} ngOnChanges(): void { const isSystemUser = this.userId && this.userId.toLowerCase() === 'system'; if (!isSystemUser) { - this._user = this._userService.getUserById(this.userId); - this.displayName = this._userService.getName(this._user); + if (this.user) { + this.userId = this.user.userId; + } else { + this.user = this._userService.getUserById(this.userId); + } + this.displayName = + this._userService.getName(this.user) + + (this.showYou && this._userService.userId === this.userId ? ` (${this._translateService.instant('initials-avatar.you')})` : ''); this.initials = this._getInitials(); - this.colorClass = this._colorClass(); + this.colorClass = this._colorClass; } else { this.displayName = 'System'; this.initials = 'SY'; @@ -40,11 +48,11 @@ export class InitialsAvatarComponent implements OnInit, OnChanges { } private _getInitials() { - if (!this._user) { + if (!this.user) { return '?'; } else { return this._userService - .getName(this._user) + .getName(this.user) .split(' ') .filter((value) => value !== ' ') .filter((value, idx) => idx < 2) @@ -53,10 +61,13 @@ export class InitialsAvatarComponent implements OnInit, OnChanges { } } - private _colorClass() { + private get _colorClass() { if (this._userService.userId === this.userId) { return 'red-white'; } + if (this.disabled) { + return 'inactive'; + } if (this.color.includes('-')) { return this.color; } @@ -64,6 +75,10 @@ export class InitialsAvatarComponent implements OnInit, OnChanges { } public get hasBorder(): boolean { - return !!this._user && this._userService.userId !== this.userId && this._userService.isManager(this._user); + return !!this.user && this._userService.userId !== this.userId && this._userService.isManager(this.user); + } + + public get disabled(): boolean { + return !this._userService.isActive(this.user); } } diff --git a/apps/red-ui/src/app/services/notification.service.ts b/apps/red-ui/src/app/services/notification.service.ts index c4486493b..71e6453f8 100644 --- a/apps/red-ui/src/app/services/notification.service.ts +++ b/apps/red-ui/src/app/services/notification.service.ts @@ -10,7 +10,7 @@ export enum NotificationType { INFO = 'INFO' } -export class Action { +export class ToastAction { title: string; action?: Function; } @@ -31,7 +31,7 @@ export class NotificationService { message: string, title?: string, notificationType: NotificationType = NotificationType.INFO, - options?: Partial & { actions?: Action[] } + options?: Partial & { actions?: ToastAction[] } ): 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 eeb7c93e2..fa07b9c20 100644 --- a/apps/red-ui/src/app/services/user.service.ts +++ b/apps/red-ui/src/app/services/user.service.ts @@ -114,6 +114,20 @@ export class UserService { return user.roles?.indexOf('RED_USER') >= 0; } + isAdmin(user?: User): boolean { + if (!user) { + user = this.user; + } + return user.roles?.indexOf('RED_ADMIN') >= 0; + } + + isActive(user?: User): boolean { + if (!user) { + user = this.user; + } + return user.roles?.length > 0; + } + private _hasAnyRedRole(u: User) { return u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0 || u.roles.indexOf('RED_ADMIN') >= 0; } diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 55b2a8f4f..60d4315a2 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -23,7 +23,6 @@ import { tap } from 'rxjs/operators'; import { humanize } from '../utils/functions'; import { FileStatusWrapper } from '../models/file/file-status.wrapper'; import { ProjectWrapper } from './model/project.wrapper'; -import { saveAs } from 'file-saver'; export interface AppState { projects: ProjectWrapper[]; diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index d196e3a41..dc6e08925 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -417,7 +417,8 @@ "show": "Show" }, "initials-avatar": { - "unassigned": "Unassigned" + "unassigned": "Unassigned", + "you": "You" }, "assign-file-owner": { "dialog": { @@ -756,6 +757,14 @@ "checkbox-1": "All documents it is used on will be impacted", "checkbox-2": "All inputted details on the documents will be lost" }, + "confirm-delete-user": { + "title": "Delete User from Workspace", + "warning": "Warning: this cannot be undone!", + "checkbox-1": "{{count}} projects will be impacted", + "checkbox-2": "{{count}} documents waiting review will be impacted", + "delete": "Delete User", + "cancel": "Keep User" + }, "document-info": { "title": "Introduce File Attributes", "save": "Save Document Info", @@ -767,9 +776,34 @@ }, "table-col-names": { "name": "Name", - "email": "Email" + "email": "Email", + "active": "Active", + "roles": "Roles" }, - "search": "Search..." + "action": { + "edit": "Edit User", + "delete": "Delete User" + }, + "search": "Search...", + "add-new": "New User" + }, + "add-edit-user": { + "title": { + "new": "Add New User", + "edit": "Edit User" + }, + "form": { + "first-name": "First Name", + "last-name": "Last Name", + "email": "Email", + "role": "Role" + }, + "actions": { + "save": "Save User", + "save-changes": "Save Changes", + "delete": "Delete User", + "cancel": "Cancel" + } }, "rules-screen": { "error": { @@ -1015,5 +1049,12 @@ "stream-download": { "abort": "You have an active download, closing this window will cause it to stop!", "error": "Failed to download {{filename}}. Please try again ..." + }, + "roles": { + "RED_USER": "User", + "RED_MANAGER": "Manager", + "RED_USER_ADMIN": "Users Admin", + "RED_ADMIN": "Application Admin", + "NO_ROLE": "No role defined" } } diff --git a/apps/red-ui/src/assets/styles/red-button.scss b/apps/red-ui/src/assets/styles/red-button.scss index 8ddf17bef..d63f68b46 100644 --- a/apps/red-ui/src/assets/styles/red-button.scss +++ b/apps/red-ui/src/assets/styles/red-button.scss @@ -2,7 +2,6 @@ .mat-button, .mat-flat-button { - font-family: Inter, sans-serif !important; border-radius: 17px !important; font-size: 13px !important; height: 34px; diff --git a/apps/red-ui/src/assets/styles/red-checkbox.scss b/apps/red-ui/src/assets/styles/red-checkbox.scss index 10f4ede37..ad06c1eee 100644 --- a/apps/red-ui/src/assets/styles/red-checkbox.scss +++ b/apps/red-ui/src/assets/styles/red-checkbox.scss @@ -18,7 +18,6 @@ } .mat-checkbox-label { - font-family: 'Inter', sans-serif; font-size: 13px; color: $accent; diff --git a/apps/red-ui/src/assets/styles/red-components.scss b/apps/red-ui/src/assets/styles/red-components.scss index 13236f052..e0cef767b 100644 --- a/apps/red-ui/src/assets/styles/red-components.scss +++ b/apps/red-ui/src/assets/styles/red-components.scss @@ -58,6 +58,11 @@ &.white-dark { border: 1px solid $grey-4; } + + &.inactive { + background-color: $grey-6; + color: $grey-7; + } } .oval { diff --git a/apps/red-ui/src/assets/styles/red-input.scss b/apps/red-ui/src/assets/styles/red-input.scss index 313f74129..6dc75c330 100644 --- a/apps/red-ui/src/assets/styles/red-input.scss +++ b/apps/red-ui/src/assets/styles/red-input.scss @@ -125,6 +125,11 @@ form { border-color: $red-1; } } + + &:disabled { + background-color: $grey-2; + color: rgba($grey-1, 0.3); + } } .hex-color-input { diff --git a/apps/red-ui/src/assets/styles/red-list.scss b/apps/red-ui/src/assets/styles/red-list.scss index 3c2d24243..48289a7b9 100644 --- a/apps/red-ui/src/assets/styles/red-list.scss +++ b/apps/red-ui/src/assets/styles/red-list.scss @@ -1,7 +1,6 @@ @import 'red-variables'; .mat-list-item { - font-family: Inter, sans-serif; color: $grey-1 !important; font-size: 13px !important; line-height: 16px !important; diff --git a/apps/red-ui/src/assets/styles/red-material-theme.scss b/apps/red-ui/src/assets/styles/red-material-theme.scss index fbee50daa..5242c9bbc 100644 --- a/apps/red-ui/src/assets/styles/red-material-theme.scss +++ b/apps/red-ui/src/assets/styles/red-material-theme.scss @@ -55,6 +55,12 @@ $gn-next-mat-theme: mat-light-theme( @include angular-material-theme($gn-next-mat-theme); +$custom-typography: mat-typography-config( + $font-family: 'Inter, sans-serif' +); + +@include angular-material-typography($custom-typography); + .mat-flat-button { min-width: unset !important; } diff --git a/apps/red-ui/src/assets/styles/red-menu.scss b/apps/red-ui/src/assets/styles/red-menu.scss index 2a2fe9792..31745e2f0 100644 --- a/apps/red-ui/src/assets/styles/red-menu.scss +++ b/apps/red-ui/src/assets/styles/red-menu.scss @@ -13,7 +13,6 @@ } .mat-menu-item { - font-family: 'Inter', sans-serif; font-size: 13px; color: $accent; padding: 0 8px; 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 030b1072c..d10a8fc83 100644 --- a/apps/red-ui/src/assets/styles/red-page-layout.scss +++ b/apps/red-ui/src/assets/styles/red-page-layout.scss @@ -121,7 +121,7 @@ body { } @media only screen and (max-width: 1600px) { - redaction-initials-avatar .username { + redaction-initials-avatar .username:not(.always-visible) { display: none; } } diff --git a/apps/red-ui/src/assets/styles/red-select.scss b/apps/red-ui/src/assets/styles/red-select.scss index b43b61ba5..eaf80a030 100644 --- a/apps/red-ui/src/assets/styles/red-select.scss +++ b/apps/red-ui/src/assets/styles/red-select.scss @@ -1,8 +1,6 @@ @import 'red-variables'; .mat-select-panel .mat-option { - font-family: Inter, sans-serif; - &:hover:not(.mat-option-disabled), &:focus:not(.mat-option-disabled) { background-color: $grey-6; diff --git a/apps/red-ui/src/assets/styles/red-toggle.scss b/apps/red-ui/src/assets/styles/red-toggle.scss index 65875cbc2..b4cadc526 100644 --- a/apps/red-ui/src/assets/styles/red-toggle.scss +++ b/apps/red-ui/src/assets/styles/red-toggle.scss @@ -26,10 +26,6 @@ display: none; } - .mat-slide-toggle-content { - font-family: Inter, sans-serif; - } - &.mat-primary.mat-checked { .mat-slide-toggle-bar { background-color: $primary; diff --git a/apps/red-ui/src/assets/styles/red-tooltips.scss b/apps/red-ui/src/assets/styles/red-tooltips.scss index f673f2393..ac70272ca 100644 --- a/apps/red-ui/src/assets/styles/red-tooltips.scss +++ b/apps/red-ui/src/assets/styles/red-tooltips.scss @@ -5,7 +5,6 @@ border-radius: 3px !important; padding: 10px; margin: 12px !important; - font-family: Inter, sans-serif; font-size: 11px; line-height: 14px; color: $white !important;