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": {