feat(console): user memberships, generic member create dialog, fix user autocomplete emitter (#606)

* load manager mgmtservice, user service

* add org memberships

* membership component, generic member creation

* refactor member create dialog

* project autocomplete context

* create batch managers in user component

* project context wrapper

* emit on user removal, preselect user on init

* membership avatar style, service

* auth user memberships, navigate to target

* cursor fix, avatar gen

* lint

* i18n fix

* remove role translations

* membership detail page, i18n

* fix role label i18n, after view init loader

* remove projectid from grant remove

* fix iam race condition

* refresh table ts, fix no permission project search

* change membership colors

* refresh table everywhere, replace assets, routing

* fix logo header size

* lint, fix project grant removal

* timestmp for p mem, user list, grants, p list (#615)

* npm audit

* update deps, resolve vulnerability

* fix tslint config

* update lock

* load 20 changes at once

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Florian Forster <[email protected]>

* membership i18n

Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
Max Peintner
2020-08-24 08:48:47 +02:00
committed by GitHub
co-authored by Florian Forster
parent d49fee23bf
commit 193cfb45f6
84 changed files with 1972 additions and 2911 deletions
@@ -132,7 +132,6 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
data: {
creationType: CreationType.PROJECT_GRANTED,
projectId: this.project.projectId,
},
width: '400px',
});
@@ -9,88 +9,66 @@
</div>
<div *ngIf="!grid && grantedProjectList">
<div class="table-header-row">
<div class="col">
<ng-container *ngIf="!selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
<span class="count">{{dataSource?.data?.length}}</span>
</ng-container>
<ng-container *ngIf="selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
<span class="count">{{selection?.selected?.length}}</span>
</ng-container>
<app-refresh-table (refreshed)="refreshPage()" [dataSize]="totalResult" [timestamp]="viewTimestamp"
[selection]="selection" [loading]="loading$ | async">
<div class="table-wrapper">
<table class="table background-style" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.NAME' | translate }} </th>
<td mat-cell *matCellDef="let project"> {{project.projectName}} </td>
</ng-container>
<ng-container matColumnDef="resourceOwnerName">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.RESOURCEOWNER' | translate }} </th>
<td class="pointer" mat-cell *matCellDef="let project">
{{project.resourceOwnerName}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.STATE' | translate }} </th>
<td mat-cell *matCellDef="let project"><span
*ngIf="project.state">{{'PROJECT.STATE.'+project.state | translate}}</span></td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
[routerLink]="['/granted-projects', row.projectId, 'grant', row.id]"></tr>
</table>
<mat-paginator class="paginator background-style" #paginator [length]="totalResult" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
<span class="fill-space"></span>
<div @list class="action-btns" *ngIf="selection.hasValue()">
<button @animate (click)="deactivateSelectedProjects()"
matTooltip="{{'PROJECT.TABLE.DEACTIVATE' | translate}}" class="icon-button" mat-icon-button>
<mat-icon svgIcon="mdi_light_off"></mat-icon>
</button>
<button @animate (click)="reactivateSelectedProjects()"
matTooltip="{{'PROJECT.TABLE.ACTIVATE' | translate}}" class="icon-button" mat-icon-button>
<mat-icon svgIcon="mdi_light_on"></mat-icon>
</button>
</div>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="(loading$ | async) || (loading$ | async)">
<mat-spinner diameter="50"></mat-spinner>
</div>
<table class="table background-style" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.NAME' | translate }} </th>
<td mat-cell *matCellDef="let project"> {{project.projectName}} </td>
</ng-container>
<ng-container matColumnDef="resourceOwnerName">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.RESOURCEOWNER' | translate }} </th>
<td class="pointer" mat-cell *matCellDef="let project">
{{project.resourceOwnerName}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.STATE' | translate }} </th>
<td mat-cell *matCellDef="let project"><span
*ngIf="project.state">{{'PROJECT.STATE.'+project.state | translate}}</span></td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
[routerLink]="['/granted-projects', row.projectId, 'grant', row.id]"></tr>
</table>
<mat-paginator class="paginator background-style" [length]="totalResult" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
</app-refresh-table>
</div>
@@ -10,47 +10,9 @@
}
}
.table-header-row {
display: flex;
align-items: center;
.col {
display: flex;
flex-direction: column;
.desc {
font-size: .8rem;
color: #8795a1;
}
.count {
font-size: 2rem;
}
}
.fill-space {
flex: 1;
}
.action-btns {
display: flex;
align-items: center;
}
.icon-button {
margin-right: .5rem;
}
}
.table-wrapper {
overflow: auto;
.spinner-container {
display: flex;
align-items: center;
justify-content: center;
}
.table,
.paginator {
width: 100%;
@@ -1,10 +1,11 @@
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
import { SelectionModel } from '@angular/cdk/collections';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { ProjectGrantView } from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service';
@@ -36,8 +37,11 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class GrantedProjectListComponent implements OnInit, OnDestroy {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public dataSource: MatTableDataSource<ProjectGrantView.AsObject> =
new MatTableDataSource<ProjectGrantView.AsObject>();
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public grantedProjectList: ProjectGrantView.AsObject[] = [];
public displayedColumns: string[] = ['select', 'name', 'resourceOwnerName', 'state', 'creationDate', 'changeDate'];
@@ -86,8 +90,12 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
this.projectService.SearchGrantedProjects(limit, offset).then(res => {
this.grantedProjectList = res.toObject().resultList;
this.totalResult = res.toObject().totalResult;
const response = res.toObject();
this.grantedProjectList = response.resultList;
this.totalResult = response.totalResult;
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
}
if (this.totalResult > 5) {
this.grid = false;
}
@@ -100,28 +108,8 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
});
}
public reactivateSelectedProjects(): void {
const promises = this.selection.selected.map(project => {
this.projectService.ReactivateProject(project.id);
});
Promise.all(promises).then(() => {
this.toast.showInfo('PROJECT.TOAST.REACTIVATED', true);
}).catch(error => {
this.toast.showError(error);
});
}
public deactivateSelectedProjects(): void {
const promises = this.selection.selected.map(project => {
this.projectService.DeactivateProject(project.id);
});
Promise.all(promises).then(() => {
this.toast.showInfo('PROJECT.TOAST.DEACTIVATED', true);
}).catch(error => {
this.toast.showError(error);
});
public refreshPage(): void {
this.selection.clear();
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
}
@@ -22,6 +22,7 @@ import { ChangesModule } from 'src/app/modules/changes/changes.module';
import { ContributorsModule } from 'src/app/modules/contributors/contributors.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
@@ -73,6 +74,7 @@ import { GrantedProjectsComponent } from './granted-projects.component';
SharedModule,
LocalizedDatePipeModule,
MemberCreateDialogModule,
RefreshTableModule,
],
})
export class GrantedProjectsModule { }
@@ -1,4 +1,5 @@
import { DataSource } from '@angular/cdk/collections';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { Application } from 'src/app/proto/generated/management_pb';
@@ -11,6 +12,8 @@ import { ProjectService } from 'src/app/services/project.service';
*/
export class ProjectApplicationsDataSource extends DataSource<Application.AsObject> {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public appsSubject: BehaviorSubject<Application.AsObject[]> = new BehaviorSubject<Application.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -25,8 +28,12 @@ export class ProjectApplicationsDataSource extends DataSource<Application.AsObje
this.loadingSubject.next(true);
from(this.projectService.SearchApplications(projectId, pageSize, offset)).pipe(
map(resp => {
this.totalResult = resp.toObject().totalResult;
return resp.toObject().resultList;
const response = resp.toObject();
this.totalResult = response.totalResult;
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
}
return response.resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
@@ -1,5 +1,5 @@
<app-refresh-table [loading]="dataSource.loading$ | async" [selection]="selection" (refreshed)="refreshPage()"
[dataSize]="dataSource.totalResult">
[dataSize]="dataSource.totalResult" [timestamp]="dataSource?.viewTimestamp">
<ng-template appHasRole [appHasRole]="['project.app.write']" actions>
<a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']"
color="primary" mat-raised-button>
@@ -1,4 +1,5 @@
import { DataSource } from '@angular/cdk/collections';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { ProjectGrant } from 'src/app/proto/generated/management_pb';
@@ -11,6 +12,7 @@ import { ProjectService } from 'src/app/services/project.service';
*/
export class ProjectGrantsDataSource extends DataSource<ProjectGrant.AsObject> {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public grantsSubject: BehaviorSubject<ProjectGrant.AsObject[]> = new BehaviorSubject<ProjectGrant.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -25,8 +27,12 @@ export class ProjectGrantsDataSource extends DataSource<ProjectGrant.AsObject> {
this.loadingSubject.next(true);
from(this.projectService.SearchProjectGrants(projectId, pageSize, offset)).pipe(
map(resp => {
this.totalResult = resp.toObject().totalResult;
return resp.toObject().resultList;
const response = resp.toObject();
this.totalResult = response.totalResult;
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
}
return response.resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
@@ -1,5 +1,5 @@
<app-refresh-table [loading]="dataSource?.loading$ | async" *ngIf="projectId" (refreshed)="loadGrantsPage()"
[dataSize]="dataSource.totalResult" [selection]="selection">
[dataSize]="dataSource.totalResult" [selection]="selection" [timestamp]="dataSource?.viewTimestamp">
<ng-template appHasRole [appHasRole]="['project.grant.member.delete:'+projectId, 'project.grant.member.delete']"
actions>
<button (click)="deleteSelectedGrants()" [disabled]="disabled" mat-icon-button *ngIf="selection.hasValue()"
@@ -8,78 +8,66 @@
</button>
</div>
<div *ngIf="!grid && ownedProjectList">
<div class="table-header-row">
<div class="col">
<ng-container *ngIf="!selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
<span class="count">{{dataSource?.data?.length}}</span>
</ng-container>
<ng-container *ngIf="selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
<span class="count">{{selection?.selected?.length}}</span>
</ng-container>
</div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['project.write']">
<app-refresh-table (refreshed)="refreshPage()" [dataSize]="totalResult" [timestamp]="viewTimestamp"
[selection]="selection" [loading]="loading$ | async">
<ng-template actions appHasRole [appHasRole]="['project.write']">
<a class="add-button" [routerLink]="[ '/projects', 'create']" color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</ng-template>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="(loading$ | async) || (loading$ | async)">
<mat-spinner diameter="50"></mat-spinner>
<div class="table-wrapper">
<table class="table background-style" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.NAME' | translate }} </th>
<td mat-cell *matCellDef="let project"> {{project.name}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.STATE' | translate }} </th>
<td mat-cell *matCellDef="let project"><span
*ngIf="project.state">{{'PROJECT.STATE.'+project.state | translate}}</span></td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
[routerLink]="['/projects', row.projectId]"></tr>
</table>
<mat-paginator class="paginator background-style" [length]="totalResult" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
<table class="table background-style" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.NAME' | translate }} </th>
<td mat-cell *matCellDef="let project"> {{project.name}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.STATE' | translate }} </th>
<td mat-cell *matCellDef="let project"><span
*ngIf="project.state">{{'PROJECT.STATE.'+project.state | translate}}</span></td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let project">
<span
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm'}}</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
[routerLink]="['/projects', row.projectId]"></tr>
</table>
<mat-paginator class="paginator background-style" [length]="totalResult" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
</div>
</app-refresh-table>
</div>
@@ -18,51 +18,9 @@ h1 {
}
}
.table-header-row {
display: flex;
align-items: center;
.col {
display: flex;
flex-direction: column;
.desc {
font-size: .8rem;
color: #8795a1;
}
.count {
font-size: 2rem;
}
}
.fill-space {
flex: 1;
}
.action-btns {
display: flex;
align-items: center;
}
.icon-button {
margin-right: .5rem;
}
.add-button {
border-radius: .5rem;
}
}
.table-wrapper {
overflow: auto;
.spinner-container {
display: flex;
align-items: center;
justify-content: center;
}
.table,
.paginator {
width: 100%;
@@ -1,10 +1,11 @@
import { animate, animateChild, query, stagger, style, transition, trigger } from '@angular/animations';
import { SelectionModel } from '@angular/cdk/collections';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { ProjectView } from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service';
@@ -36,9 +37,13 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class OwnedProjectListComponent implements OnInit, OnDestroy {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public dataSource: MatTableDataSource<ProjectView.AsObject> =
new MatTableDataSource<ProjectView.AsObject>();
@ViewChild(MatPaginator) public paginator!: MatPaginator;
public ownedProjectList: ProjectView.AsObject[] = [];
public displayedColumns: string[] = ['select', 'name', 'state', 'creationDate', 'changeDate'];
public selection: SelectionModel<ProjectView.AsObject> = new SelectionModel<ProjectView.AsObject>(true, []);
@@ -86,11 +91,15 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
this.projectService.SearchProjects(limit, offset).then(res => {
this.ownedProjectList = res.toObject().resultList;
this.totalResult = res.toObject().totalResult;
const response = res.toObject();
this.ownedProjectList = response.resultList;
this.totalResult = response.totalResult;
if (this.totalResult > 10) {
this.grid = false;
}
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
}
this.dataSource.data = this.ownedProjectList;
this.loadingSubject.next(false);
}).catch(error => {
@@ -126,4 +135,9 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
this.toast.showError(error);
});
}
public refreshPage(): void {
this.selection.clear();
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
}
@@ -17,6 +17,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
import { CardModule } from 'src/app/modules/card/card.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
@@ -61,6 +62,7 @@ import { OwnedProjectsComponent } from './owned-projects.component';
TimestampToDatePipeModule,
LocalizedDatePipeModule,
SharedModule,
RefreshTableModule,
],
})
export class OwnedProjectsModule { }
@@ -1,4 +1,5 @@
import { DataSource } from '@angular/cdk/collections';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { ProjectMember } from 'src/app/proto/generated/management_pb';
@@ -11,6 +12,8 @@ import { ProjectService } from 'src/app/services/project.service';
*/
export class ProjectGrantMembersDataSource extends DataSource<ProjectMember.AsObject> {
public totalResult: number = 0;
public viewTimestamp!: Timestamp.AsObject;
public membersSubject: BehaviorSubject<ProjectMember.AsObject[]> = new BehaviorSubject<ProjectMember.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@@ -28,8 +31,12 @@ export class ProjectGrantMembersDataSource extends DataSource<ProjectMember.AsOb
from(this.projectService.SearchProjectGrantMembers(projectId,
grantId, pageSize, offset)).pipe(
map(resp => {
this.totalResult = resp.toObject().totalResult;
return resp.toObject().resultList;
const response = resp.toObject();
this.totalResult = response.totalResult;
if (response.viewTimestamp) {
this.viewTimestamp = response.viewTimestamp;
}
return response.resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
@@ -1,15 +1,5 @@
<div class="table-header-row">
<div class="col">
<ng-container *ngIf="!selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
<span class="count">{{dataSource?.membersSubject.value.length}}</span>
</ng-container>
<ng-container *ngIf="selection.hasValue()">
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
<span class="count">{{selection?.selected?.length}}</span>
</ng-container>
</div>
<span class="fill-space"></span>
<app-refresh-table (refreshed)="refreshPage()" [dataSize]="dataSource.totalResult"
[timestamp]="dataSource?.viewTimestamp" [selection]="selection" [loading]="dataSource.loading$ | async">
<button (click)="removeProjectMemberSelection()" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}"
class="icon-button" color="warn" mat-icon-button *ngIf="selection.hasValue()">
<i class="las la-trash"></i>
@@ -18,74 +8,74 @@
mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</div>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="dataSource?.loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
<div class="table-wrapper">
<div class="spinner-container" *ngIf="dataSource?.loading$ | async">
<mat-spinner diameter="50"></mat-spinner>
</div>
<table mat-table class="table" aria-label="Elements"
[ngClass]="{'background-style': type == ProjectType.PROJECTTYPE_OWNED}" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.firstName}} </td>
</ng-container>
<ng-container matColumnDef="lastname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.lastName}} </td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.userName}} </td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.email}}
</td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
<td class="pointer" mat-cell *matCellDef="let member">
<mat-form-field class="form-field" appearance="outline" *ngIf="projectId">
<mat-label>{{ 'PROJECT.MEMBER.ROLES' | translate }}</mat-label>
<mat-select [(ngModel)]="member.rolesList" multiple [disabled]="disabled"
(selectionChange)="updateRoles(member, $event)">
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
{{ role }}
</mat-option>
</mat-select>
</mat-form-field>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
<mat-paginator class="paginator" [ngClass]="{'background-style': type == ProjectType.PROJECTTYPE_OWNED}"
#paginator [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div>
<table mat-table class="table" aria-label="Elements"
[ngClass]="{'background-style': type == ProjectType.PROJECTTYPE_OWNED}" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.firstName}} </td>
</ng-container>
<ng-container matColumnDef="lastname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.lastName}} </td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.userName}} </td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.email}}
</td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
<td class="pointer" mat-cell *matCellDef="let member">
<mat-form-field class="form-field" appearance="outline" *ngIf="projectId">
<mat-label>{{ 'PROJECT.MEMBER.ROLES' | translate }}</mat-label>
<mat-select [(ngModel)]="member.rolesList" multiple [disabled]="disabled"
(selectionChange)="updateRoles(member, $event)">
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
{{ 'ROLES.'+role | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
</table>
<mat-paginator class="paginator" [ngClass]="{'background-style': type == ProjectType.PROJECTTYPE_OWNED}" #paginator
[pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
</mat-paginator>
</div>
</app-refresh-table>
@@ -1,44 +1,7 @@
.table-header-row {
display: flex;
align-items: center;
.col {
display: flex;
flex-direction: column;
.desc {
font-size: .8rem;
color: #8795a1;
}
.count {
font-size: 2rem;
}
}
.fill-space {
flex: 1;
}
.icon-button {
margin-right: .5rem;
}
.add-button {
border-radius: .5rem;
}
}
.table-wrapper {
overflow: auto;
.spinner-container {
display: flex;
align-items: center;
justify-content: center;
}
.table,
.paginator {
td,
@@ -94,14 +94,6 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
}));
}
public removeMember(member: ProjectMember.AsObject): void {
this.projectService.RemoveProjectGrantMember(this.projectId, this.grantId, member.userId).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.membersSubject.value.length;
@@ -150,4 +142,9 @@ export class ProjectGrantMembersComponent implements AfterViewInit, OnInit {
this.toast.showError(error);
});
}
public refreshPage(): void {
this.selection.clear();
this.dataSource.loadGrantMembers(this.projectId, this.grantId, this.paginator.pageIndex, this.paginator.pageSize);
}
}
@@ -16,6 +16,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
import {
@@ -46,6 +47,7 @@ import { ProjectGrantMembersComponent } from './project-grant-members.component'
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
RefreshTableModule,
],
exports: [
ProjectGrantMembersComponent,