mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:07:23 +00:00
feat: add confirmation field for user deletion, self warning (#2971)
This commit is contained in:
parent
e624047d60
commit
478beded9f
@ -4,13 +4,19 @@
|
|||||||
<i class="icon {{data.icon}}"></i>
|
<i class="icon {{data.icon}}"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="desc"> {{data.descriptionKey | translate: data.descriptionParam}}</p>
|
<p class="desc"> {{data.descriptionKey | translate: data.descriptionParam}}</p>
|
||||||
|
|
||||||
|
<cnsl-form-field *ngIf="data.confirmation && data.confirmationKey" class="formfield">
|
||||||
|
<cnsl-label>{{data.confirmationKey | translate: {value: data.confirmation} }}</cnsl-label>
|
||||||
|
<input cnslInput [(ngModel)]="confirm" />
|
||||||
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions class="action">
|
<div mat-dialog-actions class="action">
|
||||||
<button *ngIf="data.cancelKey" mat-button (click)="closeDialog()">
|
<button *ngIf="data.cancelKey" mat-button (click)="closeDialog()">
|
||||||
{{data.cancelKey | translate}}
|
{{data.cancelKey | translate}}
|
||||||
</button>
|
</button>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<button color="warn" mat-raised-button class="ok-button" (click)="closeDialogWithSuccess()">
|
<button color="warn" [disabled]="data.confirmation && confirm !== data.confirmation" mat-raised-button
|
||||||
|
class="ok-button" (click)="closeDialogWithSuccess()">
|
||||||
{{data.confirmKey | translate}}
|
{{data.confirmKey | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -7,11 +7,8 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|||||||
styleUrls: ['./warn-dialog.component.scss'],
|
styleUrls: ['./warn-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class WarnDialogComponent {
|
export class WarnDialogComponent {
|
||||||
|
public confirm: string = '';
|
||||||
constructor(
|
constructor(public dialogRef: MatDialogRef<WarnDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {}
|
||||||
public dialogRef: MatDialogRef<WarnDialogComponent>,
|
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
public closeDialog(): void {
|
public closeDialog(): void {
|
||||||
this.dialogRef.close(false);
|
this.dialogRef.close(false);
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { InputModule } from '../input/input.module';
|
||||||
import { WarnDialogComponent } from './warn-dialog.component';
|
import { WarnDialogComponent } from './warn-dialog.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [WarnDialogComponent],
|
declarations: [WarnDialogComponent],
|
||||||
imports: [
|
imports: [CommonModule, FormsModule, TranslateModule, MatButtonModule, InputModule],
|
||||||
CommonModule,
|
|
||||||
TranslateModule,
|
|
||||||
MatButtonModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class WarnDialogModule { }
|
export class WarnDialogModule {}
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
UserNameQuery,
|
UserNameQuery,
|
||||||
UserState,
|
UserState,
|
||||||
} from 'src/app/proto/generated/zitadel/user_pb';
|
} 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 { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
import { ToastService } from 'src/app/services/toast.service';
|
import { ToastService } from 'src/app/services/toast.service';
|
||||||
|
|
||||||
@ -38,9 +39,7 @@ enum UserListSearchKey {
|
|||||||
selector: 'cnsl-user-table',
|
selector: 'cnsl-user-table',
|
||||||
templateUrl: './user-table.component.html',
|
templateUrl: './user-table.component.html',
|
||||||
styleUrls: ['./user-table.component.scss'],
|
styleUrls: ['./user-table.component.scss'],
|
||||||
animations: [
|
animations: [enterAnimations],
|
||||||
enterAnimations,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class UserTableComponent implements OnInit {
|
export class UserTableComponent implements OnInit {
|
||||||
public userSearchKey: UserListSearchKey | undefined = undefined;
|
public userSearchKey: UserListSearchKey | undefined = undefined;
|
||||||
@ -66,6 +65,7 @@ export class UserTableComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
|
private authService: GrpcAuthService,
|
||||||
private userService: ManagementService,
|
private userService: ManagementService,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
@ -77,7 +77,7 @@ export class UserTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.queryParams.pipe(take(1)).subscribe(params => {
|
this.route.queryParams.pipe(take(1)).subscribe((params) => {
|
||||||
this.getData(10, 0, this.type);
|
this.getData(10, 0, this.type);
|
||||||
if (params.deferredReload) {
|
if (params.deferredReload) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -94,41 +94,46 @@ export class UserTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public masterToggle(): void {
|
public masterToggle(): void {
|
||||||
this.isAllSelected() ?
|
this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row));
|
||||||
this.selection.clear() :
|
|
||||||
this.dataSource.data.forEach(row => this.selection.select(row));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public changePage(event: PageEvent): void {
|
public changePage(event: PageEvent): void {
|
||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
this.getData(event.pageSize, event.pageIndex * event.pageSize, this.type);
|
this.getData(event.pageSize, event.pageIndex * event.pageSize, this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public deactivateSelectedUsers(): void {
|
public deactivateSelectedUsers(): void {
|
||||||
Promise.all(this.selection.selected.map(value => {
|
Promise.all(
|
||||||
|
this.selection.selected.map((value) => {
|
||||||
return this.userService.deactivateUser(value.id);
|
return this.userService.deactivateUser(value.id);
|
||||||
})).then(() => {
|
}),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshPage();
|
this.refreshPage();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public reactivateSelectedUsers(): void {
|
public reactivateSelectedUsers(): void {
|
||||||
Promise.all(this.selection.selected.map(value => {
|
Promise.all(
|
||||||
|
this.selection.selected.map((value) => {
|
||||||
return this.userService.reactivateUser(value.id);
|
return this.userService.reactivateUser(value.id);
|
||||||
})).then(() => {
|
}),
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true);
|
this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true);
|
||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshPage();
|
this.refreshPage();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -180,7 +185,9 @@ export class UserTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userService.listUsers(limit, offset, [query]).then(resp => {
|
this.userService
|
||||||
|
.listUsers(limit, offset, [query])
|
||||||
|
.then((resp) => {
|
||||||
if (resp.details?.totalResult) {
|
if (resp.details?.totalResult) {
|
||||||
this.totalResult = resp.details?.totalResult;
|
this.totalResult = resp.details?.totalResult;
|
||||||
} else {
|
} else {
|
||||||
@ -191,7 +198,8 @@ export class UserTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.dataSource.data = resp.resultList;
|
this.dataSource.data = resp.resultList;
|
||||||
this.loadingSubject.next(false);
|
this.loadingSubject.next(false);
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
this.loadingSubject.next(false);
|
this.loadingSubject.next(false);
|
||||||
});
|
});
|
||||||
@ -205,12 +213,7 @@ export class UserTableComponent implements OnInit {
|
|||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
const filterValue = (event.target as HTMLInputElement).value;
|
const filterValue = (event.target as HTMLInputElement).value;
|
||||||
|
|
||||||
this.getData(
|
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize, this.type, filterValue);
|
||||||
this.paginator.pageSize,
|
|
||||||
this.paginator.pageIndex * this.paginator.pageSize,
|
|
||||||
this.type,
|
|
||||||
filterValue,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFilter(key: UserListSearchKey): void {
|
public setFilter(key: UserListSearchKey): void {
|
||||||
@ -229,27 +232,57 @@ export class UserTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public deleteUser(user: User.AsObject): void {
|
public deleteUser(user: User.AsObject): void {
|
||||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
const authUserData = {
|
||||||
data: {
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mgmtUserData = {
|
||||||
confirmKey: 'ACTIONS.DELETE',
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
cancelKey: 'ACTIONS.CANCEL',
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
titleKey: 'USER.DIALOG.DELETE_TITLE',
|
titleKey: 'USER.DIALOG.DELETE_TITLE',
|
||||||
descriptionKey: 'USER.DIALOG.DELETE_DESCRIPTION',
|
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',
|
width: '400px',
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: mgmtUserData,
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(resp => {
|
dialogRef.afterClosed().subscribe((resp) => {
|
||||||
if (resp) {
|
if (resp) {
|
||||||
this.userService.removeUser(user.id).then(() => {
|
this.userService
|
||||||
|
.removeUser(user.id)
|
||||||
|
.then(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshPage();
|
this.refreshPage();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
this.toast.showInfo('USER.TOAST.DELETED', true);
|
this.toast.showInfo('USER.TOAST.DELETED', true);
|
||||||
}).catch(error => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
this.toast.showError(error);
|
this.toast.showError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ import { StorageKey, StorageLocation, StorageService } from './storage.service';
|
|||||||
export class GrpcAuthService {
|
export class GrpcAuthService {
|
||||||
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
|
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
|
||||||
public user!: Observable<User.AsObject | undefined>;
|
public user!: Observable<User.AsObject | undefined>;
|
||||||
|
public userSubject: BehaviorSubject<User.AsObject | undefined> = new BehaviorSubject<User.AsObject | undefined>(undefined);
|
||||||
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject(['user.resourceowner']);
|
private zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject(['user.resourceowner']);
|
||||||
private zitadelFeatures: BehaviorSubject<string[]> = new BehaviorSubject(['']);
|
private zitadelFeatures: BehaviorSubject<string[]> = new BehaviorSubject(['']);
|
||||||
|
|
||||||
@ -137,6 +138,8 @@ export class GrpcAuthService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.user.subscribe(this.userSubject);
|
||||||
|
|
||||||
this.activeOrgChanged.subscribe(() => {
|
this.activeOrgChanged.subscribe(() => {
|
||||||
this.loadPermissions();
|
this.loadPermissions();
|
||||||
this.loadFeatures();
|
this.loadFeatures();
|
||||||
|
@ -194,8 +194,11 @@
|
|||||||
},
|
},
|
||||||
"DIALOG": {
|
"DIALOG": {
|
||||||
"DELETE_TITLE": "User löschen",
|
"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_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?",
|
"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"
|
"DELETE_BTN": "Entgültig löschen"
|
||||||
},
|
},
|
||||||
"SENDEMAILDIALOG": {
|
"SENDEMAILDIALOG": {
|
||||||
|
@ -194,8 +194,11 @@
|
|||||||
},
|
},
|
||||||
"DIALOG": {
|
"DIALOG": {
|
||||||
"DELETE_TITLE": "Delete User",
|
"DELETE_TITLE": "Delete User",
|
||||||
|
"DELETE_SELF_TITLE": "Delete Account",
|
||||||
"DELETE_DESCRIPTION": "You are about to permanently delete a user. Are you sure?",
|
"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?",
|
"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"
|
"DELETE_BTN": "Delete permanently"
|
||||||
},
|
},
|
||||||
"SENDEMAILDIALOG": {
|
"SENDEMAILDIALOG": {
|
||||||
|
@ -194,8 +194,11 @@
|
|||||||
},
|
},
|
||||||
"DIALOG": {
|
"DIALOG": {
|
||||||
"DELETE_TITLE": "Elimina utente",
|
"DELETE_TITLE": "Elimina utente",
|
||||||
|
"DELETE_SELF_TITLE": "Elimina Account",
|
||||||
"DELETE_DESCRIPTION": "Stai per eliminare definitivamente un utente. Sei sicuro?",
|
"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?",
|
"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"
|
"DELETE_BTN": "Elimina"
|
||||||
},
|
},
|
||||||
"SENDEMAILDIALOG": {
|
"SENDEMAILDIALOG": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user