mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-12 00:55:12 +00:00
feat: rehauled console (#3525)
* new console * move npm ci to angular build * rel path for assets * local grpc copy * login policy, rm clear views, features rel path * lock Co-authored-by: Livio Amstutz <[email protected]>
This commit is contained in:
co-authored by
Livio Amstutz
parent
00f7dbe875
commit
08ae39ae19
@@ -115,26 +115,27 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
}
|
||||
|
||||
private loadResponse(promise: Promise<ListUserGrantResponse.AsObject>): void {
|
||||
from(promise).pipe(
|
||||
map(resp => {
|
||||
if (resp.details?.totalResult) {
|
||||
this.totalResult = resp.details.totalResult;
|
||||
} else {
|
||||
this.totalResult = 0;
|
||||
}
|
||||
if (resp.details?.viewTimestamp) {
|
||||
this.viewTimestamp = resp.details.viewTimestamp;
|
||||
}
|
||||
return resp.resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
).subscribe(grants => {
|
||||
this.grantsSubject.next(grants);
|
||||
});
|
||||
from(promise)
|
||||
.pipe(
|
||||
map((resp) => {
|
||||
if (resp.details?.totalResult) {
|
||||
this.totalResult = resp.details.totalResult;
|
||||
} else {
|
||||
this.totalResult = 0;
|
||||
}
|
||||
if (resp.details?.viewTimestamp) {
|
||||
this.viewTimestamp = resp.details.viewTimestamp;
|
||||
}
|
||||
return resp.resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
)
|
||||
.subscribe((grants) => {
|
||||
this.grantsSubject.next(grants);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Connect this data source to the table. The table will only update when
|
||||
* the returned stream emits new items.
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
<cnsl-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()"
|
||||
<cnsl-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()" [hideRefresh]="true"
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
|
||||
[dataSize]="dataSource?.totalResult ?? 0" [selection]="selection">
|
||||
<cnsl-form-field @appearfade *ngIf="userGrantListSearchKey !== undefined" actions class="filtername">
|
||||
<input class="filterinput" cnslInput (keyup)="applyFilter($event)"
|
||||
[placeholder]="('USER.GRANTS.FILTER.' + userGrantListSearchKey.toString()) | translate" #input>
|
||||
</cnsl-form-field>
|
||||
<!-- <div leftActions class="user-grants-table-left-actions">
|
||||
<button class="user-grant-type-button" [ngClass]="{'active': type === undefined}"
|
||||
(click)="setType(undefined)">{{'PROJECT.GRANT.ALL' | translate}}</button>
|
||||
<button class="user-grant-type-button" [ngClass]="{'active': type === Type.TYPE_HUMAN}"
|
||||
(click)="setType(Type.TYPE_HUMAN)">{{'USER.TABLE.TYPES.HUMAN' | translate}}</button>
|
||||
<button class="user-grant-type-button" [ngClass]="{'active': type === Type.TYPE_MACHINE}"
|
||||
(click)="setType(Type.TYPE_MACHINE)">{{'USER.TABLE.TYPES.MACHINE' | translate}}</button>
|
||||
</div> -->
|
||||
|
||||
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
|
||||
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="cnsl-action-button" mat-raised-button actions
|
||||
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete === false">
|
||||
<i class="las la-trash"></i>
|
||||
<span>{{'ACTIONS.DELETE' | translate}}</span>
|
||||
<cnsl-action-keys (actionTriggered)="deleteGrantSelection()" [type]="ActionKeysType.DELETE"></cnsl-action-keys>
|
||||
</button>
|
||||
<a *ngIf="disableWrite === false" matTooltip="{{'GRANTS.ADD' | translate}}" actions color="primary" color="primary"
|
||||
mat-raised-button [routerLink]="routerLink">
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }}
|
||||
|
||||
<cnsl-filter-user-grants actions *ngIf="!selection.hasValue()" (filterChanged)="applySearchQuery($any($event))"
|
||||
(filterOpen)="filterOpen = $event"></cnsl-filter-user-grants>
|
||||
|
||||
<a actions *ngIf="disableWrite === false && (selection.hasValue() === false)"
|
||||
matTooltip="{{'GRANTS.ADD' | translate}}" color="primary" class="cnsl-action-button" mat-raised-button
|
||||
[routerLink]="routerLink">
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
<span>{{ 'GRANTS.ADD_BTN' | translate }}</span>
|
||||
<cnsl-action-keys (actionTriggered)="gotoCreateLink(routerLink)" [type]="ActionKeysType.ADD"></cnsl-action-keys>
|
||||
</a>
|
||||
|
||||
<div class="table-wrapper">
|
||||
@@ -29,154 +42,103 @@
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + row?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + row?.id] : []) | hasRole | async))"
|
||||
color="primary" (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)">
|
||||
<cnsl-avatar
|
||||
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
|
||||
class="avatar" [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''"
|
||||
[forColor]="row?.preferredLoginName" [size]="32">
|
||||
<cnsl-avatar *ngIf="row && row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
|
||||
[name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [forColor]="row?.preferredLoginName"
|
||||
[size]="32">
|
||||
</cnsl-avatar>
|
||||
<ng-template #cog>
|
||||
<cnsl-avatar class="avatar" [isMachine]="true" [forColor]="row.preferredLoginName" [size]="32">
|
||||
</cnsl-avatar>
|
||||
</ng-template>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="user">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.DISPLAY_NAME}"></template>
|
||||
</th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
<a class="user" [routerLink]="['/users', grant.userId]">{{grant?.displayName}}</a>
|
||||
<a class="user">{{grant.displayName ? grant.displayName :
|
||||
grant.userName ? grant.userName : ''}}</a>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="org">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.GRANTEDORGDOMAIN' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ORG_NAME}"></template>
|
||||
</th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.ORG' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.orgName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="projectId">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.PROJECT_NAME}"></template>
|
||||
</th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.projectName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="dates">
|
||||
<th mat-header-cell *matHeaderCellDef> DATES </th>
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.TYPE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
<div class="date-block" *ngIf="grant.details?.creationDate">
|
||||
<span class="date-sub">{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}:</span>
|
||||
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
|
||||
</div>
|
||||
<div class="date-block" *ngIf="grant.details?.changeDate">
|
||||
<span class="date-sub">{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</span>
|
||||
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
|
||||
</div>
|
||||
<span class="state">{{getType(grant)}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'PROJECT.GRANT.CREATIONDATE' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
<span>{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
|
||||
<span class="no-break">{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
<span>{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
|
||||
<span class="no-break">{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="roleNamesList">
|
||||
<th mat-header-cell *matHeaderCellDef class="role-data">
|
||||
{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}
|
||||
<template [ngTemplateOutlet]="templateRef"
|
||||
[ngTemplateOutletContext]="{key: UserGrantListSearchKey.ROLE_KEY}"></template>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let grant; let i = index" class="role-data">
|
||||
<ng-container
|
||||
*ngIf="(context === UserGrantContext.USER || context === UserGrantContext.NONE) && (grant.id && grantToEdit !== grant.id) || (grantToEdit !== grant.id)">
|
||||
<div class="flex-row">
|
||||
<div class="role">
|
||||
<span *ngFor="let role of grant.roleKeysList">{{ role }}</span>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<button mat-stroked-button *ngIf="grant.id ? grantToEdit !== grant.id : grantToEdit !== grant.id"
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
|
||||
(click)="loadGrantOptions(grant)" matTooltip="{{'ACTIONS.CHANGE' | translate}}">
|
||||
<i class="las la-edit"></i>
|
||||
{{'ACTIONS.EDIT' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="row-form">
|
||||
<ng-container
|
||||
*ngIf="(context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && grantToEdit === grant.id && loadedProjectId && loadedProjectId === grant.projectId">
|
||||
<cnsl-form-field class="form-field" appearance="outline">
|
||||
<mat-select [(ngModel)]="grant.roleKeysList" multiple
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
|
||||
(selectionChange)="updateRoles(grant, $event)">
|
||||
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
|
||||
{{role.key}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
|
||||
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE" mat-icon-button
|
||||
(click)="grantToEdit=''">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="(context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE) && loadedId && loadedId === grant.projectId && grantToEdit === grant.id">
|
||||
<cnsl-form-field class="form-field" appearance="outline">
|
||||
<mat-select [(ngModel)]="grant.roleKeysList" multiple
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.id] : []) | hasRole | async))"
|
||||
(selectionChange)="updateRoles(grant, $event)">
|
||||
<mat-option *ngFor="let role of grantRoleOptions" [value]="role">
|
||||
{{role}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
<button matTooltip="{{'ACTIONS.CLOSE' | translate}}"
|
||||
*ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE" mat-icon-button
|
||||
(click)="grantToEdit=''">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
<div class="flex-row">
|
||||
<cnsl-project-role-chip [roleName]="role" *ngFor="let role of grant.roleKeysList">{{ role }}
|
||||
</cnsl-project-role-chip>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"> </th>
|
||||
<td mat-cell class="user-tr-actions" *matCellDef="let grant; let i = index">
|
||||
<cnsl-table-actions [hasActions]="true">
|
||||
<button actions matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" (click)="deleteGrant(grant)"
|
||||
mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
|
||||
<button menuActions mat-menu-item [routerLink]="['/users', grant.userId]">
|
||||
{{'ACTIONS.TABLE.SHOWUSER' | translate : ({value: grant.displayName})}}
|
||||
</button>
|
||||
</cnsl-table-actions>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
<tr (click)="openEditDialog(grant)" class="highlight pointer" mat-row
|
||||
*matRowDef="let grant; columns: displayedColumns;">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div *ngIf="(dataSource.loading$ | async) === false && !dataSource?.totalResult" class="no-content-row">
|
||||
<i class="las la-exclamation"></i>
|
||||
<span>{{'GRANTS.EMPTY' | translate}}</span>
|
||||
</div>
|
||||
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp"
|
||||
[length]="dataSource.totalResult" [pageSize]="INITIAL_PAGE_SIZE" [length]="dataSource.totalResult"
|
||||
[pageSizeOptions]="[2, 3, 25, 50, 100, 250]" (page)="changePage($event)">
|
||||
</cnsl-paginator>
|
||||
</div>
|
||||
</cnsl-refresh-table>
|
||||
<div *ngIf="(dataSource.loading$ | async) === false && !dataSource?.totalResult" class="no-content-row">
|
||||
<i class="las la-exclamation"></i>
|
||||
<span>{{'GRANTS.EMPTY' | translate}}</span>
|
||||
</div>
|
||||
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [length]="dataSource.totalResult"
|
||||
[pageSize]="INITIAL_PAGE_SIZE" [length]="dataSource.totalResult" [pageSizeOptions]="[2, 3, 25, 50, 100, 250]"
|
||||
(page)="changePage($event)">
|
||||
</cnsl-paginator>
|
||||
|
||||
<ng-template #templateRef let-key="key">
|
||||
<button class="search-button" mat-icon-button (click)="setFilter(key)">
|
||||
<mat-icon class="icon" *ngIf="this.userGrantListSearchKey !== key">search</mat-icon>
|
||||
<mat-icon class="icon" *ngIf="this.userGrantListSearchKey === key">search_off</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
</cnsl-refresh-table>
|
||||
@@ -1,96 +1,73 @@
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
.table,
|
||||
.paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
@mixin user-grants-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
|
||||
td,
|
||||
th {
|
||||
padding-left: .5rem;
|
||||
padding-right: .5rem;
|
||||
.user-grants-table-left-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: .5rem;
|
||||
.user-grant-type-button {
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
padding: 0.75rem 0;
|
||||
opacity: 0.6;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
color: map-get($foreground, base);
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
th {
|
||||
.search-button {
|
||||
visibility: hidden;
|
||||
width: 30px;
|
||||
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.search-active {
|
||||
.search-button {
|
||||
visibility: visible;
|
||||
}
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-roles {
|
||||
font-size: 13px;
|
||||
color: var(--grey);
|
||||
.no-break {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
th {
|
||||
.search-button {
|
||||
visibility: hidden;
|
||||
width: 30px;
|
||||
|
||||
.icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.search-active {
|
||||
.search-button {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-width: 400px;
|
||||
|
||||
.role {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: .25rem;
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
justify-items: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: .5rem 0;
|
||||
display: block;
|
||||
max-width: 120px;
|
||||
box-sizing: border-box;
|
||||
|
||||
i {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.row-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.form-field {
|
||||
flex: 1;
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
@@ -101,14 +78,17 @@
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.user-grants-tr-actions {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.date-block {
|
||||
margin: .5rem 0;
|
||||
margin: 0.5rem 0;
|
||||
display: block;
|
||||
min-width: 120px;
|
||||
|
||||
.date-sub {
|
||||
font-size: 13px;
|
||||
color: var(--grey);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatInput } from '@angular/material/input';
|
||||
import { MatSelectChange } from '@angular/material/select';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { enterAnimations } from 'src/app/animations';
|
||||
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { Role } from 'src/app/proto/generated/zitadel/project_pb';
|
||||
import {
|
||||
UserGrant,
|
||||
UserGrantDisplayNameQuery,
|
||||
UserGrantOrgNameQuery,
|
||||
UserGrantProjectNameQuery,
|
||||
UserGrantQuery,
|
||||
UserGrantRoleKeyQuery,
|
||||
} from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { Type, UserGrant, UserGrantQuery } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { ActionKeysType } from '../action-keys/action-keys.component';
|
||||
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
|
||||
import { UserGrantRoleDialogComponent } from '../user-grant-role-dialog/user-grant-role-dialog.component';
|
||||
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
|
||||
import { UserGrantContext, UserGrantsDataSource } from './user-grants-datasource';
|
||||
|
||||
export enum UserGrantListSearchKey {
|
||||
@@ -31,9 +28,7 @@ export enum UserGrantListSearchKey {
|
||||
selector: 'cnsl-user-grants',
|
||||
templateUrl: './user-grants.component.html',
|
||||
styleUrls: ['./user-grants.component.scss'],
|
||||
animations: [
|
||||
enterAnimations,
|
||||
],
|
||||
animations: [enterAnimations],
|
||||
})
|
||||
export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
public userGrantListSearchKey: UserGrantListSearchKey | undefined = undefined;
|
||||
@@ -56,7 +51,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
@Input() grantId: string = '';
|
||||
@ViewChild('input') public filter!: MatInput;
|
||||
|
||||
public grantRoleOptions: string[] = [];
|
||||
public projectRoleOptions: Role.AsObject[] = [];
|
||||
public routerLink: any = [''];
|
||||
|
||||
@@ -65,17 +59,30 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
public grantToEdit: string = '';
|
||||
|
||||
public UserGrantContext: any = UserGrantContext;
|
||||
public Type: any = Type;
|
||||
public ActionKeysType: any = ActionKeysType;
|
||||
@Input() public type: Type | undefined = undefined;
|
||||
|
||||
public filterOpen: boolean = false;
|
||||
|
||||
constructor(
|
||||
private userService: ManagementService,
|
||||
private mgmtService: ManagementService,
|
||||
private toast: ToastService,
|
||||
) { }
|
||||
private dialog: MatDialog,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
@Input() public displayedColumns: string[] = ['select',
|
||||
@Input() public displayedColumns: string[] = [
|
||||
'select',
|
||||
'user',
|
||||
'org',
|
||||
'projectId', 'creationDate', 'changeDate', 'roleNamesList'];
|
||||
'projectId',
|
||||
'type',
|
||||
'creationDate',
|
||||
'changeDate',
|
||||
'roleNamesList',
|
||||
'actions',
|
||||
];
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.dataSource = new UserGrantsDataSource(this.userService);
|
||||
@@ -83,14 +90,14 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
switch (this.context) {
|
||||
case UserGrantContext.OWNED_PROJECT:
|
||||
if (this.projectId) {
|
||||
this.getProjectRoleOptions(this.projectId);
|
||||
// this.getProjectRoleOptions(this.projectId);
|
||||
this.routerLink = ['/grant-create', 'project', this.projectId];
|
||||
}
|
||||
break;
|
||||
case UserGrantContext.GRANTED_PROJECT:
|
||||
if (this.grantId) {
|
||||
this.routerLink = ['/grant-create', 'project', this.projectId, 'grant', this.grantId];
|
||||
this.getGrantRoleOptions(this.grantId, this.projectId);
|
||||
// this.getGrantRoleOptions(this.grantId, this.projectId);
|
||||
}
|
||||
break;
|
||||
case UserGrantContext.USER:
|
||||
@@ -102,50 +109,34 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.routerLink = ['/grant-create'];
|
||||
}
|
||||
|
||||
this.loadGrantsPage();
|
||||
this.loadGrantsPage(this.type);
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
this.paginator.page
|
||||
.pipe(
|
||||
tap(() => this.loadGrantsPage()),
|
||||
)
|
||||
.subscribe();
|
||||
this.paginator.page.pipe(tap(() => this.loadGrantsPage(this.type))).subscribe();
|
||||
}
|
||||
|
||||
private loadGrantsPage(filterValue?: string): void {
|
||||
let queries: UserGrantQuery[] = [];
|
||||
if (this.userGrantListSearchKey !== undefined && filterValue) {
|
||||
const query = new UserGrantQuery();
|
||||
switch (this.userGrantListSearchKey) {
|
||||
case UserGrantListSearchKey.DISPLAY_NAME:
|
||||
const ugDnQ = new UserGrantDisplayNameQuery();
|
||||
ugDnQ.setDisplayName(filterValue);
|
||||
ugDnQ.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
query.setDisplayNameQuery(ugDnQ);
|
||||
break;
|
||||
case UserGrantListSearchKey.ORG_NAME:
|
||||
const ugOnQ = new UserGrantOrgNameQuery();
|
||||
ugOnQ.setOrgName(filterValue);
|
||||
ugOnQ.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
query.setOrgNameQuery(ugOnQ);
|
||||
break;
|
||||
case UserGrantListSearchKey.PROJECT_NAME:
|
||||
const ugPnQ = new UserGrantProjectNameQuery();
|
||||
ugPnQ.setProjectName(filterValue);
|
||||
ugPnQ.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
query.setProjectNameQuery(ugPnQ);
|
||||
break;
|
||||
case UserGrantListSearchKey.ROLE_KEY:
|
||||
const ugRkQ = new UserGrantRoleKeyQuery();
|
||||
ugRkQ.setRoleKey(filterValue);
|
||||
ugRkQ.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
query.setRoleKeyQuery(ugRkQ);
|
||||
break;
|
||||
public setType(type: Type | undefined): void {
|
||||
this.type = type;
|
||||
this.loadGrantsPage(type);
|
||||
}
|
||||
|
||||
}
|
||||
queries = [query];
|
||||
public getType(grant: UserGrant.AsObject): string {
|
||||
if (grant.projectGrantId) {
|
||||
return 'Project Grant';
|
||||
} else if (grant.projectId) {
|
||||
return 'Project';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public gotoCreateLink(rL: any): void {
|
||||
this.router.navigate(rL);
|
||||
}
|
||||
|
||||
private loadGrantsPage(type: Type | undefined, searchQueries?: UserGrantQuery[]): void {
|
||||
let queries: UserGrantQuery[] = [];
|
||||
|
||||
this.dataSource.loadGrants(
|
||||
this.context,
|
||||
@@ -156,7 +147,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
grantId: this.grantId,
|
||||
userId: this.userId,
|
||||
},
|
||||
queries,
|
||||
searchQueries ? [...searchQueries, ...queries] : queries,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,62 +158,111 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
public masterToggle(): void {
|
||||
this.isAllSelected() ?
|
||||
this.selection.clear() :
|
||||
this.dataSource.grantsSubject.value.forEach(row => this.selection.select(row));
|
||||
this.isAllSelected()
|
||||
? this.selection.clear()
|
||||
: this.dataSource.grantsSubject.value.forEach((row) => this.selection.select(row));
|
||||
}
|
||||
|
||||
public loadGrantOptions(grant: UserGrant.AsObject): void {
|
||||
this.grantToEdit = grant.id;
|
||||
if (grant.projectGrantId && grant.projectId) {
|
||||
this.getGrantRoleOptions(grant.projectGrantId, grant.projectId);
|
||||
} else if (grant.projectId) {
|
||||
this.getProjectRoleOptions(grant.projectId);
|
||||
}
|
||||
}
|
||||
|
||||
private getGrantRoleOptions(id: string, projectId: string): void {
|
||||
this.mgmtService.getGrantedProjectByID(projectId, id).then(resp => {
|
||||
if (resp.grantedProject) {
|
||||
this.loadedId = projectId;
|
||||
this.grantRoleOptions = resp.grantedProject?.grantedRoleKeysList;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.grantToEdit = '';
|
||||
this.toast.showError(error);
|
||||
public openEditDialog(grant: UserGrant.AsObject): void {
|
||||
const dialogRef = this.dialog.open(UserGrantRoleDialogComponent, {
|
||||
data: {
|
||||
projectId: grant.projectId,
|
||||
grantId: grant.projectGrantId,
|
||||
selectedRoleKeysList: grant.roleKeysList,
|
||||
i18nTitle: 'GRANTS.EDIT.TITLE',
|
||||
},
|
||||
width: '600px',
|
||||
});
|
||||
}
|
||||
|
||||
private getProjectRoleOptions(projectId: string): void {
|
||||
this.mgmtService.listProjectRoles(projectId, 100, 0).then(resp => {
|
||||
this.loadedProjectId = projectId;
|
||||
this.projectRoleOptions = resp.resultList;
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp && resp.roles) {
|
||||
this.userService
|
||||
.updateUserGrant(grant.id, grant.userId, resp.roles)
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
|
||||
grant.roleKeysList = resp.roles;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateRoles(grant: UserGrant.AsObject, selectionChange: MatSelectChange): void {
|
||||
this.userService.updateUserGrant(grant.id, grant.userId, selectionChange.value)
|
||||
this.userService
|
||||
.updateUserGrant(grant.id, grant.userId, selectionChange.value)
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
|
||||
}).catch(error => {
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
deleteGrantSelection(): void {
|
||||
this.userService.bulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.BULKREMOVED', true);
|
||||
const data = this.dataSource.grantsSubject.getValue();
|
||||
this.selection.selected.forEach((item) => {
|
||||
const index = data.findIndex(i => i.id === item.id);
|
||||
if (index > -1) {
|
||||
data.splice(index, 1);
|
||||
this.dataSource.grantsSubject.next(data);
|
||||
}
|
||||
});
|
||||
this.selection.clear();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
public deleteGrant(grant: UserGrant.AsObject): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'GRANTS.DIALOG.DELETE_TITLE',
|
||||
descriptionKey: 'GRANTS.DIALOG.DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.userService
|
||||
.removeUserGrant(grant.id, grant.userId)
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.REMOVED', true);
|
||||
const data = this.dataSource.grantsSubject.getValue();
|
||||
|
||||
const index = data.findIndex((i) => i.id === grant.id);
|
||||
if (index > -1) {
|
||||
data.splice(index, 1);
|
||||
this.dataSource.grantsSubject.next(data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public deleteGrantSelection(): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'GRANTS.DIALOG.BULK_DELETE_TITLE',
|
||||
descriptionKey: 'GRANTS.DIALOG.BULK_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
this.userService
|
||||
.bulkRemoveUserGrant(this.selection.selected.map((grant) => grant.id))
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.BULKREMOVED', true);
|
||||
const data = this.dataSource.grantsSubject.getValue();
|
||||
this.selection.selected.forEach((item) => {
|
||||
const index = data.findIndex((i) => i.id === item.id);
|
||||
if (index > -1) {
|
||||
data.splice(index, 1);
|
||||
this.dataSource.grantsSubject.next(data);
|
||||
}
|
||||
});
|
||||
this.selection.clear();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -239,11 +279,9 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
);
|
||||
}
|
||||
|
||||
public applyFilter(event: Event): void {
|
||||
public applySearchQuery(searchQueries?: UserGrantQuery[]): void {
|
||||
this.selection.clear();
|
||||
const filterValue = (event.target as HTMLInputElement).value;
|
||||
|
||||
this.loadGrantsPage(filterValue);
|
||||
this.loadGrantsPage(this.type, searchQueries);
|
||||
}
|
||||
|
||||
public setFilter(key: UserGrantListSearchKey): void {
|
||||
@@ -257,7 +295,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.userGrantListSearchKey = key;
|
||||
} else {
|
||||
this.userGrantListSearchKey = undefined;
|
||||
this.loadGrantsPage();
|
||||
this.loadGrantsPage(this.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,37 +16,46 @@ import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/local
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||
|
||||
import { InputModule } from '../../modules/input/input.module';
|
||||
import { ActionKeysModule } from '../action-keys/action-keys.module';
|
||||
import { AvatarModule } from '../avatar/avatar.module';
|
||||
import { FilterUserGrantsModule } from '../filter-user-grants/filter-user-grants.module';
|
||||
import { PaginatorModule } from '../paginator/paginator.module';
|
||||
import { ProjectRoleChipModule } from '../project-role-chip/project-role-chip.module';
|
||||
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
|
||||
import { TableActionsModule } from '../table-actions/table-actions.module';
|
||||
import { UserGrantRoleDialogModule } from '../user-grant-role-dialog/user-grant-role-dialog.module';
|
||||
import { WarnDialogModule } from '../warn-dialog/warn-dialog.module';
|
||||
import { UserGrantsComponent } from './user-grants.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [UserGrantsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
AvatarModule,
|
||||
MatButtonModule,
|
||||
HasRoleModule,
|
||||
MatTableModule,
|
||||
PaginatorModule,
|
||||
MatIconModule,
|
||||
RouterModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
MatSelectModule,
|
||||
TranslateModule,
|
||||
HasRolePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
RefreshTableModule,
|
||||
LocalizedDatePipeModule,
|
||||
InputModule,
|
||||
],
|
||||
exports: [
|
||||
UserGrantsComponent,
|
||||
],
|
||||
declarations: [UserGrantsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
AvatarModule,
|
||||
MatButtonModule,
|
||||
HasRoleModule,
|
||||
MatTableModule,
|
||||
PaginatorModule,
|
||||
MatIconModule,
|
||||
RouterModule,
|
||||
ProjectRoleChipModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
TableActionsModule,
|
||||
UserGrantRoleDialogModule,
|
||||
MatSelectModule,
|
||||
TranslateModule,
|
||||
ActionKeysModule,
|
||||
FilterUserGrantsModule,
|
||||
HasRolePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
RefreshTableModule,
|
||||
LocalizedDatePipeModule,
|
||||
InputModule,
|
||||
WarnDialogModule,
|
||||
],
|
||||
exports: [UserGrantsComponent],
|
||||
})
|
||||
export class UserGrantsModule { }
|
||||
export class UserGrantsModule {}
|
||||
|
||||
Reference in New Issue
Block a user