mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-22 10:11:32 +00:00
fix(console): bug fixes for ListProjectRoles and general pagination (#8938)
# Which Problems Are Solved A number of small problems are fixed relating to the project roles listed in various places in the UI: - Fixes issue #8460 - Fixes an issue where the "Master checkbox" that's supposed to check and uncheck all list items breaks when there's multiple pages of results. Demonstration images are attached at the end of the PR. - Fixes an issue where the "Edit Role" dialog opened by clicking on a role in the list will not save any changes if the role's group is empty even though empty groups are allowed during creation. - Fixes issues where the list does not properly update after the user modifies or deletes some of its entries. - Fixes an issue for all paginated lists where the page number information (like "0-25" specifying that items 0 through 25 are shown on screen) was inaccurate, as described in #8460. # How the Problems Are Solved - Fixes buggy handling of pre-selected roles while editing a grant so that all selected roles are saved instead of only the ones on the current page. - Triggers the entire page to be reloaded when a user modifies or deletes a role to easily ensure the information on the screen is accurate. - Revises checkbox logic so that the "Master checkbox" will apply only to rows on the current page. I think this is the correct behavior but tell me if it should be changed. - Other fixes to faulty logic. # Additional Changes - I made clicking on a group name toggle all the rows in that group on the screen, instead of just turning them on. Tell me if this should be changed back to what it was before. # Additional Context - Closes #8460 ## An example of the broken checkboxes:     --------- Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
parent
ff70ede7c7
commit
33bff5a4b0
@ -8,12 +8,10 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination"
|
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination">{{ startIndex }} - {{ endIndex }} </span>
|
||||||
>{{ pageIndex * pageSize }} - {{ pageIndex * pageSize + pageSize }}
|
|
||||||
</span>
|
|
||||||
<div class="row" *ngIf="!hidePagination">
|
<div class="row" *ngIf="!hidePagination">
|
||||||
<cnsl-form-field class="size">
|
<cnsl-form-field class="size">
|
||||||
<mat-select class="paginator-select" [(ngModel)]="pageSize" (selectionChange)="emitChange()">
|
<mat-select class="paginator-select" [value]="pageSize" (selectionChange)="updatePageSize($event.value)">
|
||||||
<mat-option *ngFor="let sizeOption of pageSizeOptions" [value]="sizeOption">
|
<mat-option *ngFor="let sizeOption of pageSizeOptions" [value]="sizeOption">
|
||||||
{{ sizeOption }}
|
{{ sizeOption }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@ -50,6 +50,15 @@ export class PaginatorComponent {
|
|||||||
return temp <= this.length / this.pageSize;
|
return temp <= this.length / this.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get startIndex(): number {
|
||||||
|
return this.pageIndex * this.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
get endIndex(): number {
|
||||||
|
const max = this.startIndex + this.pageSize;
|
||||||
|
return this.length < max ? this.length : max;
|
||||||
|
}
|
||||||
|
|
||||||
public emitChange(): void {
|
public emitChange(): void {
|
||||||
this.page.emit({
|
this.page.emit({
|
||||||
length: this.length,
|
length: this.length,
|
||||||
@ -58,4 +67,10 @@ export class PaginatorComponent {
|
|||||||
pageSizeOptions: this.pageSizeOptions,
|
pageSizeOptions: this.pageSizeOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updatePageSize(newSize: number): void {
|
||||||
|
this.pageSize = newSize;
|
||||||
|
this.pageIndex = 0;
|
||||||
|
this.emitChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,9 @@ export class ProjectRoleDetailDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitForm(): void {
|
submitForm(): void {
|
||||||
if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) {
|
if (this.formGroup.valid && this.key?.value && this.displayName?.value) {
|
||||||
this.mgmtService
|
this.mgmtService
|
||||||
.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group.value)
|
.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group?.value)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
|
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
|
||||||
this.dialogRef.close(true);
|
this.dialogRef.close(true);
|
||||||
|
@ -35,8 +35,8 @@
|
|||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
color="primary"
|
color="primary"
|
||||||
(change)="$event ? masterToggle() : null"
|
(change)="$event ? masterToggle() : null"
|
||||||
[checked]="selection.hasValue() && isAllSelected()"
|
[checked]="isAnySelected() && isAllSelected()"
|
||||||
[indeterminate]="selection.hasValue() && !isAllSelected()"
|
[indeterminate]="isAnySelected() && !isAllSelected()"
|
||||||
>
|
>
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
class="role state"
|
class="role state"
|
||||||
[ngClass]="{ 'no-selection': !selectionAllowed }"
|
[ngClass]="{ 'no-selection': !selectionAllowed }"
|
||||||
*ngIf="role.group"
|
*ngIf="role.group"
|
||||||
(click)="selectionAllowed ? selectAllOfGroup(role.group) : openDetailDialog(role)"
|
(click)="selectionAllowed ? groupMasterToggle(role.group) : openDetailDialog(role)"
|
||||||
[matTooltip]="selectionAllowed ? ('PROJECT.ROLE.SELECTGROUPTOOLTIP' | translate: role) : null"
|
[matTooltip]="selectionAllowed ? ('PROJECT.ROLE.SELECTGROUPTOOLTIP' | translate: role) : null"
|
||||||
>{{ role.group }}</span
|
>{{ role.group }}</span
|
||||||
>
|
>
|
||||||
@ -135,7 +135,7 @@
|
|||||||
#paginator
|
#paginator
|
||||||
[timestamp]="dataSource.viewTimestamp"
|
[timestamp]="dataSource.viewTimestamp"
|
||||||
[length]="dataSource.totalResult"
|
[length]="dataSource.totalResult"
|
||||||
[pageSize]="50"
|
[pageSize]="INITIAL_PAGE_SIZE"
|
||||||
(page)="changePage()"
|
(page)="changePage()"
|
||||||
[pageSizeOptions]="[25, 50, 100, 250]"
|
[pageSizeOptions]="[25, 50, 100, 250]"
|
||||||
>
|
>
|
||||||
|
@ -18,6 +18,7 @@ import { ProjectRolesDataSource } from './project-roles-table-datasource';
|
|||||||
styleUrls: ['./project-roles-table.component.scss'],
|
styleUrls: ['./project-roles-table.component.scss'],
|
||||||
})
|
})
|
||||||
export class ProjectRolesTableComponent implements OnInit {
|
export class ProjectRolesTableComponent implements OnInit {
|
||||||
|
public INITIAL_PAGE_SIZE: number = 50;
|
||||||
@Input() public projectId: string = '';
|
@Input() public projectId: string = '';
|
||||||
@Input() public grantId: string = '';
|
@Input() public grantId: string = '';
|
||||||
@Input() public disabled: boolean = false;
|
@Input() public disabled: boolean = false;
|
||||||
@ -43,41 +44,58 @@ export class ProjectRolesTableComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.dataSource.loadRoles(this.projectId, this.grantId, 0, 25, 'asc');
|
this.loadRolesPage();
|
||||||
|
this.selection.select(...this.selectedKeys);
|
||||||
this.dataSource.rolesSubject.subscribe((roles) => {
|
|
||||||
const selectedRoles: Role.AsObject[] = roles.filter((role) => this.selectedKeys.includes(role.key));
|
|
||||||
this.selection.select(...selectedRoles.map((r) => r.key));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.selection.changed.subscribe(() => {
|
this.selection.changed.subscribe(() => {
|
||||||
this.changedSelection.emit(this.selection.selected);
|
this.changedSelection.emit(this.selection.selected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectAllOfGroup(group: string): void {
|
|
||||||
const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue().filter((role) => role.group === group);
|
|
||||||
this.selection.select(...groupRoles.map((r) => r.key));
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadRolesPage(): void {
|
private loadRolesPage(): void {
|
||||||
this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25);
|
this.dataSource.loadRoles(
|
||||||
|
this.projectId,
|
||||||
|
this.grantId,
|
||||||
|
this.paginator?.pageIndex ?? 0,
|
||||||
|
this.paginator?.pageSize ?? this.INITIAL_PAGE_SIZE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public changePage(): void {
|
public changePage(): void {
|
||||||
this.loadRolesPage();
|
this.loadRolesPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAllSelected(): boolean {
|
private listIsAllSelected(list: string[]): boolean {
|
||||||
const numSelected = this.selection.selected.length;
|
return list.findIndex((key) => !this.selection.isSelected(key)) == -1;
|
||||||
const numRows = this.dataSource.totalResult;
|
}
|
||||||
return numSelected === numRows;
|
|
||||||
|
private listIsAnySelected(list: string[]): boolean {
|
||||||
|
return list.findIndex((key) => this.selection.isSelected(key)) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private listMasterToggle(list: string[]): void {
|
||||||
|
if (this.listIsAllSelected(list)) this.selection.deselect(...list);
|
||||||
|
else this.selection.select(...list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private compilePageKeys(): string[] {
|
||||||
|
return this.dataSource.rolesSubject.value.map((role) => role.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public masterToggle(): void {
|
public masterToggle(): void {
|
||||||
this.isAllSelected()
|
this.listMasterToggle(this.compilePageKeys());
|
||||||
? this.selection.clear()
|
}
|
||||||
: this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row.key));
|
|
||||||
|
public isAllSelected(): boolean {
|
||||||
|
return this.listIsAllSelected(this.compilePageKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAnySelected(): boolean {
|
||||||
|
return this.listIsAnySelected(this.compilePageKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
public groupMasterToggle(group: string): void {
|
||||||
|
this.listMasterToggle(this.dataSource.rolesSubject.value.filter((role) => role.group == group).map((role) => role.key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteRole(role: Role.AsObject): void {
|
public deleteRole(role: Role.AsObject): void {
|
||||||
@ -93,45 +111,28 @@ export class ProjectRolesTableComponent implements OnInit {
|
|||||||
|
|
||||||
dialogRef.afterClosed().subscribe((resp) => {
|
dialogRef.afterClosed().subscribe((resp) => {
|
||||||
if (resp) {
|
if (resp) {
|
||||||
const index = this.dataSource.rolesSubject.value.findIndex((iter) => iter.key === role.key);
|
|
||||||
|
|
||||||
this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => {
|
this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => {
|
||||||
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
|
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
|
||||||
|
this.loadRolesPage();
|
||||||
if (index > -1) {
|
|
||||||
this.dataSource.rolesSubject.value.splice(index, 1);
|
|
||||||
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeRole(role: Role.AsObject, index: number): void {
|
|
||||||
this.mgmtService
|
|
||||||
.removeProjectRole(this.projectId, role.key)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
|
|
||||||
this.dataSource.rolesSubject.value.splice(index, 1);
|
|
||||||
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public openDetailDialog(role: Role.AsObject): void {
|
public openDetailDialog(role: Role.AsObject): void {
|
||||||
this.dialog.open(ProjectRoleDetailDialogComponent, {
|
const dialogRef = this.dialog.open(ProjectRoleDetailDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
role,
|
role,
|
||||||
projectId: this.projectId,
|
projectId: this.projectId,
|
||||||
},
|
},
|
||||||
width: '400px',
|
width: '400px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(() => this.loadRolesPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshPage(): void {
|
public refreshPage(): void {
|
||||||
this.dataSource.loadRoles(this.projectId, this.grantId, this.paginator?.pageIndex ?? 0, this.paginator?.pageSize ?? 25);
|
this.loadRolesPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get selectionAllowed(): boolean {
|
public get selectionAllowed(): boolean {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user