mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-13 09:25:12 +00:00
fix(console): cleanup contributor module, move loading state to shared module, button visibility in light theme (#504)
* refreshtable component * project grant refresh table * project role refresh, user grant, i18n * lint * auth user mfa table * auth mfa table * rm unused 404 page, add mgmt mfa table * change light accent color * add actions to mfa table * user detail mfa table * clear selection on refresh, bind data length * member table * fix padding mfa table * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * z-index, new colors * new senf color * create stepper * app create stepper * i18n * i18n sections, header titles * lint * add pro mode * main contributor component * drop project members shared module * project detail members * org contributors, iam contributors * invert card and background colors in light design * changes card design * lighten meta background * account card radius * fix imports, global user email link * move spinner to refresh-table component * 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/de.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/en.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/de.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <[email protected]> * light background on light design * 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]> Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
co-authored by
Florian Forster
parent
f39d3a3cc7
commit
a9f0e15e65
@@ -29,7 +29,7 @@
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 1rem;
|
||||
border-radius: 50vh;
|
||||
margin: .5rem;
|
||||
|
||||
.mat-button-wrapper {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="groups">
|
||||
<span class="header">{{ title }}</span>
|
||||
<span class="sub-header">{{ description }}</span>
|
||||
<div class="people">
|
||||
<div class="img-list">
|
||||
<ng-container *ngIf="totalResult < 10; else compact">
|
||||
<ng-container *ngFor="let member of membersSubject | async; index as i">
|
||||
<div (click)="emitShowDetail()" class="avatar-circle"
|
||||
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}"
|
||||
[ngStyle]="{'z-index': 100 - i}">
|
||||
<app-avatar *ngIf="member && (member.displayName || (member.firstName && member.lastName))"
|
||||
class="avatar dontcloseonclick"
|
||||
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
|
||||
[size]="32">
|
||||
</app-avatar>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #compact>
|
||||
<div (click)="emitShowDetail()" class="avatar-circle" matTooltip="Click to show detail">
|
||||
<span>{{totalResult}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<button class="add-img" (click)="emitAddMember()" [disabled]="disabled" mat-icon-button
|
||||
aria-label="Edit contributors">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+6
-6
@@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProjectContributorsComponent } from './project-contributors.component';
|
||||
import { ContributorsComponent } from './contributors.component';
|
||||
|
||||
describe('ProjectContributorsComponent', () => {
|
||||
let component: ProjectContributorsComponent;
|
||||
let fixture: ComponentFixture<ProjectContributorsComponent>;
|
||||
describe('ContributorsComponent', () => {
|
||||
let component: ContributorsComponent;
|
||||
let fixture: ComponentFixture<ContributorsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProjectContributorsComponent],
|
||||
declarations: [ContributorsComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectContributorsComponent);
|
||||
fixture = TestBed.createComponent(ContributorsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contributors',
|
||||
templateUrl: './contributors.component.html',
|
||||
styleUrls: ['./contributors.component.scss'],
|
||||
})
|
||||
export class ContributorsComponent {
|
||||
@Input() title: string = '';
|
||||
@Input() description: string = '';
|
||||
@Input() disabled: boolean = false;
|
||||
@Input() totalResult: number = 0;
|
||||
@Input() membersSubject!: BehaviorSubject<any[]>;
|
||||
@Output() addClicked: EventEmitter<void> = new EventEmitter();
|
||||
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
public emitAddMember(): void {
|
||||
this.addClicked.emit();
|
||||
}
|
||||
|
||||
public emitShowDetail(): void {
|
||||
this.showDetailClicked.emit();
|
||||
}
|
||||
}
|
||||
+7
-12
@@ -3,28 +3,23 @@ import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { MemberCreateDialogModule } from '../add-member-dialog/member-create-dialog.module';
|
||||
import { AvatarModule } from '../avatar/avatar.module';
|
||||
import { ProjectContributorsComponent } from './project-contributors.component';
|
||||
import { ContributorsComponent } from './contributors.component';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ProjectContributorsComponent],
|
||||
declarations: [ContributorsComponent],
|
||||
imports: [
|
||||
MemberCreateDialogModule,
|
||||
CommonModule,
|
||||
TranslateModule,
|
||||
MatTooltipModule,
|
||||
MatIconModule,
|
||||
MatButtonModule,
|
||||
AvatarModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
MatButtonModule,
|
||||
],
|
||||
exports: [
|
||||
ProjectContributorsComponent,
|
||||
ContributorsComponent,
|
||||
],
|
||||
})
|
||||
export class ProjectContributorsModule { }
|
||||
|
||||
export class ContributorsModule { }
|
||||
@@ -1,32 +0,0 @@
|
||||
<div class="groups">
|
||||
<span class="header">{{ 'PROJECT.MEMBER.TITLE' | translate }}</span>
|
||||
<span class="sub-header">{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}</span>
|
||||
<div class="people">
|
||||
<div class="img-list">
|
||||
<ng-container *ngIf="totalResult < 10; else compact">
|
||||
<ng-container *ngFor="let member of membersSubject | async; index as i">
|
||||
<div (click)="showDetail()" class="avatar-circle" [ngStyle]="{'z-index': 100 - i}"
|
||||
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}">
|
||||
<app-avatar *ngIf="member && member.firstName && member.lastName; else thumbavatar"
|
||||
class="avatar dontcloseonclick" [name]="member.firstName + ' '+ member.lastName"
|
||||
[size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #thumbavatar>
|
||||
<i class="avatar las la-tools"></i>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #compact>
|
||||
<div (click)="showDetail()" class="avatar-circle" matTooltip="Click to show detail">
|
||||
<span>{{totalResult}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<button class="add-img" (click)="openAddMember()"
|
||||
[disabled]="project?.state !== ProjectState.PROJECTSTATE_ACTIVE" mat-icon-button
|
||||
aria-label="Edit contributors">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,119 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { BehaviorSubject, from, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import {
|
||||
ProjectGrantView,
|
||||
ProjectMemberSearchResponse,
|
||||
ProjectMemberView,
|
||||
ProjectState,
|
||||
ProjectType,
|
||||
ProjectView,
|
||||
User,
|
||||
} from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { CreationType, MemberCreateDialogComponent } from '../../modules/add-member-dialog/member-create-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-contributors',
|
||||
templateUrl: './project-contributors.component.html',
|
||||
styleUrls: ['./project-contributors.component.scss'],
|
||||
})
|
||||
export class ProjectContributorsComponent implements OnInit {
|
||||
@Input() public project!: ProjectView.AsObject | ProjectGrantView.AsObject;
|
||||
@Input() public grantId: string = '';
|
||||
@Input() public projectType!: ProjectType;
|
||||
|
||||
@Input() public disabled: boolean = false;
|
||||
|
||||
public totalResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<ProjectMemberView.AsObject[]>
|
||||
= new BehaviorSubject<ProjectMemberView.AsObject[]>([]);
|
||||
public ProjectState: any = ProjectState;
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
public ProjectType: any = ProjectType;
|
||||
|
||||
constructor(private projectService: ProjectService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
private router: Router) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
const promise: Promise<ProjectMemberSearchResponse> | undefined =
|
||||
this.projectType === ProjectType.PROJECTTYPE_OWNED ?
|
||||
this.projectService.SearchProjectMembers(this.project.projectId, 100, 0) :
|
||||
this.projectType === ProjectType.PROJECTTYPE_GRANTED ?
|
||||
this.projectService.SearchProjectGrantMembers(this.project.projectId,
|
||||
this.project.projectId, 100, 0) : undefined;
|
||||
if (promise) {
|
||||
from(promise).pipe(
|
||||
map(resp => {
|
||||
this.totalResult = resp.toObject().totalResult;
|
||||
return resp.toObject().resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
).subscribe(members => {
|
||||
this.membersSubject.next(members);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public openAddMember(): void {
|
||||
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
|
||||
data: {
|
||||
// TODO replace
|
||||
creationType: this.projectType === ProjectType.PROJECTTYPE_OWNED ? CreationType.PROJECT_OWNED :
|
||||
ProjectType.PROJECTTYPE_GRANTED ? CreationType.PROJECT_GRANTED : ProjectType.PROJECTTYPE_OWNED,
|
||||
projectId: this.project.projectId,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
const users: User.AsObject[] = resp.users;
|
||||
const roles: string[] = resp.roles;
|
||||
|
||||
if (users && users.length && roles && roles.length) {
|
||||
users.forEach(user => {
|
||||
switch (this.projectType) {
|
||||
case ProjectType.PROJECTTYPE_OWNED:
|
||||
return this.projectService.AddProjectMember(this.project.projectId, user.id, roles)
|
||||
.then(() => {
|
||||
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
case ProjectType.PROJECTTYPE_GRANTED:
|
||||
return this.projectService.AddProjectGrantMember(
|
||||
this.project.projectId,
|
||||
this.grantId,
|
||||
user.id,
|
||||
roles,
|
||||
).then(() => {
|
||||
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public showDetail(): void {
|
||||
if (this.project?.state === ProjectState.PROJECTSTATE_ACTIVE) {
|
||||
if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
|
||||
this.router.navigate(['granted-projects', this.project.projectId, 'grant', this.grantId, 'members']);
|
||||
} else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
|
||||
this.router.navigate(['projects', this.project.projectId, 'members']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
|
||||
<app-refresh-table *ngIf="project" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
|
||||
[selection]="selection">
|
||||
[selection]="selection" [loading]="dataSource?.loading$ | async">
|
||||
<ng-template appHasRole actions
|
||||
[appHasRole]="['project.member.delete:' + project.projectId, 'project.member.delete']">
|
||||
<button (click)="removeProjectMemberSelection()" color="warn"
|
||||
@@ -29,9 +29,6 @@
|
||||
</ng-template>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="dataSource?.loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
<table mat-table class="background-style full-width-table" aria-label="Elements"
|
||||
[dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
|
||||
@@ -54,12 +54,6 @@
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<app-refresh-table *ngIf="projectId" (refreshed)="refreshPage()" [dataSize]="dataSource.totalResult"
|
||||
[selection]="selection">
|
||||
[selection]="selection" [loading]="dataSource.loading$ | async">
|
||||
<ng-template appHasRole [appHasRole]="['project.role.delete']" actions>
|
||||
<button color="warn" class="icon-button" [disabled]="disabled"
|
||||
matTooltip="{{'PROJECT.ROLE.DELETE' | translate}}" (click)="deleteSelectedRoles()" mat-icon-button
|
||||
@@ -15,10 +15,6 @@
|
||||
</ng-template>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
|
||||
<table [dataSource]="dataSource" mat-table class="full-width-table" matSort aria-label="Elements">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
|
||||
@@ -10,51 +10,45 @@
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
|
||||
td, th {
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.margin-neg {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
|
||||
td, th {
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.margin-neg {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pointer {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<mat-spinner *ngIf="loading" diameter="20"></mat-spinner>
|
||||
<button mat-icon-button (click)="emitRefresh()" class="icon-button" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
</button>
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export class RefreshTableComponent implements OnInit {
|
||||
@Input() public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
|
||||
@Input() public dataSize: number = 0;
|
||||
@Input() public emitRefreshAfterTimeoutInMs: number = 0;
|
||||
@Input() public loading: boolean = false;
|
||||
@Output() public refreshed: EventEmitter<void> = new EventEmitter();
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
@@ -19,6 +20,7 @@ import { RefreshTableComponent } from './refresh-table.component';
|
||||
TranslateModule,
|
||||
FormsModule,
|
||||
MatTooltipModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
exports: [
|
||||
RefreshTableComponent,
|
||||
|
||||
+3
-2
@@ -7,11 +7,12 @@
|
||||
font-size: .8rem;
|
||||
margin: 0;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
a {
|
||||
color: white;
|
||||
color: #4072b4;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
color: #6992c9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<app-refresh-table (refreshed)="changePage()" [dataSize]="dataSource.totalResult" [selection]="selection">
|
||||
<app-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()"
|
||||
[dataSize]="dataSource.totalResult" [selection]="selection">
|
||||
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button actions
|
||||
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && allowDelete">
|
||||
<i class="las la-trash"></i>
|
||||
@@ -9,9 +10,6 @@
|
||||
</a>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="dataSource.loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
<table mat-table multiTemplateDataRows class="full-width-table" aria-label="Elements" [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
background-color: #2d2e30;
|
||||
|
||||
Reference in New Issue
Block a user