fix(console): distinct user grant searches and creates, project grant member edit, import cleanup (#342)

* project grant member edit

* project grant member dialog, import cleanup

* readd project roles

* user login-methods cleanup

* fix sw config, user grant context

* delete user grants, context for creation, search

* contributor box shadow

* password to detail view

* user detail notification

* lint
This commit is contained in:
Max Peintner 2020-07-06 16:17:06 +02:00 committed by GitHub
parent c8cdbe136c
commit 9935784461
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 910 additions and 573 deletions

View File

@ -55,11 +55,28 @@ const routes: Routes = [
roles: ['org.read'], roles: ['org.read'],
}, },
}, },
{
path: 'grant-create/project/:projectid/grant/:grantid',
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
.then(m => m.UserGrantCreateModule),
},
{
path: 'grant-create/project/:projectid',
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
.then(m => m.UserGrantCreateModule),
},
{
path: 'grant-create/user/:userid',
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
.then(m => m.UserGrantCreateModule),
},
{ {
path: 'grant-create', path: 'grant-create',
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module') loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')
.then(m => m.UserGrantCreateModule), .then(m => m.UserGrantCreateModule),
}, },
{ {
path: 'signedout', path: 'signedout',
loadChildren: () => import('./pages/signedout/signedout.module').then(m => m.SignedoutModule), loadChildren: () => import('./pages/signedout/signedout.module').then(m => m.SignedoutModule),

View File

@ -103,7 +103,7 @@ export let authConfig = {
MatMenuModule, MatMenuModule,
MatSnackBarModule, MatSnackBarModule,
AvatarModule, AvatarModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-config.json', { enabled: environment.production }),
], ],
providers: [ providers: [
ThemeService, ThemeService,

View File

@ -11,8 +11,8 @@
<div class="l-accounts"> <div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.userName)"> <a class="row" *ngFor="let user of users" (click)="selectAccount(user.userName)">
<app-avatar *ngIf="user && (user.displayName || (user.firstName && user.lastName))" class="small-avatar" <app-avatar *ngIf="user && user.displayName" class="small-avatar"
[name]="user.displayName ? user.displayName : (user.firstName + ' '+ user.lastName)" [size]="32"> [name]="user.displayName ? user.displayName : ''" [size]="32">
</app-avatar> </app-avatar>
<div class="col"> <div class="col">

View File

@ -17,10 +17,7 @@ export class AccountsCardComponent implements OnInit {
@Output() public close: EventEmitter<void> = new EventEmitter(); @Output() public close: EventEmitter<void> = new EventEmitter();
public users: UserSessionView.AsObject[] = []; public users: UserSessionView.AsObject[] = [];
public loadingUsers: boolean = false; public loadingUsers: boolean = false;
constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) { } constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) {
public ngOnInit(): void {
this.loadingUsers = true;
this.userService.getMyUserSessions().then(sessions => { this.userService.getMyUserSessions().then(sessions => {
this.users = sessions.toObject().userSessionsList; this.users = sessions.toObject().userSessionsList;
@ -33,6 +30,10 @@ export class AccountsCardComponent implements OnInit {
}); });
} }
public ngOnInit(): void {
this.loadingUsers = true;
}
public editUserProfile(): void { public editUserProfile(): void {
this.router.navigate(['user/me']); this.router.navigate(['user/me']);
this.close.emit(); this.close.emit();

View File

@ -23,12 +23,10 @@ export class ChangesComponent implements OnInit {
@Input() public sortDirectionAsc: boolean = true; @Input() public sortDirectionAsc: boolean = true;
public bottom: boolean = false; public bottom: boolean = false;
// Source data
private _done: BehaviorSubject<any> = new BehaviorSubject(false); private _done: BehaviorSubject<any> = new BehaviorSubject(false);
private _loading: BehaviorSubject<any> = new BehaviorSubject(false); private _loading: BehaviorSubject<any> = new BehaviorSubject(false);
private _data: BehaviorSubject<any> = new BehaviorSubject([]); private _data: BehaviorSubject<any> = new BehaviorSubject([]);
// Observable data
loading: Observable<boolean> = this._loading.asObservable(); loading: Observable<boolean> = this._loading.asObservable();
public data!: Observable<Change.AsObject[]>; public data!: Observable<Change.AsObject[]>;
public changes!: Changes.AsObject; public changes!: Changes.AsObject;

View File

@ -19,6 +19,4 @@ export class MetaLayoutComponent {
.pipe(map(result => { .pipe(map(result => {
return result.matches; return result.matches;
})); }));
} }

View File

@ -7,10 +7,8 @@
<ng-container *ngFor="let member of membersSubject | async"> <ng-container *ngFor="let member of membersSubject | async">
<div (click)="showDetail()" class="avatar-circle" <div (click)="showDetail()" class="avatar-circle"
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}"> matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}">
<app-avatar <app-avatar *ngIf="member && member.firstName && member.lastName; else thumbavatar"
*ngIf="member && (member.displayName || (member.firstName && member.lastName)); else thumbavatar" class="avatar dontcloseonclick" [name]="member.firstName + ' '+ member.lastName"
class="avatar dontcloseonclick"
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
[size]="32"> [size]="32">
</app-avatar> </app-avatar>
<ng-template #thumbavatar> <ng-template #thumbavatar>

View File

@ -30,11 +30,16 @@
align-items: center; align-items: center;
.avatar-circle { .avatar-circle {
float: left; float: left;
margin: 0 8px 0 -15px; margin: 0 8px 0 -15px;
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 50%; border-radius: 50%;
&:not(:first-child) {
-webkit-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
-moz-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
}
} }
.add-img { .add-img {

View File

@ -38,7 +38,6 @@ export class ProjectMembersComponent implements AfterViewInit {
private route: ActivatedRoute) { private route: ActivatedRoute) {
this.route.data.pipe(take(1)).subscribe(data => { this.route.data.pipe(take(1)).subscribe(data => {
this.projectType = data.type; this.projectType = data.type;
console.log(data);
this.getRoleOptions(); this.getRoleOptions();
@ -156,6 +155,5 @@ export class ProjectMembersComponent implements AfterViewInit {
}).catch(error => { }).catch(error => {
this.toast.showError(error.message); this.toast.showError(error.message);
}); });
} }
} }

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
@ -28,6 +29,7 @@ import { ProjectRolesComponent } from './project-roles.component';
HasRoleModule, HasRoleModule,
MatTableModule, MatTableModule,
MatPaginatorModule, MatPaginatorModule,
MatDialogModule,
MatFormFieldModule, MatFormFieldModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,

View File

@ -1,9 +1,21 @@
import { DataSource } from '@angular/cdk/collections'; import { DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, from, Observable, of } from 'rxjs'; import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { UserGrant, UserGrantSearchKey, UserGrantSearchQuery } from 'src/app/proto/generated/management_pb'; import {
UserGrant,
UserGrantSearchKey,
UserGrantSearchQuery,
UserGrantSearchResponse,
} from 'src/app/proto/generated/management_pb';
import { MgmtUserService } from 'src/app/services/mgmt-user.service'; import { MgmtUserService } from 'src/app/services/mgmt-user.service';
export enum UserGrantContext {
// AUTHUSER = 'authuser',
USER = 'user',
OWNED_PROJECT = 'owned',
GRANTED_PROJECT = 'granted',
}
export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> { export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
public totalResult: number = 0; public totalResult: number = 0;
public grantsSubject: BehaviorSubject<UserGrant.AsObject[]> = new BehaviorSubject<UserGrant.AsObject[]>([]); public grantsSubject: BehaviorSubject<UserGrant.AsObject[]> = new BehaviorSubject<UserGrant.AsObject[]>([]);
@ -14,17 +26,54 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
super(); super();
} }
public loadGrants(filter: UserGrantSearchKey, userId: string, pageIndex: number, pageSize: number): void { public loadGrants(
context: UserGrantContext,
pageIndex: number,
pageSize: number,
data: {
projectId?: string;
grantId?: string;
userId?: string;
},
queries?: UserGrantSearchQuery[],
): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);
const query = new UserGrantSearchQuery(); switch (context) {
query.setKey(filter); case UserGrantContext.USER:
query.setValue(userId); if (data && data.userId) {
const userfilter = new UserGrantSearchQuery();
userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID);
userfilter.setValue(data.userId);
if (queries) {
queries.push(userfilter);
} else {
queries = [userfilter];
}
const queries: UserGrantSearchQuery[] = [query]; const promise = this.userService.SearchUserGrants(10, 0, queries);
from(this.userService.SearchUserGrants(10, 0, queries)).pipe( this.loadResponse(promise);
}
break;
case UserGrantContext.OWNED_PROJECT:
if (data && data.projectId) {
const promise1 = this.userService.SearchProjectUserGrants(data.projectId, 10, 0, queries);
this.loadResponse(promise1);
}
break;
case UserGrantContext.GRANTED_PROJECT:
if (data && data.grantId) {
const promise2 = this.userService.SearchProjectGrantUserGrants(data.grantId, 10, 0, queries);
this.loadResponse(promise2);
}
break;
}
}
private loadResponse(promise: Promise<UserGrantSearchResponse>): void {
from(promise).pipe(
map(resp => { map(resp => {
this.totalResult = resp.toObject().totalResult; this.totalResult = resp.toObject().totalResult;
console.log(resp.toObject().resultList); console.log(resp.toObject().resultList);

View File

@ -11,11 +11,11 @@
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button <button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button
*ngIf="selection.hasValue() && allowDelete"> (click)="deleteGrantSelection()" *ngIf="selection.hasValue() && allowDelete">
<i class="las la-trash"></i> <i class="las la-trash"></i>
</button> </button>
<a *ngIf="allowCreate" matTooltip="{{'GRANTS.ADD' | translate}}" color="primary" class="add-button" color="primary" <a *ngIf="allowCreate" matTooltip="{{'GRANTS.ADD' | translate}}" color="primary" class="add-button" color="primary"
mat-raised-button [routerLink]="['/grant-create', {filter: filter, filterValue: filterValue}]"> mat-raised-button [routerLink]="routerLink">
<mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'GRANTS.ADD_BTN' | translate }}
</a> </a>
</div> </div>
@ -86,7 +86,7 @@
</button> </button>
</ng-container> </ng-container>
<ng-template #showOptions> <ng-template #showOptions>
<mat-form-field class="form-field" appearance="outline" *ngIf="filterValue"> <mat-form-field class="form-field" appearance="outline" *ngIf="projectId">
<mat-label>{{ 'PROJECT.GRANT.TITLE' | translate }}</mat-label> <mat-label>{{ 'PROJECT.GRANT.TITLE' | translate }}</mat-label>
<mat-select [(ngModel)]="grant.roleKeysList" multiple [disabled]="allowCreate == false" <mat-select [(ngModel)]="grant.roleKeysList" multiple [disabled]="allowCreate == false"
(selectionChange)="updateRoles(grant, $event)"> (selectionChange)="updateRoles(grant, $event)">

View File

@ -4,12 +4,12 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSelectChange } from '@angular/material/select'; import { MatSelectChange } from '@angular/material/select';
import { MatTable } from '@angular/material/table'; import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { ProjectGrant, ProjectRoleView, UserGrant, UserGrantSearchKey } from 'src/app/proto/generated/management_pb'; import { ProjectGrant, ProjectRoleView, UserGrant } from 'src/app/proto/generated/management_pb';
import { MgmtUserService } from 'src/app/services/mgmt-user.service'; import { MgmtUserService } from 'src/app/services/mgmt-user.service';
import { ProjectService } from 'src/app/services/project.service'; import { ProjectService } from 'src/app/services/project.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { UserGrantsDataSource } from './user-grants-datasource'; import { UserGrantContext, UserGrantsDataSource } from './user-grants-datasource';
@Component({ @Component({
selector: 'app-user-grants', selector: 'app-user-grants',
@ -17,8 +17,9 @@ import { UserGrantsDataSource } from './user-grants-datasource';
styleUrls: ['./user-grants.component.scss'], styleUrls: ['./user-grants.component.scss'],
}) })
export class UserGrantsComponent implements OnInit, AfterViewInit { export class UserGrantsComponent implements OnInit, AfterViewInit {
@Input() filterValue: string = ''; // @Input() filterValue: string = '';
@Input() filter: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID; // @Input() filter: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID;
@Input() context: UserGrantContext = UserGrantContext.USER;
public grants: UserGrant.AsObject[] = []; public grants: UserGrant.AsObject[] = [];
public dataSource!: UserGrantsDataSource; public dataSource!: UserGrantsDataSource;
@ -29,7 +30,12 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
@Input() allowCreate: boolean = false; @Input() allowCreate: boolean = false;
@Input() allowDelete: boolean = false; @Input() allowDelete: boolean = false;
@Input() userId: string = '';
@Input() projectId: string = '';
@Input() grantId: string = '';
public roleOptions: ProjectRoleView.AsObject[] = []; public roleOptions: ProjectRoleView.AsObject[] = [];
public routerLink: any = [''];
constructor( constructor(
private userService: MgmtUserService, private userService: MgmtUserService,
@ -44,11 +50,35 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
public ngOnInit(): void { public ngOnInit(): void {
this.dataSource = new UserGrantsDataSource(this.userService); this.dataSource = new UserGrantsDataSource(this.userService);
this.dataSource.loadGrants(this.filter, this.filterValue, 0, 25); const data = {
projectId: this.projectId,
grantId: this.grantId,
userId: this.userId,
};
console.log(this.context);
if (this.filter === UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID) { switch (this.context) {
this.getRoleOptions(this.filterValue); case UserGrantContext.OWNED_PROJECT:
if (this.projectId) {
this.getRoleOptions(this.projectId);
this.routerLink = ['/grant-create', 'project', this.projectId];
}
break;
case UserGrantContext.GRANTED_PROJECT:
if (data && data.grantId) {
this.routerLink = ['/grant-create', 'project', this.projectId, 'grant', this.grantId];
}
break;
case UserGrantContext.USER:
if (this.userId) {
this.routerLink = ['/grant-create', 'user', this.userId];
}
break;
default:
this.routerLink = ['/grant-create'];
} }
console.log(data);
this.dataSource.loadGrants(this.context, 0, 25, data);
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
@ -61,10 +91,13 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
private loadGrantsPage(): void { private loadGrantsPage(): void {
this.dataSource.loadGrants( this.dataSource.loadGrants(
this.filter, this.context,
this.filterValue,
this.paginator.pageIndex, this.paginator.pageIndex,
this.paginator.pageSize, this.paginator.pageSize,
{
projectId: this.projectId,
grantId: this.grantId,
},
); );
} }
@ -81,7 +114,7 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
} }
public loadRoleOptions(projectId: string): void { public loadRoleOptions(projectId: string): void {
if (this.filter === UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID) { if (this.context === UserGrantContext.USER) {
this.getRoleOptions(projectId); this.getRoleOptions(projectId);
} }
} }
@ -103,4 +136,13 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
this.toast.showError(error.message); this.toast.showError(error.message);
}); });
} }
deleteGrantSelection(): void {
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
this.toast.showInfo('Grants deleted');
this.loadGrantsPage();
}).catch(error => {
this.toast.showError(error.message);
});
}
} }

