feat(console): org managers, refactor, bugfixes (#226)

* resourceowner header, i18n

* password policy validators, roles required field

* use angular pattern instead of custom validator

* user detail fixes, mfa qr code, add org

* use mgmt for mfa list

* fetch owned projects

* search project

* seperate owned from granted projects

* lint

* fix granted project grid

* refactor project detail

* disable zitadel apps

* refactor project contributors

* changed i18n

* hide meta nav

* mobile meta layout

* refactor contributor name

* refactor org contributors

* org i18n, org member detail view
This commit is contained in:
Max Peintner
2020-06-16 14:52:07 +02:00
committed by GitHub
parent 7a6ca24625
commit f2cdae9ea3
95 changed files with 5019 additions and 3413 deletions
@@ -8,7 +8,8 @@
<div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="accent" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.userName)">
<mat-icon class="small-avatar" svgIcon="mdi_account_circle_outline"></mat-icon>
<i class="small-avatar las la-user-circle"></i>
<div class="col">
<span class="title">{{user.userName}}</span>
<span class="email">{{'USER.STATE.'+user.authState | translate}}</span>
@@ -1,21 +1,21 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectMemberCreateDialogComponent } from './project-member-create-dialog.component';
import { MemberCreateDialogComponent } from './member-create-dialog.component';
describe('AddMemberDialogComponent', () => {
let component: ProjectMemberCreateDialogComponent;
let fixture: ComponentFixture<ProjectMemberCreateDialogComponent>;
let component: MemberCreateDialogComponent;
let fixture: ComponentFixture<MemberCreateDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProjectMemberCreateDialogComponent],
declarations: [MemberCreateDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectMemberCreateDialogComponent);
fixture = TestBed.createComponent(MemberCreateDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -10,11 +10,11 @@ export enum CreationType {
ORG = 2,
}
@Component({
selector: 'app-project-member-create-dialog',
templateUrl: './project-member-create-dialog.component.html',
styleUrls: ['./project-member-create-dialog.component.scss'],
selector: 'app-member-create-dialog',
templateUrl: './member-create-dialog.component.html',
styleUrls: ['./member-create-dialog.component.scss'],
})
export class ProjectMemberCreateDialogComponent {
export class MemberCreateDialogComponent {
public projectId: string = '';
public creationType!: CreationType;
public users: Array<User.AsObject> = [];
@@ -23,7 +23,7 @@ export class ProjectMemberCreateDialogComponent {
public memberRoleOptions: string[] = [];
constructor(
private projectService: ProjectService,
public dialogRef: MatDialogRef<ProjectMemberCreateDialogComponent>,
public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
toastService: ToastService,
) {
@@ -12,10 +12,10 @@ import {
OrgMemberRolesAutocompleteModule,
} from '../../pages/orgs/org-member-roles-autocomplete/org-member-roles-autocomplete.module';
import { SearchRolesAutocompleteModule } from '../search-roles-autocomplete/search-roles-autocomplete.module';
import { ProjectMemberCreateDialogComponent } from './project-member-create-dialog.component';
import { MemberCreateDialogComponent } from './member-create-dialog.component';
@NgModule({
declarations: [ProjectMemberCreateDialogComponent],
declarations: [MemberCreateDialogComponent],
imports: [
CommonModule,
MatDialogModule,
@@ -29,7 +29,7 @@ import { ProjectMemberCreateDialogComponent } from './project-member-create-dial
OrgMemberRolesAutocompleteModule,
],
entryComponents: [
ProjectMemberCreateDialogComponent,
MemberCreateDialogComponent,
],
})
export class ProjectMemberCreateDialogModule { }
export class MemberCreateDialogModule { }
@@ -11,7 +11,7 @@
.item {
box-sizing: border-box;
height: 60px;
height: 50px;
padding: .5rem;
margin: .25rem 0;
border-radius: .5rem;
@@ -26,8 +26,7 @@
.desc {
overflow-x: auto;
font-size: .9rem;
margin: 4px 0;
font-size: 14px;
}
}
@@ -1,8 +1,10 @@
<div class="meta-wrapper">
<div class="main-content">
<div class="main-content" [ngClass]="{'hidden': (hidden)}">
<ng-content></ng-content>
</div>
<div class="meta">
<div class="meta" [ngClass]="{'hidden': hidden}">
<button (click)="hidden = !hidden" color="accent" class="hide" mat-icon-button><i
class="las la-arrow-right"></i></button>
<ng-content class="meta-content" select="metainfo"></ng-content>
</div>
</div>
@@ -1,19 +1,27 @@
.meta-wrapper {
display: flex;
height: 100%;
height: 100%;
overflow-x: hidden;
transition: all .2s ease-in-out;
.main-content {
display: relative;
width: 100%;
overflow-y: auto;
&.hidden {
flex-basis: 100%;
}
}
.meta {
position: relative;
flex: 1 0 300px;
background: linear-gradient(to bottom right, #4072b410 20%,transparent 50%);
@media only screen and (min-width: 1500px) {
flex-basis: 400px;
}
// overflow-y: auto;
padding: 1rem;
.meta-content {
@@ -21,13 +29,58 @@
display: flex;
flex-direction: column;
}
&.hidden {
flex: 0 0 0 !important;
width: 0;
background: linear-gradient(to bottom right, #4072b450,transparent 50%);
.hide {
transform: rotate(180deg);
visibility: visible;
opacity: 1;
}
}
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 50%;
border-left: 2px solid #4072b4;
-webkit-border-image: -webkit-gradient(linear, left top, left bottom, from(#4072b4), to(#212224), color-stop(01, #212224)) 50 21;
border-image: -webkit-gradient(linear, left top, left bottom, from(#4072b4), to(#212224), color-stop(01, #212224)) 50 21;
}
.hide {
position: absolute;
left: -40px;
top: .5rem;
opacity: 0;
transition: all .3s ease-in-out;
i {
font-size: 1.5rem;
}
&:hover {
visibility: visible;
}
}
&:hover {
.hide {
opacity: 1;
}
}
}
@media only screen and (max-width: 700px) {
flex-direction: column;
.main-content, .meta {
overflow-y: visible;
}
}
// @media only screen and (max-width: 700px) {
// flex-direction: column;
// .main-content, .meta {
// overflow-y: visible;
// }
// }
}
@@ -1,4 +1,7 @@
import { BreakpointObserver } from '@angular/cdk/layout';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-meta-layout',
@@ -6,4 +9,16 @@ import { Component } from '@angular/core';
styleUrls: ['./meta-layout.component.scss'],
})
export class MetaLayoutComponent {
constructor(private breakpointObserver: BreakpointObserver) {
this.isSmallScreen$.subscribe(small => this.hidden = small);
}
public hidden: boolean = false;
public isSmallScreen$: Observable<boolean> = this.breakpointObserver
.observe('(max-width: 899px)')
.pipe(map(result => {
return result.matches;
}));
}
@@ -1,5 +1,7 @@
import { LayoutModule } from '@angular/cdk/layout';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MetaLayoutComponent } from './meta-layout.component';
@@ -9,6 +11,8 @@ import { MetaLayoutComponent } from './meta-layout.component';
declarations: [MetaLayoutComponent],
imports: [
CommonModule,
MatButtonModule,
LayoutModule,
],
exports: [MetaLayoutComponent],
})
@@ -70,8 +70,6 @@
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
@@ -6,9 +6,10 @@ import { MatChipInputEvent } from '@angular/material/chips';
import { from } from 'rxjs';
import { debounceTime, switchMap, tap } from 'rxjs/operators';
import {
GrantedProjectSearchKey,
GrantedProjectSearchQuery,
Project,
ProjectGrantView,
ProjectSearchKey,
ProjectSearchQuery,
SearchMethod,
} from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service';
@@ -25,21 +26,22 @@ export class SearchProjectAutocompleteComponent {
public separatorKeysCodes: number[] = [ENTER, COMMA];
public myControl: FormControl = new FormControl();
public names: string[] = [];
public projects: Array<Project.AsObject> = [];
public filteredProjects: Array<Project.AsObject> = [];
public projects: Array<ProjectGrantView.AsObject> = [];
public filteredProjects: Array<ProjectGrantView.AsObject> = [];
public isLoading: boolean = false;
@ViewChild('nameInput') public nameInput!: ElementRef<HTMLInputElement>;
@ViewChild('auto') public matAutocomplete!: MatAutocomplete;
@Input() public singleOutput: boolean = false;
@Output() public selectionChanged: EventEmitter<Project.AsObject[] | Project.AsObject> = new EventEmitter();
@Output() public selectionChanged: EventEmitter<ProjectGrantView.AsObject[] | ProjectGrantView.AsObject>
= new EventEmitter();
constructor(private projectService: ProjectService) {
this.myControl.valueChanges
.pipe(
debounceTime(200),
tap(() => this.isLoading = true),
switchMap(value => {
const query = new GrantedProjectSearchQuery();
query.setKey(GrantedProjectSearchKey.PROJECTSEARCHKEY_PROJECT_NAME);
const query = new ProjectSearchQuery();
query.setKey(ProjectSearchKey.PROJECTSEARCHKEY_PROJECT_NAME);
query.setValue(value);
query.setMethod(SearchMethod.SEARCHMETHOD_CONTAINS);
return from(this.projectService.SearchGrantedProjects(10, 0, [query]));
@@ -63,8 +65,8 @@ export class SearchProjectAutocompleteComponent {
if ((value || '').trim()) {
const index = this.filteredProjects.findIndex((project) => {
if (project.name) {
return project.name === value;
if (project.projectName) {
return project.projectName === value;
}
});
if (index > -1) {
@@ -82,7 +84,7 @@ export class SearchProjectAutocompleteComponent {
}
}
public remove(project: Project.AsObject): void {
public remove(project: ProjectGrantView.AsObject): void {
const index = this.projects.indexOf(project);
if (index >= 0) {