diff --git a/console/src/app/modules/warn-dialog/warn-dialog.component.html b/console/src/app/modules/warn-dialog/warn-dialog.component.html index 97d0f484e7..6a8bdaa896 100644 --- a/console/src/app/modules/warn-dialog/warn-dialog.component.html +++ b/console/src/app/modules/warn-dialog/warn-dialog.component.html @@ -1,16 +1,22 @@ {{data.titleKey | translate: data.titleParam}}
- +

{{data.descriptionKey | translate: data.descriptionParam}}

+ + + {{data.confirmationKey | translate: {value: data.confirmation} }} + +
-
\ No newline at end of file diff --git a/console/src/app/modules/warn-dialog/warn-dialog.component.ts b/console/src/app/modules/warn-dialog/warn-dialog.component.ts index ace2cf5435..7b658af06c 100644 --- a/console/src/app/modules/warn-dialog/warn-dialog.component.ts +++ b/console/src/app/modules/warn-dialog/warn-dialog.component.ts @@ -7,11 +7,8 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; styleUrls: ['./warn-dialog.component.scss'], }) export class WarnDialogComponent { - - constructor( - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any, - ) { } + public confirm: string = ''; + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) {} public closeDialog(): void { this.dialogRef.close(false); diff --git a/console/src/app/modules/warn-dialog/warn-dialog.module.ts b/console/src/app/modules/warn-dialog/warn-dialog.module.ts index 6520c8ef7e..0ec93b3591 100644 --- a/console/src/app/modules/warn-dialog/warn-dialog.module.ts +++ b/console/src/app/modules/warn-dialog/warn-dialog.module.ts @@ -1,18 +1,14 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { TranslateModule } from '@ngx-translate/core'; +import { InputModule } from '../input/input.module'; import { WarnDialogComponent } from './warn-dialog.component'; - - @NgModule({ - declarations: [WarnDialogComponent], - imports: [ - CommonModule, - TranslateModule, - MatButtonModule, - ], + declarations: [WarnDialogComponent], + imports: [CommonModule, FormsModule, TranslateModule, MatButtonModule, InputModule], }) -export class WarnDialogModule { } +export class WarnDialogModule {} diff --git a/console/src/app/pages/users/user-list/user-table/user-table.component.ts b/console/src/app/pages/users/user-list/user-table/user-table.component.ts index aa36df8fac..de5122e742 100644 --- a/console/src/app/pages/users/user-list/user-table/user-table.component.ts +++ b/console/src/app/pages/users/user-list/user-table/user-table.component.ts @@ -23,6 +23,7 @@ import { UserNameQuery, UserState, } from 'src/app/proto/generated/zitadel/user_pb'; +import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { ManagementService } from 'src/app/services/mgmt.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -38,9 +39,7 @@ enum UserListSearchKey { selector: 'cnsl-user-table', templateUrl: './user-table.component.html', styleUrls: ['./user-table.component.scss'], - animations: [ - enterAnimations, - ], + animations: [enterAnimations], }) export class UserTableComponent implements OnInit { public userSearchKey: UserListSearchKey | undefined = undefined; @@ -66,6 +65,7 @@ export class UserTableComponent implements OnInit { constructor( public translate: TranslateService, + private authService: GrpcAuthService, private userService: ManagementService, private toast: ToastService, private dialog: MatDialog, @@ -77,7 +77,7 @@ export class UserTableComponent implements OnInit { } ngOnInit(): void { - this.route.queryParams.pipe(take(1)).subscribe(params => { + this.route.queryParams.pipe(take(1)).subscribe((params) => { this.getData(10, 0, this.type); if (params.deferredReload) { setTimeout(() => { @@ -94,43 +94,48 @@ export class UserTableComponent implements OnInit { } public masterToggle(): void { - this.isAllSelected() ? - this.selection.clear() : - this.dataSource.data.forEach(row => this.selection.select(row)); + this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row)); } - public changePage(event: PageEvent): void { this.selection.clear(); this.getData(event.pageSize, event.pageIndex * event.pageSize, this.type); } public deactivateSelectedUsers(): void { - Promise.all(this.selection.selected.map(value => { - return this.userService.deactivateUser(value.id); - })).then(() => { - this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true); - this.selection.clear(); - setTimeout(() => { - this.refreshPage(); - }, 1000); - }).catch(error => { - this.toast.showError(error); - }); + Promise.all( + this.selection.selected.map((value) => { + return this.userService.deactivateUser(value.id); + }), + ) + .then(() => { + this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true); + this.selection.clear(); + setTimeout(() => { + this.refreshPage(); + }, 1000); + }) + .catch((error) => { + this.toast.showError(error); + }); } public reactivateSelectedUsers(): void { - Promise.all(this.selection.selected.map(value => { - return this.userService.reactivateUser(value.id); - })).then(() => { - this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true); - this.selection.clear(); - setTimeout(() => { - this.refreshPage(); - }, 1000); - }).catch(error => { - this.toast.showError(error); - }); + Promise.all( + this.selection.selected.map((value) => { + return this.userService.reactivateUser(value.id); + }), + ) + .then(() => { + this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true); + this.selection.clear(); + setTimeout(() => { + this.refreshPage(); + }, 1000); + }) + .catch((error) => { + this.toast.showError(error); + }); } private async getData(limit: number, offset: number, type: Type, searchValue?: string): Promise { @@ -180,21 +185,24 @@ export class UserTableComponent implements OnInit { } } - this.userService.listUsers(limit, offset, [query]).then(resp => { - if (resp.details?.totalResult) { - this.totalResult = resp.details?.totalResult; - } else { - this.totalResult = 0; - } - if (resp.details?.viewTimestamp) { - this.viewTimestamp = resp.details?.viewTimestamp; - } - this.dataSource.data = resp.resultList; - this.loadingSubject.next(false); - }).catch(error => { - this.toast.showError(error); - this.loadingSubject.next(false); - }); + this.userService + .listUsers(limit, offset, [query]) + .then((resp) => { + if (resp.details?.totalResult) { + this.totalResult = resp.details?.totalResult; + } else { + this.totalResult = 0; + } + if (resp.details?.viewTimestamp) { + this.viewTimestamp = resp.details?.viewTimestamp; + } + this.dataSource.data = resp.resultList; + this.loadingSubject.next(false); + }) + .catch((error) => { + this.toast.showError(error); + this.loadingSubject.next(false); + }); } public refreshPage(): void { @@ -205,12 +213,7 @@ export class UserTableComponent implements OnInit { this.selection.clear(); const filterValue = (event.target as HTMLInputElement).value; - this.getData( - this.paginator.pageSize, - this.paginator.pageIndex * this.paginator.pageSize, - this.type, - filterValue, - ); + this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type, filterValue); } public setFilter(key: UserListSearchKey): void { @@ -229,27 +232,57 @@ export class UserTableComponent implements OnInit { } public deleteUser(user: User.AsObject): void { - const dialogRef = this.dialog.open(WarnDialogComponent, { - data: { - confirmKey: 'ACTIONS.DELETE', - cancelKey: 'ACTIONS.CANCEL', - titleKey: 'USER.DIALOG.DELETE_TITLE', - descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION', - }, - width: '400px', - }); + const authUserData = { + confirmKey: 'ACTIONS.DELETE', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'USER.DIALOG.DELETE_SELF_TITLE', + descriptionKey: 'USER.DIALOG.DELETE_SELF_DESCRIPTION', + confirmationKey: 'USER.DIALOG.TYPEUSERNAME', + confirmation: user.preferredLoginName, + }; - dialogRef.afterClosed().subscribe(resp => { - if (resp) { - this.userService.removeUser(user.id).then(() => { - setTimeout(() => { - this.refreshPage(); - }, 1000); - this.toast.showInfo('USER.TOAST.DELETED', true); - }).catch(error => { - this.toast.showError(error); + const mgmtUserData = { + confirmKey: 'ACTIONS.DELETE', + cancelKey: 'ACTIONS.CANCEL', + titleKey: 'USER.DIALOG.DELETE_TITLE', + descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION', + confirmationKey: 'USER.DIALOG.TYPEUSERNAME', + confirmation: user.preferredLoginName, + }; + + if (user && user.id) { + const authUser = this.authService.userSubject.getValue(); + const isMe = authUser?.id === user.id; + + let dialogRef; + + if (isMe) { + dialogRef = this.dialog.open(WarnDialogComponent, { + data: authUserData, + width: '400px', + }); + } else { + dialogRef = this.dialog.open(WarnDialogComponent, { + data: mgmtUserData, + width: '400px', }); } - }); + + dialogRef.afterClosed().subscribe((resp) => { + if (resp) { + this.userService + .removeUser(user.id) + .then(() => { + setTimeout(() => { + this.refreshPage(); + }, 1000); + this.toast.showInfo('USER.TOAST.DELETED', true); + }) + .catch((error) => { + this.toast.showError(error); + }); + } + }); + } } } diff --git a/console/src/app/services/grpc-auth.service.ts b/console/src/app/services/grpc-auth.service.ts index 45bc1df0df..497f838916 100644 --- a/console/src/app/services/grpc-auth.service.ts +++ b/console/src/app/services/grpc-auth.service.ts @@ -96,6 +96,7 @@ import { StorageKey, StorageLocation, StorageService } from './storage.service'; export class GrpcAuthService { private _activeOrgChanged: Subject = new Subject(); public user!: Observable; + public userSubject: BehaviorSubject = new BehaviorSubject(undefined); private zitadelPermissions: BehaviorSubject = new BehaviorSubject(['user.resourceowner']); private zitadelFeatures: BehaviorSubject = new BehaviorSubject(['']); @@ -137,6 +138,8 @@ export class GrpcAuthService { }), ); + this.user.subscribe(this.userSubject); + this.activeOrgChanged.subscribe(() => { this.loadPermissions(); this.loadFeatures(); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 14a5282dad..b4605cf63b 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -194,8 +194,11 @@ }, "DIALOG": { "DELETE_TITLE": "User löschen", + "DELETE_SELF_TITLE": "Eigenen User löschen", "DELETE_DESCRIPTION": "Sie sind im Begriff einen Benutzer endgültig zu löschen. Wollen Sie dies wirklich tun?", + "DELETE_SELF_DESCRIPTION": "Sie sind im Begriff Ihren eigenen Benutzer endgültig zu löschen. Dadurch werden Sie ausgeloggt und Ihr Account wird gelöscht. Diese Aktion kann nicht rückgängig gemacht werden!", "DELETE_AUTH_DESCRIPTION": "Sie sind im Begriff Ihren Account endgültig zu löschen. Wollen Sie dies wirklich tun?", + "TYPEUSERNAME": "Wiederholen Sie '{{value}}', um den Benutzer zu löschen.", "DELETE_BTN": "Entgültig löschen" }, "SENDEMAILDIALOG": { diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index a5707fac59..47c134008f 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -194,8 +194,11 @@ }, "DIALOG": { "DELETE_TITLE": "Delete User", + "DELETE_SELF_TITLE": "Delete Account", "DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?", + "DELETE_SELF_DESCRIPTION": "You are about to permanently delete your personal account. This will log you out and delete your user. This action cannot be undone!", "DELETE_AUTH_DESCRIPTION": "You are about to permanently delete your personal account. Are you sure?", + "TYPEUSERNAME": "Type '{{value}}', to confirm and delete the user.", "DELETE_BTN": "Delete permanently" }, "SENDEMAILDIALOG": { diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 8babf3a037..8c0b625187 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -194,8 +194,11 @@ }, "DIALOG": { "DELETE_TITLE": "Elimina utente", + "DELETE_SELF_TITLE": "Elimina Account", "DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?", + "DELETE_SELF_DESCRIPTION": "Stai per eliminare definitivamente il tuo account. Questo ti disconnetterà ed eliminerà il tuo utente. Questa azione non può essere annullata!", "DELETE_AUTH_DESCRIPTION": "Stai per eleminare il tuo account personale in modo permanente. Vuoi continuare?", + "TYPEUSERNAME": "Ripeti '{{value}}' per confermare ed eliminare l'utente.", "DELETE_BTN": "Elimina" }, "SENDEMAILDIALOG": {