View File

@ -15,7 +15,7 @@
<!-- <ng-template appHasRole [appHasRole]="['project.grant.user.grant.read']"> --> <!-- <ng-template appHasRole [appHasRole]="['project.grant.user.grant.read']"> -->
<app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}" <app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}"> description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<app-user-grants [filter]="userGrantSearchKey" [filterValue]="project.projectId" <app-user-grants [context]="userGrantContext" [projectId]="projectId" [grantId]="grantId"
[allowCreate]="['project.grant.user.grant.write'] | hasRole" [allowCreate]="['project.grant.user.grant.write'] | hasRole"
[allowDelete]="['project.grant.user.grant.delete'] | hasRole"> [allowDelete]="['project.grant.user.grant.delete'] | hasRole">
</app-user-grants> </app-user-grants>

View File

@ -6,6 +6,7 @@ import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { import {
Application, Application,
ApplicationSearchResponse, ApplicationSearchResponse,
@ -58,6 +59,7 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
public isZitadel: boolean = false; public isZitadel: boolean = false;
public userGrantContext: UserGrantContext = UserGrantContext.GRANTED_PROJECT;
public userGrantSearchKey: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID; public userGrantSearchKey: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID;
constructor( constructor(

View File

@ -3,10 +3,7 @@ import { HttpClient } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
@ -29,10 +26,7 @@ import { ChangesModule } from '../../modules/changes/changes.module';
import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module'; import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module'; import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module';
import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module'; import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module';
import { SearchUserAutocompleteModule } from '../../modules/search-user-autocomplete/search-user-autocomplete.module';
import { PipesModule } from '../../pipes/pipes.module'; import { PipesModule } from '../../pipes/pipes.module';
import { OrgContributorsModule } from '../orgs/org-contributors/org-contributors.module';
import { UserListModule } from '../user-list/user-list.module';
import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component'; import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
import { GrantedProjectGridComponent } from './granted-project-grid/granted-project-grid.component'; import { GrantedProjectGridComponent } from './granted-project-grid/granted-project-grid.component';
import { GrantedProjectListComponent } from './granted-project-list/granted-project-list.component'; import { GrantedProjectListComponent } from './granted-project-list/granted-project-list.component';
@ -57,29 +51,23 @@ import { GrantedProjectsComponent } from './granted-projects.component';
HasRoleModule, HasRoleModule,
MatTableModule, MatTableModule,
MatPaginatorModule, MatPaginatorModule,
MatMenuModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
ChangesModule, ChangesModule,
UserListModule,
MatMenuModule,
MatChipsModule,
MatIconModule, MatIconModule,
MatSelectModule, MatSelectModule,
MatButtonModule, MatButtonModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MetaLayoutModule, MetaLayoutModule,
MatProgressBarModule, MatProgressBarModule,
MatDialogModule,
MatButtonToggleModule,
MatTabsModule, MatTabsModule,
ProjectRolesModule, ProjectRolesModule,
SearchUserAutocompleteModule,
MatCheckboxModule, MatCheckboxModule,
CardModule, CardModule,
MatTooltipModule, MatTooltipModule,
MatSortModule, MatSortModule,
PipesModule, PipesModule,
OrgContributorsModule,
TranslateModule.forChild({ TranslateModule.forChild({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,

View File

@ -30,11 +30,16 @@
align-items: center; align-items: center;
.avatar-img, .avatar-circle { .avatar-img, .avatar-circle {
float: left; float: left;
margin: 0 8px 0 -15px; margin: 0 8px 0 -15px;
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 50%; border-radius: 50%;
&:not(:first-child) {
-webkit-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
-moz-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
}
} }
.add-img { .add-img {

View File

@ -39,6 +39,7 @@ export class IamViewsComponent {
).subscribe(views => { ).subscribe(views => {
this.dataSource = new MatTableDataSource(views); this.dataSource = new MatTableDataSource(views);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}); });
} }

View File

@ -9,17 +9,7 @@
</div> </div>
<metainfo class="side"> <metainfo class="side">
<!-- <div class="details">
</div> -->
<!-- <mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
<mat-tab label="Details"> -->
<app-iam-contributors> <app-iam-contributors>
</app-iam-contributors> </app-iam-contributors>
<!-- </mat-tab>
<mat-tab label="{{ 'CHANGES.ORG.TITLE' | translate }}" class="flex-col">
</mat-tab>
</mat-tab-group> -->
</metainfo> </metainfo>
</app-meta-layout> </app-meta-layout>

View File

@ -1,17 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-iam', selector: 'app-iam',
templateUrl: './iam.component.html', templateUrl: './iam.component.html',
styleUrls: ['./iam.component.scss'], styleUrls: ['./iam.component.scss'],
}) })
export class IamComponent implements OnInit { export class IamComponent {
constructor() {
}
ngOnInit(): void {
}
} }

View File

@ -35,6 +35,11 @@
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 50%; border-radius: 50%;
&:not(:first-child) {
-webkit-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
-moz-box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
box-shadow: -9px 0px 20px -6px rgba(0,0,0,0.75);
}
} }
.add-img { .add-img {

View File

@ -78,7 +78,7 @@
<ng-template appHasRole [appHasRole]="['user.grant.read']"> <ng-template appHasRole [appHasRole]="['user.grant.read']">
<app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}" <app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}"> description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<app-user-grants [filter]="userGrantSearchKey" [filterValue]="project.projectId" <app-user-grants [context]="userGrantContext" [projectId]="projectId"
[allowCreate]="['user.grant.write'] | hasRole" [allowCreate]="['user.grant.write'] | hasRole"
[allowDelete]="['user.grant.delete'] | hasRole"> [allowDelete]="['user.grant.delete'] | hasRole">
</app-user-grants> </app-user-grants>

View File

@ -6,6 +6,7 @@ import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { import {
Application, Application,
ApplicationSearchResponse, ApplicationSearchResponse,
@ -59,6 +60,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public isZitadel: boolean = false; public isZitadel: boolean = false;
public userGrantSearchKey: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID; public userGrantSearchKey: UserGrantSearchKey = UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID;
public userGrantContext: UserGrantContext = UserGrantContext.OWNED_PROJECT;
constructor( constructor(
public translate: TranslateService, public translate: TranslateService,

View File

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectsComponent } from './projects.component'; import { OwnedProjectsComponent } from './owned-projects.component';
describe('ProjectsComponent', () => { describe('OwnedProjectComponent', () => {
let component: ProjectsComponent; let component: OwnedProjectsComponent;
let fixture: ComponentFixture<ProjectsComponent>; let fixture: ComponentFixture<OwnedProjectsComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProjectsComponent], declarations: [OwnedProjectsComponent],
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProjectsComponent); fixture = TestBed.createComponent(OwnedProjectsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -3,10 +3,8 @@ import { HttpClient } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
@ -14,14 +12,13 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSortModule } from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { AvatarModule } from 'src/app/modules/avatar/avatar.module'; import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
import { ProjectGrantMembersModule } from 'src/app/modules/project-grant-members/project-grant-members.module'; import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.module';
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module'; import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
import { HttpLoaderFactory } from '../../app.module'; import { HttpLoaderFactory } from '../../app.module';
@ -30,10 +27,7 @@ import { CardModule } from '../../modules/card/card.module';
import { ChangesModule } from '../../modules/changes/changes.module'; import { ChangesModule } from '../../modules/changes/changes.module';
import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module'; import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module'; import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module';
import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module';
import { SearchUserAutocompleteModule } from '../../modules/search-user-autocomplete/search-user-autocomplete.module';
import { PipesModule } from '../../pipes/pipes.module'; import { PipesModule } from '../../pipes/pipes.module';
import { OrgContributorsModule } from '../orgs/org-contributors/org-contributors.module';
import { UserListModule } from '../user-list/user-list.module'; import { UserListModule } from '../user-list/user-list.module';
import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component'; import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component';
import { OwnedProjectGridComponent } from './owned-project-grid/owned-project-grid.component'; import { OwnedProjectGridComponent } from './owned-project-grid/owned-project-grid.component';
@ -59,7 +53,6 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
OwnedProjectsRoutingModule, OwnedProjectsRoutingModule,
UserGrantsModule, UserGrantsModule,
ProjectContributorsModule, ProjectContributorsModule,
ProjectGrantMembersModule,
FormsModule, FormsModule,
TranslateModule, TranslateModule,
AvatarModule, AvatarModule,
@ -74,22 +67,17 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
MatMenuModule, MatMenuModule,
MatChipsModule, MatChipsModule,
MatIconModule, MatIconModule,
MatSelectModule,
MatButtonModule, MatButtonModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MetaLayoutModule, MetaLayoutModule,
MatProgressBarModule, MatProgressBarModule,
MatDialogModule,
MatButtonToggleModule,
MatTabsModule,
ProjectRolesModule, ProjectRolesModule,
SearchUserAutocompleteModule, MatTabsModule,
MatCheckboxModule, MatCheckboxModule,
CardModule, CardModule,
MatTooltipModule, MatTooltipModule,
MatSortModule, MatSortModule,
PipesModule, PipesModule,
OrgContributorsModule,
TranslateModule.forChild({ TranslateModule.forChild({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,

View File

@ -13,10 +13,10 @@ import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { ProjectGrantMembersModule } from 'src/app/modules/project-grant-members/project-grant-members.module';
import { ProjectGrantDetailRoutingModule } from './project-grant-detail-routing.module'; import { ProjectGrantDetailRoutingModule } from './project-grant-detail-routing.module';
import { ProjectGrantDetailComponent } from './project-grant-detail.component'; import { ProjectGrantDetailComponent } from './project-grant-detail.component';
import { ProjectGrantMembersModule } from './project-grant-members/project-grant-members.module';
@NgModule({ @NgModule({

View File

@ -0,0 +1,30 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
import { ProjectGrantMembersCreateDialogComponent } from './project-grant-members-create-dialog.component';
@NgModule({
declarations: [ProjectGrantMembersCreateDialogComponent],
imports: [
CommonModule,
FormsModule,
MatDialogModule,
MatButtonModule,
TranslateModule,
MatSelectModule,
MatFormFieldModule,
SearchUserAutocompleteModule,
],
entryComponents: [
ProjectGrantMembersCreateDialogComponent,
],
})
export class ProjectGrantMembersCreateDialogModule { }

View File

@ -19,8 +19,8 @@ export class ProjectGrantMembersDataSource extends DataSource<ProjectMember.AsOb
super(); super();
} }
public loadMembers(projectId: string, grantId: string, pageIndex: number, public loadGrantMembers(projectId: string, grantId: string, pageIndex: number,
pageSize: number, sortDirection?: string): void { pageSize: number): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);
@ -41,7 +41,6 @@ export class ProjectGrantMembersDataSource extends DataSource<ProjectMember.AsOb
} }
/** /**
* Connect this data source to the table. The table will only update when * Connect this data source to the table. The table will only update when
* the returned stream emits new items. * the returned stream emits new items.

View File

@ -67,24 +67,16 @@
<ng-container matColumnDef="roles"> <ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <td class="pointer" mat-cell *matCellDef="let member">
<span class="role app-label" *ngFor="let role of member.rolesList; index as i"> <mat-form-field class="form-field" appearance="outline" *ngIf="projectId">
{{ 'ROLES.'+role | translate }}</span> <mat-label>{{ 'PROJECT.GRANT.TITLE' | translate }}</mat-label>
</td> <mat-select [(ngModel)]="member.rolesList" multiple [disabled]="disabled"
</ng-container> (selectionChange)="updateRoles(member, $event)">
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
<ng-container matColumnDef="action" stickyEnd> {{ 'ROLES.'+role | translate }}
<th class="action" mat-header-cell *matHeaderCellDef></th> </mat-option>
<td class="action" mat-cell *matCellDef="let member"> </mat-select>
<button [disabled]="disabled" [matMenuTriggerFor]="menu" mat-icon-button> </mat-form-field>
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item [routerLink]="['/user', member.userId]">Edit Member</button>
<button mat-menu-item (click)="removeMember(member)">
Remove Member
</button>
</mat-menu>
</td> </td>
</ng-container> </ng-container>

View File

@ -2,6 +2,7 @@ import { SelectionModel } from '@angular/cdk/collections';
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatSelectChange } from '@angular/material/select';
import { MatTable } from '@angular/material/table'; import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { ProjectMember, ProjectType } from 'src/app/proto/generated/management_pb'; import { ProjectMember, ProjectType } from 'src/app/proto/generated/management_pb';
@ -35,17 +36,19 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles']; public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
public ProjectType: any = ProjectType; public ProjectType: any = ProjectType;
public memberRoleOptions: string[] = [];
constructor( constructor(
private projectService: ProjectService, private projectService: ProjectService,
private dialog: MatDialog, private dialog: MatDialog,
private toast: ToastService, private toast: ToastService,
) { } ) {
this.dataSource = new ProjectGrantMembersDataSource(this.projectService);
this.getRoleOptions();
}
public ngOnInit(): void { public ngOnInit(): void {
this.dataSource = new ProjectGrantMembersDataSource(this.projectService); this.dataSource.loadGrantMembers(this.projectId, this.grantId, 0, 25);
console.log(this.projectId);
this.dataSource.loadMembers(this.projectId, this.grantId, 0, 25, 'asc');
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
@ -56,14 +59,32 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
.subscribe(); .subscribe();
} }
public getRoleOptions(): void {
if (this.type === ProjectType.PROJECTTYPE_GRANTED) {
this.projectService.GetProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
console.log(this.memberRoleOptions);
}).catch(error => {
this.toast.showError(error.message);
});
} else if (this.type === ProjectType.PROJECTTYPE_OWNED) {
this.projectService.GetProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.toObject().rolesList;
}).catch(error => {
this.toast.showError(error.message);
});
}
}
private loadMembersPage(): void { private loadMembersPage(): void {
this.dataSource.loadMembers( this.dataSource.loadGrantMembers(
this.projectId, this.projectId,
this.grantId, this.grantId,
this.paginator.pageIndex, this.paginator.pageIndex,
this.paginator.pageSize, this.paginator.pageSize,
); );
} }
public removeProjectMemberSelection(): void { public removeProjectMemberSelection(): void {
Promise.all(this.selection.selected.map(member => { Promise.all(this.selection.selected.map(member => {
return this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => { return this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => {
@ -124,4 +145,15 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
} }
}); });
} }
updateRoles(member: ProjectMember.AsObject, selectionChange: MatSelectChange): void {
console.log(this.projectId, this.grantId, member.userId, selectionChange.value);
this.projectService.ChangeProjectGrantMember(this.projectId, this.grantId, member.userId, selectionChange.value)
.then((newmember: ProjectMember) => {
console.log(newmember.toObject());
this.toast.showInfo('Member updated!');
}).catch(error => {
this.toast.showError(error.message);
});
}
} }

View File

@ -1,15 +1,12 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
@ -19,22 +16,19 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
import { SearchUserAutocompleteModule } from '../search-user-autocomplete/search-user-autocomplete.module';
import { import {
ProjectGrantMembersCreateDialogComponent, ProjectGrantMembersCreateDialogModule,
} from './project-grant-members-create-dialog/project-grant-members-create-dialog.component'; } from './project-grant-members-create-dialog/project-grant-members-create-dialog.module';
import { ProjectGrantMembersComponent } from './project-grant-members.component'; import { ProjectGrantMembersComponent } from './project-grant-members.component';
@NgModule({ @NgModule({
declarations: [ProjectGrantMembersComponent, ProjectGrantMembersCreateDialogComponent], declarations: [ProjectGrantMembersComponent],
imports: [ imports: [
CommonModule, CommonModule,
MatAutocompleteModule,
HasRoleModule, HasRoleModule,
RouterModule, RouterModule,
MatChipsModule,
MatMenuModule,
MatButtonModule, MatButtonModule,
MatCheckboxModule, MatCheckboxModule,
MatIconModule, MatIconModule,
@ -43,6 +37,7 @@ import { ProjectGrantMembersComponent } from './project-grant-members.component'
MatSelectModule, MatSelectModule,
MatTableModule, MatTableModule,
SearchUserAutocompleteModule, SearchUserAutocompleteModule,
ProjectGrantMembersCreateDialogModule,
MatPaginatorModule, MatPaginatorModule,
MatSortModule, MatSortModule,
MatTooltipModule, MatTooltipModule,

View File

@ -5,10 +5,6 @@ import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import { MatTable } from '@angular/material/table'; import { MatTable } from '@angular/material/table';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import {
ProjectGrantMembersCreateDialogComponent,
ProjectGrantMembersCreateDialogExportType,
} from 'src/app/modules/project-grant-members/project-grant-members-create-dialog/project-grant-members-create-dialog.component';
import { ProjectGrant, ProjectMemberView } from 'src/app/proto/generated/management_pb'; import { ProjectGrant, ProjectMemberView } from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service'; import { ProjectService } from 'src/app/services/project.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -75,35 +71,4 @@ export class ProjectGrantsComponent implements OnInit, AfterViewInit {
this.selection.clear() : this.selection.clear() :
this.dataSource.grantsSubject.value.forEach(row => this.selection.select(row)); this.dataSource.grantsSubject.value.forEach(row => this.selection.select(row));
} }
public async addProjectGrantMember(grant: ProjectGrant.AsObject): Promise<void> {
const keysList = (await this.projectService.GetProjectGrantMemberRoles()).toObject();
console.log(keysList);
const dialogRef = this.dialog.open(ProjectGrantMembersCreateDialogComponent, {
data: {
roleKeysList: keysList.rolesList,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((dataToAdd: ProjectGrantMembersCreateDialogExportType) => {
console.log(dataToAdd);
if (dataToAdd) {
dataToAdd.userIds.forEach((userid: string) => {
this.projectService.AddProjectGrantMember(
this.projectId,
grant.id,
userid,
dataToAdd.rolesKeyList,
).then(() => {
this.toast.showInfo('Project Grant Member successfully added!');
}).catch(error => {
this.toast.showError(error.message);
});
});
}
});
}
} }

View File

@ -33,80 +33,20 @@
</app-card> </app-card>
</div> </div>
<app-card *ngIf="user" title="{{'USER.PASSWORD.TITLE' | translate}}"
description="{{'USER.PASSWORD.DESCRIPTION' | translate}}">
<form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setPassword()">
<div class="content">
<mat-form-field class="formfield">
<mat-label>{{ 'USER.PASSWORD.OLD' | translate }}</mat-label>
<input autocomplete="off" name="password" type="password" matInput
formControlName="currentPassword" />
<mat-error *ngIf="currentPassword?.errors?.required">{{ 'USER.PASSWORD.REQUIRED' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
<input autocomplete="off" name="new password" type="password" matInput
formControlName="newPassword" />
<mat-error *ngIf="newPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:newPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</mat-label>
<input autocomplete="off" name="password repeating" type="password" matInput
formControlName="confirmPassword" />
<mat-error *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:confirmPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
</div>
<div class="btn-container">
<button [disabled]="passwordForm.invalid" mat-raised-button
color="primary">{{ 'USER.PASSWORD.RESET' | translate }}</button>
</div>
</form>
</app-card>
<app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}" <app-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}"> description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<div class="method-col"> <div class="method-col">
<div class="method-row">
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
<span>*********</span>
<div class="actions">
<a [routerLink]="['password']" mat-icon-button>
<mat-icon>chevron_right</mat-icon>
</a>
</div>
</div>
<div class="method-row"> <div class="method-row">
<span class="label">{{ 'USER.EMAIL' | translate }}</span> <span class="label">{{ 'USER.EMAIL' | translate }}</span>
@ -166,9 +106,6 @@
<button (click)="phoneEditState = true" mat-icon-button> <button (click)="phoneEditState = true" mat-icon-button>
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
<button color="warn" *ngIf="user?.phone" (click)="deletePhone()" mat-icon-button>
<i class="las la-trash"></i>
</button>
</div> </div>
</ng-container> </ng-container>
@ -188,36 +125,6 @@
</app-card> </app-card>
<app-auth-user-mfa *ngIf="user"></app-auth-user-mfa> <app-auth-user-mfa *ngIf="user"></app-auth-user-mfa>
<!-- <app-card title="{{ 'USER.ADDRESS.TITLE' | translate }}" *ngIf="user">
<form [formGroup]="addressForm" (ngSubmit)="saveAddress()">
<div class="content">
<mat-form-field class="formfield">
<mat-label>{{ 'USER.ADDRESS.STREET' | translate }}</mat-label>
<input matInput formControlName="streetAddress" />
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.ADDRESS.POSTAL_CODE' | translate }}</mat-label>
<input matInput formControlName="postalCode" />
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.ADDRESS.LOCALITY' | translate }}</mat-label>
<input matInput formControlName="locality" />
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.ADDRESS.REGION' | translate }}</mat-label>
<input matInput formControlName="region" />
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.ADDRESS.COUNTRY' | translate }}</mat-label>
<input matInput formControlName="country" />
</mat-form-field>
</div>
<div class="btn-container">
<button type="submit" color="primary" mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</form>
</app-card> -->
</div> </div>
<metainfo *ngIf="user" class="side"> <metainfo *ngIf="user" class="side">

View File

@ -94,7 +94,7 @@ h1 {
flex-wrap: wrap; flex-wrap: wrap;
.label, .name { .label, .name {
margin-right: 1rem; margin-right: 1rem;
} }
.actions { .actions {
@ -107,7 +107,7 @@ h1 {
.label { .label {
flex: 1; flex: 1;
font-size: .9rem; font-size: .9rem;
color: #818a8a; color: #8795a1;
} }
mat-icon { mat-icon {
@ -150,6 +150,20 @@ h1 {
.theme-card { .theme-card {
max-width: 300px; max-width: 300px;
} }
.pwd-row {
display: flex;
align-items: center;
span {
flex: 1;
}
.title {
color: #8795a1;
font-size: 0.9rem;
}
}
} }
.side { .side {

View File

@ -1,17 +1,15 @@
import { Component, OnDestroy } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { Gender, UserAddress, UserEmail, UserPhone, UserProfile, UserView } from 'src/app/proto/generated/auth_pb'; import { Gender, UserAddress, UserEmail, UserPhone, UserProfile, UserView } from 'src/app/proto/generated/auth_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/management_pb';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { CodeDialogComponent } from '../code-dialog/code-dialog.component'; import { CodeDialogComponent } from '../code-dialog/code-dialog.component';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators';
function passwordConfirmValidator(c: AbstractControl): any { function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) { if (!c.parent || !c) {
@ -44,7 +42,6 @@ export class AuthUserDetailComponent implements OnDestroy {
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE]; public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en']; public languages: string[] = ['de', 'en'];
public passwordForm!: FormGroup;
private subscription: Subscription = new Subscription(); private subscription: Subscription = new Subscription();
public emailEditState: boolean = false; public emailEditState: boolean = false;
@ -52,7 +49,6 @@ export class AuthUserDetailComponent implements OnDestroy {
public loading: boolean = false; public loading: boolean = false;
public policy!: PasswordComplexityPolicy.AsObject;
public copied: string = ''; public copied: string = '';
public ChangeType: any = ChangeType; public ChangeType: any = ChangeType;
@ -66,40 +62,6 @@ export class AuthUserDetailComponent implements OnDestroy {
private dialog: MatDialog, private dialog: MatDialog,
private orgService: OrgService, private orgService: OrgService,
) { ) {
const validators: Validators[] = [Validators.required];
this.orgService.GetPasswordComplexityPolicy().then(data => {
this.policy = data.toObject();
if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength));
}
if (this.policy.hasLowercase) {
validators.push(lowerCaseValidator);
}
if (this.policy.hasUppercase) {
validators.push(upperCaseValidator);
}
if (this.policy.hasNumber) {
validators.push(numberValidator);
}
if (this.policy.hasSymbol) {
validators.push(symbolValidator);
}
this.passwordForm = this.fb.group({
currentPassword: ['', []],
newPassword: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
}).catch(error => {
this.toast.showError(error.message);
console.error(error.message);
this.passwordForm = this.fb.group({
currentPassword: ['', []],
newPassword: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
});
this.loading = true; this.loading = true;
this.getData().then(() => { this.getData().then(() => {
this.loading = false; this.loading = false;
@ -136,19 +98,6 @@ export class AuthUserDetailComponent implements OnDestroy {
}); });
} }
public setPassword(): void {
if (this.passwordForm.valid && this.currentPassword &&
this.currentPassword.value &&
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.userService
.ChangeMyPassword(this.currentPassword.value, this.newPassword.value).then((data: any) => {
this.toast.showInfo('Password Set');
}).catch(data => {
this.toast.showError(data.message);
});
}
}
public saveEmail(): void { public saveEmail(): void {
this.emailEditState = false; this.emailEditState = false;
@ -163,11 +112,6 @@ export class AuthUserDetailComponent implements OnDestroy {
}); });
} }
public deletePhone(): void {
this.user.phone = '';
this.savePhone();
}
public enterCode(): void { public enterCode(): void {
const dialogRef = this.dialog.open(CodeDialogComponent, { const dialogRef = this.dialog.open(CodeDialogComponent, {
data: { data: {
@ -220,16 +164,6 @@ export class AuthUserDetailComponent implements OnDestroy {
}); });
} }
public get currentPassword(): AbstractControl | null {
return this.passwordForm.get('currentPassword');
}
public get newPassword(): AbstractControl | null {
return this.passwordForm.get('newPassword');
}
public get confirmPassword(): AbstractControl | null {
return this.passwordForm.get('confirmPassword');
}
private async getData(): Promise<void> { private async getData(): Promise<void> {
this.userService.GetMyUser().then(user => { this.userService.GetMyUser().then(user => {
this.user = user.toObject(); this.user = user.toObject();

View File

@ -0,0 +1,151 @@
<div class="max-width-container">
<div class="container">
<div class="left">
<a *ngIf="userId" [routerLink]="[ '/user',userId]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<a *ngIf="!userId" [routerLink]="[ '/user/me']" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
</div>
<div class="right">
<div class="head">
<h1>{{ 'USER.PASSWORD.TITLE' | translate }}</h1>
<p class="desc">{{ 'USER.PASSWORD.DESCRIPTION' | translate }}</p>
</div>
<ng-container *ngIf="userId; else authUser">
<form *ngIf="passwordForm" autocomplete="new-password" [formGroup]="passwordForm"
(ngSubmit)="setInitialPassword(userId)">
<div class="content center">
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
<input autocomplete="off" name="password" matInput formControlName="password"
type="password" />
<mat-error *ngIf="password?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:password?.errors?.minlength }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</mat-label>
<input autocomplete="off" name="passwordRepeat" matInput formControlName="confirmPassword"
type="password" />
<mat-error *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:confirmPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
</div>
<div class="btn-container">
<button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button
color="primary">{{ 'USER.PASSWORD.SET' | translate }}</button>
</div>
</form>
</ng-container>
<ng-template #authUser>
<form *ngIf="passwordForm" [formGroup]="passwordForm" (ngSubmit)="setPassword()">
<div class="content">
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'USER.PASSWORD.OLD' | translate }}</mat-label>
<input autocomplete="off" name="password" type="password" matInput
formControlName="currentPassword" />
<mat-error *ngIf="currentPassword?.errors?.required">
{{ 'USER.PASSWORD.REQUIRED' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
<input autocomplete="off" name="new password" type="password" matInput
formControlName="newPassword" />
<mat-error *ngIf="newPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="newPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:newPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield" appearance="outline">
<mat-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</mat-label>
<input autocomplete="off" name="password repeating" type="password" matInput
formControlName="confirmPassword" />
<mat-error *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:confirmPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
</div>
<button class="submit-button" [disabled]="passwordForm.invalid" mat-raised-button
color="primary">{{ 'USER.PASSWORD.RESET' | translate }}</button>
</form>
</ng-template>
</div>
</div>
</div>

View File

@ -0,0 +1,66 @@
.container {
display: flex;
padding-bottom: 3rem;
.left {
width: 100px;
display: flex;
padding: 1rem;
justify-content: center;
a {
margin-top: .2rem;
}
}
.right {
flex: 1;
padding-top: 1rem;
.head {
display: flex;
align-items: center;
border-bottom: 1px solid #ffffff20;
margin-bottom: 2rem;
flex-wrap: wrap;
a {
display: block;
}
h1 {
font-size: 2rem;
margin-top: 0.2rem;
}
.desc {
width: 100%;
display: block;
font-size: .9rem;
color: #8795a1;
}
}
}
}
.content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 -.5rem;
mat-form-field {
margin: 0 .5rem;
}
&.center {
align-items: center;
}
}
.submit-button {
margin-top: 100px;
display: block;
padding: 0.5rem 4rem;
border-radius: 0.5rem;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordComponent } from './password.component';
describe('PasswordComponent', () => {
let component: PasswordComponent;
let fixture: ComponentFixture<PasswordComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PasswordComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,137 @@
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/management_pb';
import { AuthUserService } from 'src/app/services/auth-user.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service';
import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators';
function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) {
return;
}
const pwd = c.parent.get('password');
const cpwd = c.parent.get('confirmPassword');
if (!pwd || !cpwd) {
return;
}
if (pwd.value !== cpwd.value) {
return { invalid: true, notequal: 'Password is not equal' };
}
}
@Component({
selector: 'app-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.scss'],
})
export class PasswordComponent implements OnInit {
userId: string = '';
public policy!: PasswordComplexityPolicy.AsObject;
public passwordForm!: FormGroup;
constructor(
private orgService: OrgService,
activatedRoute: ActivatedRoute,
private fb: FormBuilder,
private userService: AuthUserService,
private mgmtUserService: MgmtUserService,
private toast: ToastService,
) {
activatedRoute.params.subscribe(data => {
const { id } = data;
if (id) {
this.userId = id;
}
const validators: Validators[] = [Validators.required];
this.orgService.GetPasswordComplexityPolicy().then(complexity => {
this.policy = complexity.toObject();
if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength));
}
if (this.policy.hasLowercase) {
validators.push(lowerCaseValidator);
}
if (this.policy.hasUppercase) {
validators.push(upperCaseValidator);
}
if (this.policy.hasNumber) {
validators.push(numberValidator);
}
if (this.policy.hasSymbol) {
validators.push(symbolValidator);
}
this.setupForm(validators);
}).catch(error => {
this.setupForm(validators);
});
});
}
ngOnInit(): void {
}
setupForm(validators: Validators[]): void {
if (this.userId) {
this.passwordForm = this.fb.group({
password: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
} else {
this.passwordForm = this.fb.group({
currentPassword: ['', validators],
newPassword: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
}
}
public setInitialPassword(userId: string): void {
if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.SetInitialPassword(userId, this.password.value).then((data: any) => {
this.toast.showInfo('Set initial Password');
window.history.back();
}).catch(data => {
this.toast.showError(data.message);
});
}
}
public setPassword(): void {
if (this.passwordForm.valid && this.currentPassword &&
this.currentPassword.value &&
this.newPassword && this.newPassword.value && this.newPassword.valid) {
this.userService
.ChangeMyPassword(this.currentPassword.value, this.newPassword.value).then((data: any) => {
this.toast.showInfo('Password Set');
window.history.back();
}).catch(data => {
this.toast.showError(data.message);
});
}
}
public get password(): AbstractControl | null {
return this.passwordForm.get('password');
}
public get newPassword(): AbstractControl | null {
return this.passwordForm.get('newPassword');
}
public get currentPassword(): AbstractControl | null {
return this.passwordForm.get('newPassword');
}
public get confirmPassword(): AbstractControl | null {
return this.passwordForm.get('confirmPassword');
}
}

View File

@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { RoleGuard } from 'src/app/guards/role.guard'; import { RoleGuard } from 'src/app/guards/role.guard';
import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component'; import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.component';
import { PasswordComponent } from './password/password.component';
import { UserDetailComponent } from './user-detail/user-detail.component'; import { UserDetailComponent } from './user-detail/user-detail.component';
const routes: Routes = [ const routes: Routes = [
@ -10,6 +11,10 @@ const routes: Routes = [
path: 'me', path: 'me',
component: AuthUserDetailComponent, component: AuthUserDetailComponent,
}, },
{
path: 'me/password',
component: PasswordComponent,
},
{ {
path: ':id', path: ':id',
component: UserDetailComponent, component: UserDetailComponent,
@ -18,6 +23,14 @@ const routes: Routes = [
roles: ['user.read'], roles: ['user.read'],
}, },
}, },
{
path: ':id/password',
component: PasswordComponent,
canActivate: [RoleGuard],
data: {
roles: ['user.write'],
},
},
]; ];
@NgModule({ @NgModule({

View File

@ -28,6 +28,7 @@ import { ThemeSettingComponent } from './theme-setting/theme-setting.component';
import { UserDetailRoutingModule } from './user-detail-routing.module'; import { UserDetailRoutingModule } from './user-detail-routing.module';
import { UserDetailComponent } from './user-detail/user-detail.component'; import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-mfa/user-mfa.component'; import { UserMfaComponent } from './user-mfa/user-mfa.component';
import { PasswordComponent } from './password/password.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -37,6 +38,7 @@ import { UserMfaComponent } from './user-mfa/user-mfa.component';
AuthUserMfaComponent, AuthUserMfaComponent,
UserMfaComponent, UserMfaComponent,
ThemeSettingComponent, ThemeSettingComponent,
PasswordComponent,
], ],
imports: [ imports: [
UserDetailRoutingModule, UserDetailRoutingModule,

View File

@ -24,76 +24,22 @@
</app-card> </app-card>
</ng-template> </ng-template>
<app-card title="{{'USER.PASSWORD.TITLE' | translate}}"
description="{{'USER.PASSWORD.DESCRIPTION' | translate}}">
<card-actions class="card-actions">
<button *ngIf="user.state === UserState.USERSTATE_INITIAL" mat-stroked-button color="primary"
(click)="sendSetPasswordNotification()">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
</card-actions>
<form *ngIf="passwordForm" autocomplete="new-password" [formGroup]="passwordForm"
(ngSubmit)="setInitialPassword()">
<div class="content center">
<mat-form-field class="formfield">
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
<input autocomplete="off" name="password" matInput formControlName="password" type="password" />
<mat-error *ngIf="password?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="password?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:password?.errors?.minlength }}
</mat-error>
</mat-form-field>
<mat-form-field class="formfield">
<mat-label>{{ 'USER.PASSWORD.CONFIRM' | translate }}</mat-label>
<input autocomplete="off" name="passwordRepeat" matInput formControlName="confirmPassword"
type="password" />
<mat-error *ngIf="confirmPassword?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.symbolValidator">
{{ 'USER.VALIDATION.SYMBOLERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.numberValidator">
{{ 'USER.VALIDATION.NUMBERERROR' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.notequal">
{{ 'USER.PASSWORD.NOTEQUAL' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.upperCaseValidator">
{{ 'USER.VALIDATION.UPPERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.lowerCaseValidator">
{{ 'USER.VALIDATION.LOWERCASEMISSING' | translate }}
</mat-error>
<mat-error *ngIf="confirmPassword?.errors?.minlength">
{{ 'USER.VALIDATION.MINLENGTH' | translate:confirmPassword?.errors?.minlength }}
</mat-error>
</mat-form-field>
</div>
<div class="btn-container">
<button [disabled]="passwordForm.invalid" mat-raised-button
color="primary">{{ 'USER.PASSWORD.SET' | translate }}</button>
</div>
</form>
</app-card>
<app-card title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}" <app-card title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}"> description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<div class="method-col"> <div class="method-col">
<div class="method-row">
<span class="label">{{ 'USER.PROFILE.PASSWORD' | translate }}</span>
<span>*********</span>
<div class="actions">
<button class="notify-change-pwd" (click)="sendSetPasswordNotification()" mat-stroked-button
color="primary"
*ngIf="user.state === UserState.USERSTATE_INITIAL">{{ 'USER.PASSWORD.RESENDNOTIFICATION' | translate }}</button>
<a [routerLink]="['password']" mat-icon-button>
<mat-icon>chevron_right</mat-icon>
</a>
</div>
</div>
<div class="method-row"> <div class="method-row">
<span class="label">{{ 'USER.EMAIL' | translate }}</span> <span class="label">{{ 'USER.EMAIL' | translate }}</span>
@ -142,7 +88,6 @@
<ng-container *ngIf="user?.phone && !user?.isPhoneVerified"> <ng-container *ngIf="user?.phone && !user?.isPhoneVerified">
<mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off <mat-icon color="warn" aria-hidden="false" aria-label="not verified icon">highlight_off
</mat-icon> </mat-icon>
<a class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}" <a class="verify" matTooltip="{{'USER.LOGINMETHODS.PHONE.RESEND' | translate}}"
(click)="resendPhoneVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a> (click)="resendPhoneVerification()">{{'USER.LOGINMETHODS.RESENDCODE' | translate}}</a>
</ng-container> </ng-container>
@ -152,9 +97,6 @@
<button (click)="phoneEditState = true" mat-icon-button> <button (click)="phoneEditState = true" mat-icon-button>
<mat-icon>edit</mat-icon> <mat-icon>edit</mat-icon>
</button> </button>
<button *ngIf="user?.phone" (click)="deletePhone()" color="warn" mat-icon-button>
<i class="las la-trash"></i>
</button>
</div> </div>
</ng-container> </ng-container>
@ -177,7 +119,7 @@
<app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}" <app-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}"> description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<app-user-grants [filterValue]="user?.id" [allowCreate]="['user.grant.write'] | hasRole" <app-user-grants [userId]="user.id" [allowCreate]="['user.grant.write'] | hasRole"
[allowDelete]="['user.grant.delete'] | hasRole"></app-user-grants> [allowDelete]="['user.grant.delete'] | hasRole"></app-user-grants>
</app-card> </app-card>
</div> </div>

View File

@ -9,7 +9,7 @@
} }
h1 { h1 {
font-size: 1.2rem; font-size: 2rem;
margin: 0 1rem; margin: 0 1rem;
margin-left: 2rem; margin-left: 2rem;
font-weight: normal; font-weight: normal;
@ -82,6 +82,10 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
.notify-change-pwd {
border-radius: .5rem;
}
} }
.label { .label {

View File

@ -1,7 +1,5 @@
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -9,7 +7,6 @@ import { ChangeType } from 'src/app/modules/changes/changes.component';
import { import {
Gender, Gender,
NotificationType, NotificationType,
PasswordComplexityPolicy,
UserEmail, UserEmail,
UserPhone, UserPhone,
UserProfile, UserProfile,
@ -18,27 +15,8 @@ import {
} from 'src/app/proto/generated/management_pb'; } from 'src/app/proto/generated/management_pb';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service'; import { MgmtUserService } from 'src/app/services/mgmt-user.service';
import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { CodeDialogComponent } from '../code-dialog/code-dialog.component';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../validators';
function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) {
return;
}
const pwd = c.parent.get('password');
const cpwd = c.parent.get('confirmPassword');
if (!pwd || !cpwd) {
return;
}
if (pwd.value !== cpwd.value) {
return { invalid: true, notequal: 'Password is not equal' };
}
}
@Component({ @Component({
selector: 'app-user-detail', selector: 'app-user-detail',
templateUrl: './user-detail.component.html', templateUrl: './user-detail.component.html',
@ -46,13 +24,9 @@ function passwordConfirmValidator(c: AbstractControl): any {
}) })
export class UserDetailComponent implements OnInit, OnDestroy { export class UserDetailComponent implements OnInit, OnDestroy {
public user!: UserView.AsObject; public user!: UserView.AsObject;
// public address: UserAddress.AsObject = { id: '' } as any;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE]; public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en']; public languages: string[] = ['de', 'en'];
public passwordForm!: FormGroup;
// public addressForm!: FormGroup;
public isMgmt: boolean = false; public isMgmt: boolean = false;
private subscription: Subscription = new Subscription(); private subscription: Subscription = new Subscription();
public emailEditState: boolean = false; public emailEditState: boolean = false;
@ -62,49 +36,14 @@ export class UserDetailComponent implements OnInit, OnDestroy {
public loading: boolean = false; public loading: boolean = false;
public UserState: any = UserState; public UserState: any = UserState;
public policy!: PasswordComplexityPolicy.AsObject;
constructor( constructor(
public translate: TranslateService, public translate: TranslateService,
private route: ActivatedRoute, private route: ActivatedRoute,
private toast: ToastService, private toast: ToastService,
private mgmtUserService: MgmtUserService, private mgmtUserService: MgmtUserService,
private fb: FormBuilder,
private _location: Location, private _location: Location,
private dialog: MatDialog,
private orgService: OrgService,
public authUserService: AuthUserService, public authUserService: AuthUserService,
) { ) { }
const validators: Validators[] = [Validators.required];
this.orgService.GetPasswordComplexityPolicy().then(data => {
this.policy = data.toObject();
if (this.policy.minLength) {
validators.push(Validators.minLength(this.policy.minLength));
}
if (this.policy.hasLowercase) {
validators.push(lowerCaseValidator);
}
if (this.policy.hasUppercase) {
validators.push(upperCaseValidator);
}
if (this.policy.hasNumber) {
validators.push(numberValidator);
}
if (this.policy.hasSymbol) {
validators.push(symbolValidator);
}
this.passwordForm = this.fb.group({
password: ['', validators],
confirmPassword: ['', [...validators, passwordConfirmValidator]],
});
}).catch(error => {
this.passwordForm = this.fb.group({
password: ['', []],
confirmPassword: ['', [passwordConfirmValidator]],
});
});
}
public ngOnInit(): void { public ngOnInit(): void {
this.subscription = this.route.params.subscribe(params => { this.subscription = this.route.params.subscribe(params => {
@ -121,25 +60,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
public deletePhone(): void {
this.user.phone = '';
this.savePhone();
}
public enterCode(): void {
const dialogRef = this.dialog.open(CodeDialogComponent, {
data: {
number: this.user.phone,
},
});
dialogRef.afterClosed().subscribe(code => {
if (code) {
this.toast.showInfo('TODO: implement service');
}
});
}
public saveProfile(profileData: UserProfile.AsObject): void { public saveProfile(profileData: UserProfile.AsObject): void {
this.user.firstName = profileData.firstName; this.user.firstName = profileData.firstName;
this.user.lastName = profileData.lastName; this.user.lastName = profileData.lastName;
@ -180,27 +100,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
}); });
} }
public setInitialPassword(): void {
if (this.passwordForm.valid && this.password && this.password.value) {
this.mgmtUserService.SetInitialPassword(this.user.id, this.password.value).then((data: any) => {
this.toast.showInfo('Set initial Password');
this.user.email = data.toObject();
}).catch(data => {
this.toast.showError(data.message);
});
}
}
public sendSetPasswordNotification(): void {
this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL)
.then((data: any) => {
this.toast.showInfo('Set initial Password');
this.user.email = data.toObject();
}).catch(data => {
this.toast.showError(data.message);
});
}
public saveEmail(): void { public saveEmail(): void {
this.emailEditState = false; this.emailEditState = false;
this.mgmtUserService this.mgmtUserService
@ -227,13 +126,6 @@ export class UserDetailComponent implements OnInit, OnDestroy {
this._location.back(); this._location.back();
} }
public get password(): AbstractControl | null {
return this.passwordForm.get('password');
}
public get confirmPassword(): AbstractControl | null {
return this.passwordForm.get('confirmPassword');
}
private async getData({ id }: Params): Promise<void> { private async getData({ id }: Params): Promise<void> {
this.isMgmt = true; this.isMgmt = true;
this.mgmtUserService.GetUserByID(id).then(user => { this.mgmtUserService.GetUserByID(id).then(user => {
@ -242,4 +134,13 @@ export class UserDetailComponent implements OnInit, OnDestroy {
console.error(err); console.error(err);
}); });
} }
public sendSetPasswordNotification(): void {
this.mgmtUserService.SendSetPasswordNotification(this.user.id, NotificationType.NOTIFICATIONTYPE_EMAIL)
.then(() => {
this.toast.showInfo('Set initial Password');
}).catch(data => {
this.toast.showError(data.message);
});
}
} }

View File

@ -16,7 +16,7 @@
{{'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate}} {{'PROJECT.GRANT.CREATE.ORG_DESCRIPTION_DESC' | translate}}
</p> </p>
<ng-container *ngIf="filter && filter == UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID"> <ng-container *ngIf="context && context == UserGrantContext.USER">
<h1>{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</h1> <h1>{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</h1>
<app-search-project-autocomplete class="block" singleOutput="true" <app-search-project-autocomplete class="block" singleOutput="true"
@ -24,7 +24,8 @@
</app-search-project-autocomplete> </app-search-project-autocomplete>
</ng-container> </ng-container>
<ng-container *ngIf="filter && filter == UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID"> <ng-container
*ngIf="context && (context == UserGrantContext.GRANTED_PROJECT || context == UserGrantContext.OWNED_PROJECT)">
<h1>{{'PROJECT.GRANT.CREATE.SEL_USER' | translate}}</h1> <h1>{{'PROJECT.GRANT.CREATE.SEL_USER' | translate}}</h1>
<app-search-user-autocomplete class="block" singleOutput="true" (selectionChanged)="selectUser($event)"> <app-search-user-autocomplete class="block" singleOutput="true" (selectionChanged)="selectUser($event)">
@ -43,7 +44,7 @@
<div class="btn-container"> <div class="btn-container">
<ng-container *ngIf="currentCreateStep === 1"> <ng-container *ngIf="currentCreateStep === 1">
<button <button
[disabled]="!org || (filter == UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID && !projectId) || (filter == UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID && !userId)" [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> (click)="next()" color="primary" mat-raised-button class="big-button" cdkFocusInitial>
{{ 'ACTIONS.CONTINUE' | translate }} {{ 'ACTIONS.CONTINUE' | translate }}
</button> </button>

View File

@ -2,15 +2,9 @@ import { Location } from '@angular/common';
import { Component, OnDestroy } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { Org } from 'src/app/proto/generated/auth_pb'; import { Org } from 'src/app/proto/generated/auth_pb';
import { import { ProjectGrantView, ProjectRole, ProjectView, User, UserGrant } from 'src/app/proto/generated/management_pb';
ProjectGrantView,
ProjectRole,
ProjectView,
User,
UserGrant,
UserGrantSearchKey,
} from 'src/app/proto/generated/management_pb';
import { AuthService } from 'src/app/services/auth.service'; import { AuthService } from 'src/app/services/auth.service';
import { MgmtUserService } from 'src/app/services/mgmt-user.service'; import { MgmtUserService } from 'src/app/services/mgmt-user.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@ -21,6 +15,8 @@ import { ToastService } from 'src/app/services/toast.service';
styleUrls: ['./user-grant-create.component.scss'], styleUrls: ['./user-grant-create.component.scss'],
}) })
export class UserGrantCreateComponent implements OnDestroy { export class UserGrantCreateComponent implements OnDestroy {
public context!: UserGrantContext;
public org!: Org.AsObject; public org!: Org.AsObject;
public userId: string = ''; public userId: string = '';
public projectId: string = ''; public projectId: string = '';
@ -30,12 +26,11 @@ export class UserGrantCreateComponent implements OnDestroy {
public STEPS: number = 2; // project, roles public STEPS: number = 2; // project, roles
public currentCreateStep: number = 1; public currentCreateStep: number = 1;
public filter!: UserGrantSearchKey;
public filterValue: string = ''; public filterValue: string = '';
private subscription: Subscription = new Subscription(); private subscription: Subscription = new Subscription();
public UserGrantSearchKey: any = UserGrantSearchKey; public UserGrantContext: any = UserGrantContext;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private userService: MgmtUserService, private userService: MgmtUserService,
@ -45,18 +40,20 @@ export class UserGrantCreateComponent implements OnDestroy {
) { ) {
this.subscription = this.route.params.subscribe((params: Params) => { this.subscription = this.route.params.subscribe((params: Params) => {
console.log(params); console.log(params);
const { filter, filterValue } = params; const { context, projectid, grantid, userid } = params;
this.filter = filter; this.context = context;
switch (filter) {
case (`${UserGrantSearchKey.USERGRANTSEARCHKEY_PROJECT_ID}`):
this.projectId = filterValue;
break;
case (`${UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID}`):
this.userId = filterValue;
break;
}
console.log(this.projectId, this.userId); this.projectId = projectid;
this.grantId = grantid;
this.userId = userid;
if (this.userId) {
this.context = UserGrantContext.USER;
} else if (this.projectId && !this.grantId) {
this.context = UserGrantContext.OWNED_PROJECT;
} else if (this.projectId && this.grantId) {
this.context = UserGrantContext.GRANTED_PROJECT;
}
}); });
this.authService.GetActiveOrg().then(org => { this.authService.GetActiveOrg().then(org => {
@ -69,15 +66,48 @@ export class UserGrantCreateComponent implements OnDestroy {
} }
public addGrant(): void { public addGrant(): void {
this.userService.CreateUserGrant( switch (this.context) {
this.projectId, case UserGrantContext.USER:
this.userId, this.userService.CreateUserGrant(
this.rolesList, this.projectId,
).then((data: UserGrant) => { this.userId,
this.close(); this.rolesList,
}).catch(error => { ).then((data: UserGrant) => {
this.toast.showError(error.message); this.toast.showInfo('User Grant added');
}); this.close();
}).catch(error => {
this.toast.showError(error.message);
});
break;
case UserGrantContext.OWNED_PROJECT:
this.userService.CreateProjectUserGrant(
this.projectId,
this.userId,
this.rolesList,
).then((data: UserGrant) => {
this.toast.showInfo('Project User Grant added');
this.close();
}).catch(error => {
this.toast.showError(error.message);
});
break;
case UserGrantContext.GRANTED_PROJECT:
this.userService.CreateProjectGrantUserGrant(
this.org.id,
this.projectId,
this.grantId,
this.userId,
this.rolesList,
).then((data: UserGrant) => {
this.toast.showInfo('Project Grant User Grant added');
this.close();
}).catch(error => {
this.toast.showError(error.message);
});
break;
}
} }
public selectProject(project: ProjectView.AsObject | ProjectGrantView.AsObject | any): void { public selectProject(project: ProjectView.AsObject | ProjectGrantView.AsObject | any): void {

View File

@ -15,10 +15,12 @@ import {
ProjectGrantMemberSearchQuery, ProjectGrantMemberSearchQuery,
ProjectGrantMemberSearchRequest, ProjectGrantMemberSearchRequest,
ProjectGrantMemberSearchResponse, ProjectGrantMemberSearchResponse,
ProjectGrantUserGrantCreate,
ProjectGrantUserGrantID, ProjectGrantUserGrantID,
ProjectGrantUserGrantSearchRequest, ProjectGrantUserGrantSearchRequest,
ProjectGrantUserGrantUpdate, ProjectGrantUserGrantUpdate,
ProjectRoleAdd, ProjectRoleAdd,
ProjectUserGrantSearchRequest,
SetPasswordNotificationRequest, SetPasswordNotificationRequest,
UpdateUserAddressRequest, UpdateUserAddressRequest,
UpdateUserEmailRequest, UpdateUserEmailRequest,
@ -30,6 +32,7 @@ import {
UserGrant, UserGrant,
UserGrantCreate, UserGrantCreate,
UserGrantID, UserGrantID,
UserGrantRemoveBulk,
UserGrantSearchQuery, UserGrantSearchQuery,
UserGrantSearchRequest, UserGrantSearchRequest,
UserGrantSearchResponse, UserGrantSearchResponse,
@ -221,6 +224,44 @@ export class MgmtUserService {
); );
} }
public async CreateProjectUserGrant(
projectId: string,
userId: string,
roleNamesList: string[],
): Promise<UserGrant> {
const req = new UserGrantCreate();
req.setProjectId(projectId);
req.setUserId(userId);
req.setRoleKeysList(roleNamesList);
return await this.request(
c => c.createProjectUserGrant,
req,
f => f,
);
}
public async CreateProjectGrantUserGrant(
orgId: string,
projectId: string,
grantId: string,
userId: string,
roleNamesList: string[],
): Promise<UserGrant> {
const req = new ProjectGrantUserGrantCreate();
req.setOrgId(orgId);
req.setProjectId(projectId);
req.setProjectGrantId(grantId);
req.setUserId(userId);
req.setRoleKeysList(roleNamesList);
return await this.request(
c => c.createProjectGrantUserGrant,
req,
f => f,
);
}
public async ReactivateUser(id: string): Promise<UserPhone> { public async ReactivateUser(id: string): Promise<UserPhone> {
const req = new UserID(); const req = new UserID();
req.setId(id); req.setId(id);
@ -355,6 +396,8 @@ export class MgmtUserService {
); );
} }
// USER GRANTS
public async SearchUserGrants( public async SearchUserGrants(
limit: number, limit: number,
offset: number, offset: number,
@ -373,13 +416,34 @@ export class MgmtUserService {
); );
} }
public async SearchProjectUserGrants(
projectId: string,
limit: number,
offset: number,
queryList?: UserGrantSearchQuery[],
): Promise<UserGrantSearchResponse> {
const req = new ProjectUserGrantSearchRequest();
req.setProjectId(projectId);
req.setLimit(limit);
req.setOffset(offset);
if (queryList) {
req.setQueriesList(queryList);
}
return await this.request(
c => c.searchProjectUserGrants,
req,
f => f,
);
}
public async searchProjectGrantUserGrants( public async SearchProjectGrantUserGrants(
projectGrantId: string,
limit: number, limit: number,
offset: number, offset: number,
queryList?: UserGrantSearchQuery[], queryList?: UserGrantSearchQuery[],
): Promise<UserGrantSearchResponse> { ): Promise<UserGrantSearchResponse> {
const req = new ProjectGrantUserGrantSearchRequest(); const req = new ProjectGrantUserGrantSearchRequest();
req.setProjectGrantId(projectGrantId);
req.setLimit(limit); req.setLimit(limit);
req.setOffset(offset); req.setOffset(offset);
if (queryList) { if (queryList) {
@ -460,6 +524,36 @@ export class MgmtUserService {
); );
} }
public async RemoveUserGrant(
id: string,
userId: string,
): Promise<Empty> {
const req = new UserGrantID();
req.setId(id);
req.setUserId(userId);
return await this.request(
c => c.removeUserGrant,
req,
f => f,
);
}
public async BulkRemoveUserGrant(
idsList: string[],
): Promise<Empty> {
const req = new UserGrantRemoveBulk();
req.setIdsList(idsList);
return await this.request(
c => c.bulkRemoveUserGrant,
req,
f => f,
);
}
//
public async ApplicationChanges(id: string, limit: number, offset: number): Promise<Changes> { public async ApplicationChanges(id: string, limit: number, offset: number): Promise<Changes> {
const req = new ChangeRequest(); const req = new ChangeRequest();
req.setId(id); req.setId(id);

View File

@ -19,7 +19,9 @@ import {
ProjectGrant, ProjectGrant,
ProjectGrantCreate, ProjectGrantCreate,
ProjectGrantID, ProjectGrantID,
ProjectGrantMember,
ProjectGrantMemberAdd, ProjectGrantMemberAdd,
ProjectGrantMemberChange,
ProjectGrantMemberRemove, ProjectGrantMemberRemove,
ProjectGrantMemberRoles, ProjectGrantMemberRoles,
ProjectGrantMemberSearchQuery, ProjectGrantMemberSearchQuery,
@ -246,6 +248,24 @@ export class ProjectService {
); );
} }
public async ChangeProjectGrantMember(
projectId: string,
grantId: string,
userId: string,
rolesList: string[],
): Promise<ProjectGrantMember> {
const req = new ProjectGrantMemberChange();
req.setProjectId(projectId);
req.setGrantId(grantId);
req.setUserId(userId);
req.setRolesList(rolesList);
return await this.request(
c => c.changeProjectGrantMember,
req,
f => f,
);
}
public async SearchProjectGrantMembers( public async SearchProjectGrantMembers(
projectId: string, projectId: string,
grantId: string, grantId: string,

View File

@ -3,7 +3,7 @@
"HOME": { "HOME": {
"TITLE": "zitadel", "TITLE": "zitadel",
"SECURITYANDPRIVACY": "Datenschutz und Personalisierung", "SECURITYANDPRIVACY": "Datenschutz und Personalisierung",
"SECURITYANDPRIVACY_DESC": "Sie können die Daten in Ihrem Caos-Konto sehen und auswählen, welche Aktivitäten gespeichert werden, um Caos für sich zu personalisieren", "SECURITYANDPRIVACY_DESC": "Sie können die Daten in Ihrem Zitadel Konto sehen und auswählen, welche Aktivitäten gespeichert werden, um Zitadel für sich zu personalisieren",
"SECURITYANDPRIVACY_BUTTON": "Daten verwalten und personalisieren", "SECURITYANDPRIVACY_BUTTON": "Daten verwalten und personalisieren",
"PROTECTION": "Organisationsrichtlinien", "PROTECTION": "Organisationsrichtlinien",
"PROTECTION_DESC": "Verwalten Sie Ihre Organisationsrichtlinien. Entdecken Sie einige vorgefertigte Lösungen, die Ihnen Zeit sparen und Sicherheit gewährleisten.", "PROTECTION_DESC": "Verwalten Sie Ihre Organisationsrichtlinien. Entdecken Sie einige vorgefertigte Lösungen, die Ihnen Zeit sparen und Sicherheit gewährleisten.",
@ -52,7 +52,7 @@
}, },
"USER": { "USER": {
"TITLE": "Persönliche Informationen", "TITLE": "Persönliche Informationen",
"DESCRIPTION": "Hier können Sie Ihre Daten sowie die Datenschutz- und Sicherheitseinstellungen verwalten, um Caos optimal an Ihre Bedürfnisse anzupassen", "DESCRIPTION": "Hier können Sie Ihre Daten sowie die Datenschutz- und Sicherheitseinstellungen verwalten, um Zitadel optimal an Ihre Bedürfnisse anzupassen",
"PAGES": { "PAGES": {
"LIST": "Benutzer", "LIST": "Benutzer",
"TITLE": "Benutzer", "TITLE": "Benutzer",
@ -112,18 +112,19 @@
"TITLE": "Profil", "TITLE": "Profil",
"EMAIL": "Email", "EMAIL": "Email",
"PHONE": "Phonenumber", "PHONE": "Phonenumber",
"DESCRIPTION": "Hier können Sie Ihre Daten sowie die Datenschutz- und Sicherheitseinstellungen verwalten, um Caos optimal an Ihre Bedürfnisse anzupassen", "DESCRIPTION": "Hier können Sie Ihre Daten sowie die Datenschutz- und Sicherheitseinstellungen verwalten, um Zitadel optimal an Ihre Bedürfnisse anzupassen",
"USERNAME": "Benutzername", "USERNAME": "Benutzername",
"FIRSTNAME": "Vorname", "FIRSTNAME": "Vorname",
"LASTNAME": "Nachname", "LASTNAME": "Nachname",
"NICKNAME": "Spitzname", "NICKNAME": "Spitzname",
"DISPLAYNAME": "Anzeigename", "DISPLAYNAME": "Anzeigename",
"PREFERRED_LANGUAGE": "Sprache", "PREFERRED_LANGUAGE": "Sprache",
"GENDER": "Geschlecht" "GENDER": "Geschlecht",
"PASSWORD":"Passwort"
}, },
"PASSWORD": { "PASSWORD": {
"TITLE": "Passwort", "TITLE": "Passwort",
"DESCRIPTION": "Das Password muss aus mindestens 8 Zeichen bestehen und einen Grossbuchstaben, ein Sonderzeichen und eine Zahl enthalten. Die maximale Anzahl an Zeichen ist 72!", "DESCRIPTION": "Beachten Sie, dass das Password der von Ihrer Organisation definierten Richtlinie entsprechen muss.",
"OLD": "Aktuelles Passwort", "OLD": "Aktuelles Passwort",
"NEW": "Neues Passwort", "NEW": "Neues Passwort",
"CONFIRM": "Neues Passwort Wiederholung", "CONFIRM": "Neues Passwort Wiederholung",

View File

@ -3,7 +3,7 @@
"HOME": { "HOME": {
"TITLE": "zitadel", "TITLE": "zitadel",
"SECURITYANDPRIVACY": "Data protection and personalization", "SECURITYANDPRIVACY": "Data protection and personalization",
"SECURITYANDPRIVACY_DESC": "You can view the data in your Caos account and choose which activities are saved in order to personalize Caos for you", "SECURITYANDPRIVACY_DESC": "You can view the data in your Zitadel account and choose which activities are saved in order to personalize Zitadel for you",
"SECURITYANDPRIVACY_BUTTON": "Personalize data", "SECURITYANDPRIVACY_BUTTON": "Personalize data",
"PROTECTION": "Organizational Policies", "PROTECTION": "Organizational Policies",
"PROTECTION_DESC": "Manage your organizational guidelines. Explore some pre-packaged solutions that save you time and ensure security.", "PROTECTION_DESC": "Manage your organizational guidelines. Explore some pre-packaged solutions that save you time and ensure security.",
@ -112,18 +112,19 @@
"TITLE": "Profile", "TITLE": "Profile",
"EMAIL": "Email", "EMAIL": "Email",
"PHONE": "Phonenumber", "PHONE": "Phonenumber",
"DESCRIPTION": "Here you can manage your data as well as data protection and security settings in order to optimally adapt Caos to your needs", "DESCRIPTION": "Here you can manage your data as well as data protection and security settings in order to optimally adapt Zitadel to your needs",
"USERNAME": "Username", "USERNAME": "Username",
"FIRSTNAME": "Firstname", "FIRSTNAME": "Firstname",
"LASTNAME": "Lastname", "LASTNAME": "Lastname",
"NICKNAME": "Nickname", "NICKNAME": "Nickname",
"DISPLAYNAME": "Displayname", "DISPLAYNAME": "Displayname",
"PREFERRED_LANGUAGE": "Language", "PREFERRED_LANGUAGE": "Language",
"GENDER": "Gender" "GENDER": "Gender",
"PASSWORD":"Password"
}, },
"PASSWORD": { "PASSWORD": {
"TITLE": "Password", "TITLE": "Password",
"DESCRIPTION": "The password must consist of at least 8 characters and contain a capital letter, a special character and a number. The maximum length is 72.", "DESCRIPTION": "Note that the possword must correspond to the policy your organization has set.",
"OLD": "Current Password", "OLD": "Current Password",
"NEW": "New Password", "NEW": "New Password",
"CONFIRM": "Password Confirmation", "CONFIRM": "Password Confirmation",
@ -137,7 +138,7 @@
"EMAIL": "Email", "EMAIL": "Email",
"PHONE": "Phonenumber", "PHONE": "Phonenumber",
"LOGINMETHODS": { "LOGINMETHODS": {
"TITLE": "Methods to check your identity", "TITLE": "Methods to access your identity",
"DESCRIPTION": "This allows us to confirm your identity when you register or to contact you if suspicious activity is detected in your account", "DESCRIPTION": "This allows us to confirm your identity when you register or to contact you if suspicious activity is detected in your account",
"EMAIL": { "EMAIL": {
"TITLE": "Email", "TITLE": "Email",