mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
feat(console): authorizations from user context, add grants to auth user view, fix app saving (#895)
* user grant on project * grant in auth user, enable creation, fix inv regex * use autocomplete solutions, section for usre ctx * user grant create for user context * fix edit from table * fix create context * fix authorization to write * grant overview component * fix user grants without context * lint * turn table highlighting off, rm logs * fix app name saving * fix table refresh for project grants * translate toast * i18n * lint * Update console/src/assets/i18n/de.json Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
parent
afa38aa2c2
commit
78e5c26015
@ -65,6 +65,14 @@ const routes: Routes = [
|
||||
roles: ['org.read'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'grants',
|
||||
loadChildren: () => import('./pages/grants/grants.module').then(m => m.GrantsModule),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['user.grant.read'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'grant-create',
|
||||
canActivate: [AuthGuard],
|
||||
@ -87,6 +95,24 @@ const routes: Routes = [
|
||||
roles: ['user.grant.write'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'user/:userid',
|
||||
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
|
||||
.then(m => m.UserGrantCreateModule),
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
roles: ['user.grant.write'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
|
||||
.then(m => m.UserGrantCreateModule),
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
roles: ['user.grant.write'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -138,6 +138,21 @@
|
||||
<span class="label">{{ 'MENU.MACHINEUSERS' | translate }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.grant.read(:[0-9]*)?']">
|
||||
<div @navitem class="divider">
|
||||
<div class="line"></div>
|
||||
<span class="label">
|
||||
{{ 'MENU.GRANTSECTION' | translate }}</span>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
|
||||
<a @navitem class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/grants']"
|
||||
[routerLinkActiveOptions]="{ exact: true }">
|
||||
<i class="icon las la-shield-alt"></i>
|
||||
<span class="label">{{ 'MENU.GRANTS' | translate }}</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
|
@ -212,7 +212,7 @@
|
||||
padding: 2px 1rem;
|
||||
border-radius: 50vw;
|
||||
color: var(--grey);
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.line {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||
emitRefreshOnPreviousRoute="/iam/idp/create" [timestamp]="idpResult?.viewTimestamp" [selection]="selection">
|
||||
[emitRefreshOnPreviousRoutes]="['/iam/idp/create']" [timestamp]="idpResult?.viewTimestamp" [selection]="selection">
|
||||
<ng-template appHasRole [appHasRole]="['iam.write']" actions>
|
||||
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button"
|
||||
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<app-refresh-table *ngIf="projectId" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult"
|
||||
emitRefreshOnPreviousRoute="/projects/{{projectId}}/roles/create" [selection]="selection"
|
||||
[emitRefreshOnPreviousRoutes]="['/projects/'+projectId+'/roles/create']" [selection]="selection"
|
||||
[loading]="dataSource?.loading$ | async" [timestamp]="dataSource?.viewTimestamp">
|
||||
<ng-template appHasRole [appHasRole]="['project.role.delete', 'project.role.delete:' + projectId]" actions>
|
||||
<button color="warn" class="icon-button" [disabled]="disabled"
|
||||
|
@ -33,7 +33,7 @@ export class RefreshTableComponent implements OnInit {
|
||||
@Input() public dataSize: number = 0;
|
||||
@Input() public emitRefreshAfterTimeoutInMs: number = 0;
|
||||
@Input() public loading: boolean = false;
|
||||
@Input() public emitRefreshOnPreviousRoute: string = '';
|
||||
@Input() public emitRefreshOnPreviousRoutes: string[] = [];
|
||||
@Output() public refreshed: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
constructor(private refreshService: RefreshService) { }
|
||||
@ -45,7 +45,8 @@ export class RefreshTableComponent implements OnInit {
|
||||
}, this.emitRefreshAfterTimeoutInMs);
|
||||
}
|
||||
|
||||
if (this.emitRefreshOnPreviousRoute && this.refreshService.previousUrls.includes(this.emitRefreshOnPreviousRoute)) {
|
||||
if (this.emitRefreshOnPreviousRoutes.length && this.refreshService.previousUrls
|
||||
.some(url => this.emitRefreshOnPreviousRoutes.includes(url))) {
|
||||
setTimeout(() => {
|
||||
console.log('refresh now');
|
||||
this.emitRefresh();
|
||||
|
@ -1,5 +1,5 @@
|
||||
<form>
|
||||
<mat-form-field *ngIf="target == UserTarget.SELF" appearance="outline" class="full-width">
|
||||
<mat-form-field *ngIf="target && target == UserTarget.SELF" appearance="outline" class="full-width">
|
||||
<mat-label>Organizations User Loginname</mat-label>
|
||||
|
||||
<input matInput *ngIf="singleOutput" type="text" placeholder="Search for the user loginname" #usernameInput
|
||||
@ -28,12 +28,21 @@
|
||||
<small>{{user.preferredLoginName}}</small>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-hint class="target-desc">
|
||||
{{'USER.TARGET.SELF'| translate}}
|
||||
<a (click)="changeTarget()">{{'USER.TARGET.CLICKHERE' | translate}}</a>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="target == UserTarget.EXTERNAL" class="line">
|
||||
<div *ngIf="target && target == UserTarget.EXTERNAL" class="line">
|
||||
<mat-form-field class="form-field" appearance="outline">
|
||||
<mat-label>Global User Loginname</mat-label>
|
||||
<input matInput type="text" [formControl]="globalLoginNameControl" />
|
||||
<mat-hint class="target-desc">
|
||||
{{(target == UserTarget.SELF ? 'USER.TARGET.SELF' : 'USER.TARGET.EXTERNAL') | translate}}
|
||||
<a (click)="changeTarget()">{{'USER.TARGET.CLICKHERE' | translate}}</a>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<button color="primary" mat-icon-button (click)="getGlobalUser()">
|
||||
@ -41,17 +50,25 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="target == UserTarget.EXTERNAL && users.length > 0">
|
||||
<div class="found" *ngIf="target == UserTarget.EXTERNAL && users.length > 0">
|
||||
<span class="found-label">{{'USER.SEARCH.FOUND' | translate}}:</span>
|
||||
<div class="found-user-row" *ngFor="let user of users; index as i">
|
||||
<div class="circle">
|
||||
<app-avatar
|
||||
*ngIf="user.human && user.human.displayName && user.human?.firstName && user.human?.lastName; else cog"
|
||||
class="avatar" [name]="user.human.displayName" [size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon">
|
||||
<i class="las la-user-cog"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<span>{{user.preferredLoginName}}</span>
|
||||
<button mat-icon-button>
|
||||
<button mat-icon-button color="warn">
|
||||
<i class="las la-minus-circle" (click)="users.splice(i, 1)"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="target-desc">{{(target == UserTarget.SELF ? 'USER.TARGET.SELF' : 'USER.TARGET.EXTERNAL') | translate}}
|
||||
<a (click)="changeTarget()">{{'USER.TARGET.CLICKHERE' | translate}}</a>
|
||||
</p>
|
||||
</form>
|
@ -3,11 +3,6 @@
|
||||
}
|
||||
|
||||
.target-desc {
|
||||
color: var(--grey);
|
||||
font-size: .8rem;
|
||||
margin: 0;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
a {
|
||||
color: #4072b4;
|
||||
|
||||
@ -37,12 +32,35 @@
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.found {
|
||||
margin: .5rem 0;
|
||||
background: #4072b410;
|
||||
border-radius: .5rem;
|
||||
padding: .5rem;
|
||||
|
||||
.found-user-row {
|
||||
padding: .5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 56px;
|
||||
|
||||
button {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
button {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.found-label {
|
||||
font-size: .9rem;
|
||||
color: var(--grey);
|
||||
}
|
||||
}
|
||||
|
||||
.circle {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
@ -1,5 +1,15 @@
|
||||
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import {
|
||||
AfterContentChecked,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatChipInputEvent } from '@angular/material/chips';
|
||||
@ -19,7 +29,7 @@ export enum UserTarget {
|
||||
templateUrl: './search-user-autocomplete.component.html',
|
||||
styleUrls: ['./search-user-autocomplete.component.scss'],
|
||||
})
|
||||
export class SearchUserAutocompleteComponent {
|
||||
export class SearchUserAutocompleteComponent implements OnInit, AfterContentChecked {
|
||||
public selectable: boolean = true;
|
||||
public removable: boolean = true;
|
||||
public addOnBlur: boolean = true;
|
||||
@ -32,7 +42,7 @@ export class SearchUserAutocompleteComponent {
|
||||
@Input() public users: Array<UserView.AsObject> = [];
|
||||
public filteredUsers: Array<UserView.AsObject> = [];
|
||||
public isLoading: boolean = false;
|
||||
public target: UserTarget = UserTarget.SELF;
|
||||
@Input() public target: UserTarget = UserTarget.SELF;
|
||||
public hint: string = '';
|
||||
public UserTarget: any = UserTarget;
|
||||
@ViewChild('usernameInput') public usernameInput!: ElementRef<HTMLInputElement>;
|
||||
@ -41,8 +51,19 @@ export class SearchUserAutocompleteComponent {
|
||||
@Input() public singleOutput: boolean = false;
|
||||
|
||||
private unsubscribed$: Subject<void> = new Subject();
|
||||
constructor(private userService: ManagementService, private toast: ToastService) {
|
||||
this.getFilteredResults();
|
||||
constructor(private userService: ManagementService, private toast: ToastService, private cdref: ChangeDetectorRef) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
if (this.target === UserTarget.EXTERNAL) {
|
||||
this.filteredUsers = [];
|
||||
this.unsubscribed$.next(); // clear old subscription
|
||||
} else if (this.target === UserTarget.SELF) {
|
||||
this.getFilteredResults(); // new subscription
|
||||
}
|
||||
}
|
||||
|
||||
public ngAfterContentChecked(): void {
|
||||
this.cdref.detectChanges();
|
||||
}
|
||||
|
||||
private getFilteredResults(): void {
|
||||
@ -143,10 +164,11 @@ export class SearchUserAutocompleteComponent {
|
||||
|
||||
public getGlobalUser(): void {
|
||||
this.userService.GetUserByLoginNameGlobal(this.globalLoginNameControl.value).then(user => {
|
||||
this.users = [user.toObject()];
|
||||
if (this.singleOutput) {
|
||||
this.users = [user.toObject()];
|
||||
this.selectionChanged.emit(this.users[0]);
|
||||
} else {
|
||||
this.users.push(user.toObject());
|
||||
this.selectionChanged.emit(this.users);
|
||||
}
|
||||
}).catch(error => {
|
||||
|
@ -11,6 +11,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AvatarModule } from '../avatar/avatar.module';
|
||||
import { SearchUserAutocompleteComponent } from './search-user-autocomplete.component';
|
||||
|
||||
|
||||
@ -29,6 +30,7 @@ import { SearchUserAutocompleteComponent } from './search-user-autocomplete.comp
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
MatSelectModule,
|
||||
AvatarModule,
|
||||
],
|
||||
exports: [SearchUserAutocompleteComponent],
|
||||
})
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
|
||||
export enum UserGrantContext {
|
||||
// AUTHUSER = 'authuser',
|
||||
NONE = 'none',
|
||||
USER = 'user',
|
||||
OWNED_PROJECT = 'owned',
|
||||
GRANTED_PROJECT = 'granted',
|
||||
@ -42,14 +42,13 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
},
|
||||
queries?: UserGrantSearchQuery[],
|
||||
): void {
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
switch (context) {
|
||||
case UserGrantContext.USER:
|
||||
if (data && data.userId) {
|
||||
this.loadingSubject.next(true);
|
||||
const userfilter = new UserGrantSearchQuery();
|
||||
userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID);
|
||||
userfilter.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
|
||||
userfilter.setValue(data.userId);
|
||||
if (queries) {
|
||||
queries.push(userfilter);
|
||||
@ -57,7 +56,7 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
queries = [userfilter];
|
||||
}
|
||||
|
||||
const promise = this.userService.SearchUserGrants(10, 0, queries);
|
||||
const promise = this.userService.SearchUserGrants(pageSize, pageSize * pageIndex, queries);
|
||||
this.loadResponse(promise);
|
||||
}
|
||||
break;
|
||||
@ -66,6 +65,7 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
this.loadingSubject.next(true);
|
||||
const projectfilter = new UserGrantSearchQuery();
|
||||
projectfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID);
|
||||
projectfilter.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
|
||||
projectfilter.setValue(data.projectId);
|
||||
if (queries) {
|
||||
queries.push(projectfilter);
|
||||
@ -73,7 +73,7 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
queries = [projectfilter];
|
||||
}
|
||||
|
||||
const promise1 = this.userService.SearchUserGrants(10, 0, queries);
|
||||
const promise1 = this.userService.SearchUserGrants(pageSize, pageSize * pageIndex, queries);
|
||||
this.loadResponse(promise1);
|
||||
}
|
||||
break;
|
||||
@ -97,10 +97,15 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
||||
queries = [projectfilter, grantquery];
|
||||
}
|
||||
|
||||
const promise2 = this.userService.SearchUserGrants(10, 0, queries);
|
||||
const promise2 = this.userService.SearchUserGrants(pageSize, pageSize * pageIndex, queries);
|
||||
this.loadResponse(promise2);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.loadingSubject.next(true);
|
||||
const promise3 = this.userService.SearchUserGrants(pageSize, pageSize * pageIndex, []);
|
||||
this.loadResponse(promise3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
<app-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()"
|
||||
[emitRefreshOnPreviousRoute]="refreshOnPreviousRoute" [timestamp]="dataSource?.viewTimestamp"
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
|
||||
[dataSize]="dataSource?.totalResult" [selection]="selection">
|
||||
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
|
||||
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && allowDelete">
|
||||
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete == false">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<a *ngIf="allowWrite && context !== UserGrantContext.USER" matTooltip="{{'GRANTS.ADD' | translate}}" actions
|
||||
color="primary" color="primary" mat-raised-button [routerLink]="routerLink">
|
||||
<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 }}
|
||||
</a>
|
||||
|
||||
@ -14,75 +14,81 @@
|
||||
<table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox [disabled]="allowWrite == false" color="primary"
|
||||
(change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelected()"
|
||||
<mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td class="selection" mat-cell *matCellDef="let row">
|
||||
<mat-checkbox [disabled]="allowWrite == false" color="primary" (click)="$event.stopPropagation()"
|
||||
<mat-checkbox
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + row?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + row?.grantId] : []) | hasRole | async))"
|
||||
color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
<app-avatar *ngIf="row && row?.displayName && row.firstName && row.lastName" class="avatar"
|
||||
[name]="row.displayName" [size]="32">
|
||||
<app-avatar
|
||||
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
|
||||
class="avatar" [name]="row.displayName" [size]="32">
|
||||
</app-avatar>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="user">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant?.displayName}}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="org">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.GRANTEDORGDOMAIN' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.orgName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="projectId">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.projectName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CREATIONDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
<td mat-cell *matCellDef="let grant">
|
||||
{{grant.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="roleNamesList">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let grant; let i = index">
|
||||
<ng-container *ngIf="context === UserGrantContext.USER">
|
||||
<span class="no-roles"
|
||||
*ngIf="grant.roleKeysList?.length === 0">{{'PROJECT.GRANT.NOROLES' | translate}}</span>
|
||||
<span
|
||||
*ngFor="let role of grant.roleKeysList">{{ (role.length>8)? (role | slice:0:8)+'..':(role) }}</span>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="context === UserGrantContext.OWNED_PROJECT">
|
||||
<ng-container *ngIf="loadedProjectId !== grant.projectId">
|
||||
<span class="role app-label"
|
||||
*ngFor="let role of grant.roleKeysList">{{ (role.length>6)? (role | slice:0:6)+'..':(role) }}</span>
|
||||
<button mat-icon-button (click)="getProjectRoleOptions(grant.projectId)"
|
||||
<ng-container *ngIf="context === UserGrantContext.USER || context === UserGrantContext.NONE">
|
||||
<ng-container
|
||||
*ngIf="(grant.grantId && loadedGrantId !== grant.grantId) || (loadedProjectId !== grant.projectId)">
|
||||
<div class="flex-row">
|
||||
<span class="role" *ngFor="let role of grant.roleKeysList">{{ role }}</span>
|
||||
<button mat-stroked-button
|
||||
*ngIf="grant.grantId ? loadedGrantId !== grant.grantId : loadedProjectId !== grant.projectId"
|
||||
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + grant?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + grant?.grantId] : []) | hasRole | async))"
|
||||
(click)="grant.grantId ? getGrantRoleOptions(grant.grantId, grant.projectId) : getProjectRoleOptions(grant.projectId)"
|
||||
matTooltip="{{'ACTIONS.CHANGE' | translate}}">
|
||||
<i class="las la-edit"></i>
|
||||
{{'ACTIONS.EDIT' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="context === UserGrantContext.OWNED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE">
|
||||
<mat-form-field class="form-field" appearance="outline"
|
||||
*ngIf="loadedProjectId === grant.projectId">
|
||||
*ngIf="loadedProjectId && loadedProjectId === grant.projectId">
|
||||
<mat-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="grant.roleKeysList" multiple [disabled]="allowWrite == false"
|
||||
<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?.grantId] : []) | hasRole | async))"
|
||||
(selectionChange)="updateRoles(grant, $event)">
|
||||
<mat-option *ngFor="let role of projectRoleOptions" [value]="role.key">
|
||||
{{role.key}}
|
||||
@ -91,10 +97,13 @@
|
||||
</mat-form-field>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="context === UserGrantContext.GRANTED_PROJECT">
|
||||
<mat-form-field class="form-field" appearance="outline">
|
||||
<ng-container
|
||||
*ngIf="context === UserGrantContext.GRANTED_PROJECT || context === UserGrantContext.USER || context === UserGrantContext.NONE">
|
||||
<mat-form-field class="form-field" appearance="outline"
|
||||
*ngIf="loadedGrantId && loadedGrantId === grant.grantId">
|
||||
<mat-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="grant.roleKeysList" multiple [disabled]="allowWrite == false"
|
||||
<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?.grantId] : []) | hasRole | async))"
|
||||
(selectionChange)="updateRoles(grant, $event)">
|
||||
<mat-option *ngFor="let role of grantRoleOptions" [value]="role">
|
||||
{{role}}
|
||||
@ -106,11 +115,11 @@
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator class="paginator" #paginator [length]="dataSource.totalResult" [pageSize]="50"
|
||||
<mat-paginator class="paginator" #paginator [length]="dataSource.totalResult" [pageSize]="INITIAL_PAGE_SIZE"
|
||||
[length]="dataSource.totalResult" [pageSizeOptions]="[2, 3, 25, 50, 100, 250]" (page)="changePage($event)">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
@ -25,20 +25,38 @@
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pointer {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.no-roles {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
|
||||
.role {
|
||||
display: block;
|
||||
margin: .25rem;
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: .5rem 0;
|
||||
max-width: 120px;
|
||||
|
||||
i {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
@ -4,14 +4,7 @@ import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSelectChange } from '@angular/material/select';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import {
|
||||
ProjectRoleView,
|
||||
SearchMethod,
|
||||
UserGrant,
|
||||
UserGrantSearchKey,
|
||||
UserGrantSearchQuery,
|
||||
UserGrantView,
|
||||
} from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectRoleView, UserGrant, UserGrantView } from 'src/app/proto/generated/management_pb';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@ -23,8 +16,9 @@ import { UserGrantContext, UserGrantsDataSource } from './user-grants-datasource
|
||||
styleUrls: ['./user-grants.component.scss'],
|
||||
})
|
||||
export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
@Input() context: UserGrantContext = UserGrantContext.USER;
|
||||
@Input() refreshOnPreviousRoute: string = '';
|
||||
public INITIAL_PAGE_SIZE: number = 50;
|
||||
@Input() context: UserGrantContext = UserGrantContext.NONE;
|
||||
@Input() refreshOnPreviousRoutes: string[] = [];
|
||||
public grants: UserGrantView.AsObject[] = [];
|
||||
|
||||
public dataSource!: UserGrantsDataSource;
|
||||
@ -32,8 +26,8 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ViewChild(MatTable) public table!: MatTable<UserGrantView.AsObject>;
|
||||
|
||||
@Input() allowWrite: boolean = false;
|
||||
@Input() allowDelete: boolean = false;
|
||||
@Input() disableWrite: boolean = false;
|
||||
@Input() disableDelete: boolean = false;
|
||||
|
||||
@Input() userId: string = '';
|
||||
@Input() projectId: string = '';
|
||||
@ -80,15 +74,11 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.routerLink = ['/grant-create', 'user', this.userId];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
case UserGrantContext.NONE:
|
||||
this.routerLink = ['/grant-create'];
|
||||
}
|
||||
|
||||
this.dataSource.loadGrants(this.context, 0, 25, {
|
||||
projectId: this.projectId,
|
||||
grantId: this.grantId,
|
||||
userId: this.userId,
|
||||
});
|
||||
this.loadGrantsPage();
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
@ -102,11 +92,12 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
private loadGrantsPage(): void {
|
||||
this.dataSource.loadGrants(
|
||||
this.context,
|
||||
this.paginator.pageIndex,
|
||||
this.paginator.pageSize,
|
||||
this.paginator?.pageIndex ?? 0,
|
||||
this.paginator?.pageSize ?? this.INITIAL_PAGE_SIZE,
|
||||
{
|
||||
projectId: this.projectId,
|
||||
grantId: this.grantId,
|
||||
userId: this.userId,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -125,7 +116,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
public getGrantRoleOptions(grantId: string, projectId: string): void {
|
||||
this.mgmtService.GetGrantedProjectByID(projectId, grantId).then(resp => {
|
||||
this.loadedGrantId = projectId;
|
||||
this.loadedGrantId = grantId;
|
||||
this.grantRoleOptions = resp.toObject().roleKeysList;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
@ -140,9 +131,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
updateRoles(grant: UserGrant.AsObject, selectionChange: MatSelectChange): void {
|
||||
switch (this.context) {
|
||||
case UserGrantContext.OWNED_PROJECT:
|
||||
if (grant.id && grant.projectId) {
|
||||
this.userService.UpdateUserGrant(grant.id, grant.userId, selectionChange.value)
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
|
||||
@ -150,24 +138,6 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case UserGrantContext.GRANTED_PROJECT:
|
||||
if (this.grantId && this.projectId) {
|
||||
const projectQuery: UserGrantSearchQuery = new UserGrantSearchQuery();
|
||||
projectQuery.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID);
|
||||
projectQuery.setMethod(SearchMethod.SEARCHMETHOD_EQUALS);
|
||||
projectQuery.setValue(this.projectId);
|
||||
this.userService.UpdateUserGrant(
|
||||
grant.id, grant.userId, selectionChange.value)
|
||||
.then(() => {
|
||||
this.toast.showInfo('GRANTS.TOAST.UPDATED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deleteGrantSelection(): void {
|
||||
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
|
||||
|
17
console/src/app/pages/grants/grants-routing.module.ts
Normal file
17
console/src/app/pages/grants/grants-routing.module.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { GrantsComponent } from './grants.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: GrantsComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class GrantsRoutingModule { }
|
9
console/src/app/pages/grants/grants.component.html
Normal file
9
console/src/app/pages/grants/grants.component.html
Normal file
@ -0,0 +1,9 @@
|
||||
<div class="max-width-container">
|
||||
<h2>{{ 'GRANTS.TITLE' | translate }}</h2>
|
||||
<p class="desc">{{'GRANTS.DESC' | translate }}</p>
|
||||
<app-user-grants
|
||||
[displayedColumns]="['select', 'user', 'org', 'projectId', 'creationDate', 'changeDate', 'roleNamesList']"
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</div>
|
4
console/src/app/pages/grants/grants.component.scss
Normal file
4
console/src/app/pages/grants/grants.component.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.desc {
|
||||
color: var(--grey);
|
||||
margin-bottom: 2rem;
|
||||
}
|
25
console/src/app/pages/grants/grants.component.spec.ts
Normal file
25
console/src/app/pages/grants/grants.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GrantsComponent } from './grants.component';
|
||||
|
||||
describe('GrantsComponent', () => {
|
||||
let component: GrantsComponent;
|
||||
let fixture: ComponentFixture<GrantsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GrantsComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GrantsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
8
console/src/app/pages/grants/grants.component.ts
Normal file
8
console/src/app/pages/grants/grants.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-grants',
|
||||
templateUrl: './grants.component.html',
|
||||
styleUrls: ['./grants.component.scss'],
|
||||
})
|
||||
export class GrantsComponent { }
|
24
console/src/app/pages/grants/grants.module.ts
Normal file
24
console/src/app/pages/grants/grants.module.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
|
||||
import { GrantsRoutingModule } from './grants-routing.module';
|
||||
import { GrantsComponent } from './grants.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
GrantsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GrantsRoutingModule,
|
||||
UserGrantsModule,
|
||||
TranslateModule,
|
||||
HasRoleModule,
|
||||
HasRolePipeModule,
|
||||
],
|
||||
})
|
||||
export class GrantsModule { }
|
@ -12,7 +12,7 @@
|
||||
<span *ngIf="errorMessage" class="err-container">{{errorMessage}}</span>
|
||||
|
||||
<app-card title="{{ 'APP.PAGES.DETAIL.TITLE' | translate }}" *ngIf="app">
|
||||
<form [formGroup]="appNameForm" (ngSubmit)="saveOIDCApp()">
|
||||
<form [formGroup]="appNameForm" (ngSubmit)="saveApp()">
|
||||
<div class="content">
|
||||
<mat-button-toggle-group formControlName="state" class="toggle" (change)="changeState($event)">
|
||||
<mat-button-toggle [value]="AppState.APPSTATE_INACTIVE"
|
||||
|
@ -216,6 +216,22 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public saveApp(): void {
|
||||
if (this.appNameForm.valid) {
|
||||
this.app.name = this.name?.value;
|
||||
|
||||
this.mgmtService
|
||||
.UpdateApplication(this.projectId, this.app.id, this.name?.value)
|
||||
.then(() => {
|
||||
this.toast.showInfo('APP.TOAST.OIDCUPDATED', true);
|
||||
})
|
||||
.catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public saveOIDCApp(): void {
|
||||
if (this.appNameForm.valid) {
|
||||
this.app.name = this.name?.value;
|
||||
|
@ -18,9 +18,9 @@
|
||||
<app-user-grants *ngIf="projectId && grantId" [context]="UserGrantContext.GRANTED_PROJECT"
|
||||
[projectId]="projectId" [grantId]="grantId"
|
||||
[displayedColumns]="['select','user', 'projectId', 'creationDate', 'changeDate', 'roleNamesList']"
|
||||
[allowWrite]="['user.grant.write$','user.grant.write:'+grantId] | hasRole | async"
|
||||
[allowDelete]="['user.grant.delete$','user.grant.delete:'+grantId] | hasRole | async"
|
||||
refreshOnPreviousRoute="/grant-create/project/{{projectId}}/grant/{{grantId}}">
|
||||
[disableWrite]="(['user.grant.write$','user.grant.write:'+grantId] | hasRole | async) == false"
|
||||
[disableDelete]="(['user.grant.delete$','user.grant.delete:'+grantId] | hasRole | async) == false"
|
||||
refreshOnPreviousRoutes="['/grant-create/project/{{projectId}}/grant/{{grantId}}']">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
</ng-template>
|
||||
|
@ -68,7 +68,8 @@
|
||||
[appHasRole]="['project.grant.read:' + project.projectId, 'project.grant.read']">
|
||||
<app-card title="{{ 'PROJECT.GRANT.TITLE' | translate }}"
|
||||
description="{{ 'PROJECT.GRANT.DESCRIPTION' | translate }}">
|
||||
<app-project-grants refreshOnPreviousRoute="/projects/{{projectId}}/grants/create"
|
||||
<app-project-grants
|
||||
[refreshOnPreviousRoutes]="['/projects/'+projectId+'/grants/create','/projects/'+projectId+'/roles/create']"
|
||||
[disabled]="((['project.grant.write$', 'project.grant.write:'+ project.projectId]| hasRole | async))== false"
|
||||
[projectId]="projectId">
|
||||
</app-project-grants>
|
||||
@ -97,9 +98,9 @@
|
||||
<app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
|
||||
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [context]="UserGrantContext.OWNED_PROJECT" [projectId]="projectId"
|
||||
refreshOnPreviousRoute="/grant-create/project/{{projectId}}"
|
||||
[allowWrite]="(['user.grant.write$', 'user.grant.write:'+projectId] | hasRole) | async"
|
||||
[allowDelete]="(['user.grant.delete$','user.grant.delete:'+projectId] | hasRole) | async">
|
||||
[refreshOnPreviousRoutes]="['/grant-create/project/'+projectId]"
|
||||
[disableWrite]="((['user.grant.write$', 'user.grant.write:'+projectId] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$','user.grant.delete:'+projectId] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
</ng-template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<app-refresh-table [loading]="dataSource?.loading$ | async" *ngIf="projectId" (refreshed)="loadGrantsPage()"
|
||||
[dataSize]="dataSource.totalResult" [selection]="selection" [timestamp]="dataSource?.viewTimestamp"
|
||||
[emitRefreshOnPreviousRoute]="refreshOnPreviousRoute">
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" (refreshed)="getRoleOptions(projectId)">
|
||||
<ng-template appHasRole [appHasRole]="['project.grant.member.delete:'+projectId, 'project.grant.member.delete']"
|
||||
actions>
|
||||
<button (click)="deleteSelectedGrants()" [disabled]="disabled" mat-icon-button *ngIf="selection.hasValue()"
|
||||
|
@ -24,7 +24,7 @@ import { ProjectGrantsDataSource } from './project-grants-datasource';
|
||||
],
|
||||
})
|
||||
export class ProjectGrantsComponent implements OnInit, AfterViewInit {
|
||||
@Input() refreshOnPreviousRoute: string = '';
|
||||
@Input() refreshOnPreviousRoutes: string[] = [];
|
||||
@Input() public projectId: string = '';
|
||||
@Input() public disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ -50,7 +50,6 @@ export class ProjectGrantsComponent implements OnInit, AfterViewInit {
|
||||
tap(() => this.loadGrantsPage()),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
}
|
||||
|
||||
public loadGrantsPage(pageIndex?: number, pageSize?: number): void {
|
||||
|
@ -16,33 +16,36 @@
|
||||
{{'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate}}
|
||||
</p>
|
||||
|
||||
<ng-container *ngIf="context && context == UserGrantContext.USER">
|
||||
<ng-container>
|
||||
<h1>{{'PROJECT.GRANT.CREATE.SEL_USER' | translate}}</h1>
|
||||
|
||||
<app-search-user-autocomplete class="block" singleOutput="true" [users]="user ? [user] : []"
|
||||
(selectionChanged)="selectUser($event)"
|
||||
[target]="context === UserGrantContext.USER ? UserTarget.EXTERNAL : UserTarget.SELF">
|
||||
</app-search-user-autocomplete>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="context && (context == UserGrantContext.USER || context == UserGrantContext.NONE)">
|
||||
<h1>{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</h1>
|
||||
|
||||
<app-search-project-autocomplete class="block" singleOutput="true"
|
||||
(selectionChanged)="selectProject($event)">
|
||||
</app-search-project-autocomplete>
|
||||
</ng-container>
|
||||
|
||||
<ng-container
|
||||
*ngIf="context && (context == UserGrantContext.GRANTED_PROJECT || context == UserGrantContext.OWNED_PROJECT)">
|
||||
<h1>{{'PROJECT.GRANT.CREATE.SEL_USER' | translate}}</h1>
|
||||
|
||||
<app-search-user-autocomplete class="block" singleOutput="true" (selectionChanged)="selectUser($event)">
|
||||
</app-search-user-autocomplete>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentCreateStep === STEPS">
|
||||
<h1>{{'PROJECT.GRANT.CREATE.SEL_ROLES' | translate}}</h1>
|
||||
<ng-container *ngIf="context === UserGrantContext.OWNED_PROJECT && projectId">
|
||||
<ng-container
|
||||
*ngIf="(projectId && (context === UserGrantContext.OWNED_PROJECT || ((context === UserGrantContext.USER || context === UserGrantContext.NONE) && $any(project)?.id == undefined)))">
|
||||
<app-card>
|
||||
<app-project-roles (changedSelection)="selectRoles($event)" [projectId]="projectId">
|
||||
</app-project-roles>
|
||||
</app-card>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="context === UserGrantContext.GRANTED_PROJECT && grantRolesKeyList">
|
||||
<ng-container
|
||||
*ngIf="(context === UserGrantContext.GRANTED_PROJECT || ((context === UserGrantContext.USER || context === UserGrantContext.NONE) && $any(project)?.id)) && grantRolesKeyList">
|
||||
<mat-form-field class="form-field" appearance="outline">
|
||||
<mat-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</mat-label>
|
||||
<mat-select multiple (selectionChange)="rolesList = $event.value">
|
||||
@ -56,9 +59,8 @@
|
||||
|
||||
<div class="btn-container">
|
||||
<ng-container *ngIf="currentCreateStep === 1">
|
||||
<button
|
||||
[disabled]="!org || ((context == UserGrantContext.GRANTED_PROJECT || context == UserGrantContext.OWNED_PROJECT) && !projectId) || (context == UserGrantContext.USER && !userId)"
|
||||
(click)="next()" color="primary" mat-raised-button class="big-button" cdkFocusInitial>
|
||||
<button [disabled]="!org || !projectId || !userId" (click)="next()" color="primary" mat-raised-button
|
||||
class="big-button" cdkFocusInitial>
|
||||
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||
</button>
|
||||
</ng-container>
|
||||
|
@ -64,3 +64,13 @@
|
||||
padding: .5rem 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sa-icon {
|
||||
display: block;
|
||||
width: 32px;
|
||||
margin: 0 .5rem;
|
||||
|
||||
i {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { Location } from '@angular/common';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UserTarget } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.component';
|
||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||
import { Org } from 'src/app/proto/generated/auth_pb';
|
||||
import { ProjectGrantView, ProjectRole, ProjectView, UserGrant, UserView } from 'src/app/proto/generated/management_pb';
|
||||
@ -19,7 +20,10 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
|
||||
public org!: Org.AsObject;
|
||||
public userId: string = '';
|
||||
|
||||
public projectId: string = '';
|
||||
public project!: ProjectGrantView.AsObject | ProjectView.AsObject;
|
||||
|
||||
public grantId: string = '';
|
||||
public rolesList: string[] = [];
|
||||
|
||||
@ -33,6 +37,12 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
public UserGrantContext: any = UserGrantContext;
|
||||
|
||||
public grantRolesKeyList: string[] = [];
|
||||
|
||||
public user!: UserView.AsObject;
|
||||
public UserTarget: any = UserTarget;
|
||||
|
||||
public ProjectGrantView: any = ProjectGrantView;
|
||||
public ProjectView: any = ProjectView;
|
||||
constructor(
|
||||
private userService: ManagementService,
|
||||
private toast: ToastService,
|
||||
@ -42,8 +52,8 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
private mgmtService: ManagementService,
|
||||
) {
|
||||
this.subscription = this.route.params.subscribe((params: Params) => {
|
||||
const { context, projectid, grantid, userid } = params;
|
||||
this.context = context;
|
||||
const { projectid, grantid, userid } = params;
|
||||
this.context = UserGrantContext.NONE;
|
||||
|
||||
this.projectId = projectid;
|
||||
this.grantId = grantid;
|
||||
@ -58,6 +68,14 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
}).catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (this.userId) {
|
||||
this.context = UserGrantContext.USER;
|
||||
this.mgmtService.GetUserByID(this.userId).then(resp => {
|
||||
this.user = resp.toObject();
|
||||
console.log(this.user);
|
||||
}).catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -97,12 +115,52 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
break;
|
||||
case UserGrantContext.USER:
|
||||
let grantId;
|
||||
|
||||
if ((this.project as ProjectGrantView.AsObject)?.id) {
|
||||
grantId = (this.project as ProjectGrantView.AsObject).id;
|
||||
}
|
||||
|
||||
this.userService.CreateUserGrant(
|
||||
this.userId,
|
||||
this.rolesList,
|
||||
this.project.projectId,
|
||||
grantId,
|
||||
).then((data: UserGrant) => {
|
||||
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
|
||||
this.close();
|
||||
}).catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
break;
|
||||
case UserGrantContext.NONE:
|
||||
let tempGrantId;
|
||||
|
||||
if ((this.project as ProjectGrantView.AsObject)?.id) {
|
||||
tempGrantId = (this.project as ProjectGrantView.AsObject).id;
|
||||
}
|
||||
|
||||
this.userService.CreateUserGrant(
|
||||
this.userId,
|
||||
this.rolesList,
|
||||
this.project.projectId,
|
||||
tempGrantId,
|
||||
).then((data: UserGrant) => {
|
||||
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
|
||||
this.close();
|
||||
}).catch((error: any) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public selectProject(project: ProjectView.AsObject | ProjectGrantView.AsObject | any): void {
|
||||
this.project = project;
|
||||
this.projectId = project.projectId;
|
||||
this.grantRolesKeyList = project.roleKeysList ?? [];
|
||||
}
|
||||
|
||||
public selectUser(user: UserView.AsObject): void {
|
||||
|
@ -47,6 +47,15 @@
|
||||
</app-card>
|
||||
|
||||
<app-auth-user-mfa *ngIf="user" #mfaComponent></app-auth-user-mfa>
|
||||
|
||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList']"
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
<div *ngIf="user" class="side" metainfo>
|
||||
|
@ -2,6 +2,7 @@ import { Component, OnDestroy } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||
import { Gender, UserAddress, UserEmail, UserPhone, UserProfile, UserView } from 'src/app/proto/generated/auth_pb';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@ -26,6 +27,8 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
public ChangeType: any = ChangeType;
|
||||
public userLoginMustBeDomain: boolean = false;
|
||||
|
||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
private toast: ToastService,
|
||||
|
@ -3,7 +3,7 @@
|
||||
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
|
||||
|
||||
<span>*********</span>
|
||||
<div>
|
||||
<div class="overflow">
|
||||
<ng-content select="[phoneAction]"></ng-content>
|
||||
<a [disabled]="!canWrite" [routerLink]="['password']" mat-icon-button>
|
||||
<mat-icon class="icon">chevron_right</mat-icon>
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
.label {
|
||||
font-size: .9rem;
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
@ -45,3 +45,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow: auto;
|
||||
}
|
||||
|
@ -83,10 +83,10 @@
|
||||
|
||||
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
|
||||
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
|
||||
<app-user-grants [userId]="user.id"
|
||||
[allowWrite]="['user.grant.write$'+ 'user.grant.write:'+user?.id] | hasRole | async"
|
||||
<app-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
|
||||
[displayedColumns]="['select', 'projectId', 'creationDate', 'changeDate', 'roleNamesList']"
|
||||
[allowDelete]="['user.grant.delete$', 'user.grant.delete'+ user?.id] | hasRole | async">
|
||||
[disableWrite]="((['user.grant.write$'] | hasRole) | async) == false"
|
||||
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) == false">
|
||||
</app-user-grants>
|
||||
</app-card>
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import {
|
||||
Gender,
|
||||
@ -37,6 +38,7 @@ export class UserDetailComponent implements OnInit, OnDestroy {
|
||||
|
||||
public UserState: any = UserState;
|
||||
public copied: string = '';
|
||||
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
|
||||
|
||||
constructor(
|
||||
public translate: TranslateService,
|
||||
|
@ -1,6 +1,6 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||
[timestamp]="userResult?.viewTimestamp" [selection]="selection"
|
||||
[emitRefreshOnPreviousRoute]="refreshOnPreviousRoute">
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
|
||||
<mat-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
|
||||
<mat-label>{{'USER.PAGES.FILTER' | translate}}</mat-label>
|
||||
<input matInput (keyup)="applyFilter($event)"
|
||||
|
@ -27,7 +27,7 @@ export class UserTableComponent implements OnInit {
|
||||
public userSearchKey: UserSearchKey | undefined = undefined;
|
||||
public UserType: any = UserType;
|
||||
@Input() userType: UserType = UserType.HUMAN;
|
||||
@Input() refreshOnPreviousRoute: string = '';
|
||||
@Input() refreshOnPreviousRoutes: string[] = [];
|
||||
@Input() disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ViewChild('input') public filter!: MatInput;
|
||||
|
@ -1,19 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ToastService {
|
||||
constructor(private dialog: MatDialog,
|
||||
constructor(
|
||||
private snackBar: MatSnackBar,
|
||||
private translate: TranslateService,
|
||||
private authService: AuthenticationService,
|
||||
) { }
|
||||
|
||||
public showInfo(message: string, i18nkey: boolean = false): void {
|
||||
@ -21,17 +18,23 @@ export class ToastService {
|
||||
this.translate
|
||||
.get(message)
|
||||
.subscribe(data => {
|
||||
this.showMessage(data, 'close');
|
||||
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
|
||||
this.showMessage(data, value);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.showMessage(message, 'close');
|
||||
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
|
||||
this.showMessage(message, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public showError(grpcError: any): void {
|
||||
const { message, code, metadata } = grpcError;
|
||||
if (code !== 16) {
|
||||
this.showMessage(decodeURI(message), 'close');
|
||||
this.translate.get('ACTIONS.CLOSE').pipe(take(1)).subscribe(value => {
|
||||
this.showMessage(decodeURI(message), value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,9 @@
|
||||
"LOGOUT": "Alle Benutzer abmelden",
|
||||
"NEWORG":"Neue Organisation",
|
||||
"IAMADMIN":"Du bist ein IAM-Administrator. Beachte, dass Du erhöhte Rechte besitzt.",
|
||||
"SHOWORGS":"Alle Organisationen anzeigen"
|
||||
"SHOWORGS":"Alle Organisationen anzeigen",
|
||||
"GRANTSECTION":"Berechtigungssektion",
|
||||
"GRANTS":"Berechtigungen"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"SAVE": "Speichern",
|
||||
@ -870,6 +872,8 @@
|
||||
},
|
||||
"ROLESLABEL":"Rollen",
|
||||
"GRANTS": {
|
||||
"TITLE":"Berechtigungen",
|
||||
"DESC":"Hier kannst Du die Berechtigungen Deiner Organisation verwalten.",
|
||||
"DELETE":"Berechtigung löschen",
|
||||
"ADD":"Berechtigung erstellen",
|
||||
"ADD_BTN":"Neu",
|
||||
|
@ -35,7 +35,9 @@
|
||||
"LOGOUT": "Logout All Users",
|
||||
"NEWORG":"New Organisation",
|
||||
"IAMADMIN":"You are an IAM Administrator. Note that you have extended permissions.",
|
||||
"SHOWORGS":"Show All Organisations"
|
||||
"SHOWORGS":"Show All Organisations",
|
||||
"GRANTSECTION":"Authorization Section",
|
||||
"GRANTS":"Authorizations"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"SAVE": "Save",
|
||||
@ -870,6 +872,8 @@
|
||||
},
|
||||
"ROLESLABEL":"Roles",
|
||||
"GRANTS": {
|
||||
"TITLE":"Authorisations",
|
||||
"DESC":"Here you can manage authorizations of your organization users.",
|
||||
"DELETE":"Delete Authorisation",
|
||||
"ADD":"Create Authorisation",
|
||||
"ADD_BTN":"New",
|
||||
|
@ -31,9 +31,9 @@
|
||||
}
|
||||
|
||||
tr {
|
||||
&.highlight {
|
||||
cursor: pointer;
|
||||
|
||||
&.highlight {
|
||||
&:hover {
|
||||
td {
|
||||
background-color: var(--table-row-back); // rgba($inv-color, .05);
|
||||
|
Loading…
Reference in New Issue
Block a user