feat(console): integrate frontend (#95)

* feat: console frontend

* chore(dependabot): cycle and npm

* chore: rename citadel to zitadel, remove generated files

* chore: delete go files

* chore(frontend): ci steps

* chore: remove docker and envoy files

* chore: remove docker file

* chore: working dir

* chore: run proto build

* add console start

* chore: restructure folders

* chore: remove gui build

* statikFs

* generate proto for console

* add statik import

* import

* chore: try statik

* chore: path

* chore: path

* chore: script in root

* chore: order build steps

* chore: go get

* chore: folder traversal

* chore: non empty test file

* chore: gitignore

* chore: gitignore

* chore: statik path

* chore: switch to failing FE build

* fix: build

* fix: project-grant-test

* fix: rm test

* add statik.go

* go mod tidy

* chore: place test, seperate test from build

* chore: lint all the world

* chore: ci the world instead

* chore: tune docker

* chore: undo container test

* chore: fix run

* chore: docker build

* chore: test docker build

* chore: go build flags

* finaly

* fix caos_local

* go mod

Co-authored-by: Livio Amstutz <[email protected]>
Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
Florian Forster
2020-05-13 14:41:43 +02:00
committed by GitHub
co-authored by Livio Amstutz Max Peintner
parent 9e32740eb8
commit 92a294f5c8
375 changed files with 97826 additions and 52 deletions
@@ -0,0 +1,27 @@
<form>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Role Name</mat-label>
<input matInput *ngIf="singleOutput" type="text" placeholder="Search for the role name" #nameInput
[formControl]="myControl" [matAutocomplete]="auto" />
<mat-chip-list *ngIf="!singleOutput" #chipList aria-label="name selection">
<mat-chip class="chip" *ngFor="let selectedRole of roles" [selectable]="selectable" [removable]="removable"
(removed)="remove(selectedRole)">
{{selectedRole.displayName}}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
<input placeholder="Role Name" #nameInput [formControl]="myControl" [matAutocomplete]="auto"
[matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur" (matChipInputTokenEnd)="add($event)" />
</mat-chip-list>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
<mat-option *ngIf="isLoading" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let role of filteredRoles" [value]="role.key">
{{role.displayName}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
</form>
@@ -0,0 +1,3 @@
.full-width {
width: 100%;
}
@@ -0,0 +1,27 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchRolesAutocompleteComponent } from './search-roles-autocomplete.component';
describe('SearchProjectComponent', () => {
let component: SearchRolesAutocompleteComponent;
let fixture: ComponentFixture<SearchRolesAutocompleteComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SearchRolesAutocompleteComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchRolesAutocompleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,117 @@
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips';
import { from } from 'rxjs';
import { debounceTime, switchMap, tap } from 'rxjs/operators';
import {
ProjectRole,
ProjectRoleSearchKey,
ProjectRoleSearchQuery,
SearchMethod,
} from 'src/app/proto/generated/management_pb';
import { ProjectService } from 'src/app/services/project.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-search-roles-autocomplete',
templateUrl: './search-roles-autocomplete.component.html',
styleUrls: ['./search-roles-autocomplete.component.scss'],
})
export class SearchRolesAutocompleteComponent {
public selectable: boolean = true;
public removable: boolean = true;
public addOnBlur: boolean = true;
public separatorKeysCodes: number[] = [ENTER, COMMA];
public myControl: FormControl = new FormControl();
public names: string[] = [];
public roles: Array<ProjectRole.AsObject> = [];
public filteredRoles: Array<ProjectRole.AsObject> = [];
public isLoading: boolean = false;
@ViewChild('nameInput') public nameInput!: ElementRef<HTMLInputElement>;
@ViewChild('auto') public matAutocomplete!: MatAutocomplete;
@Input() public projectId: string = '';
@Input() public singleOutput: boolean = false;
@Output() public selectionChanged: EventEmitter<ProjectRole.AsObject[] | ProjectRole.AsObject> = new EventEmitter();
constructor(private projectService: ProjectService, private toast: ToastService) {
this.myControl.valueChanges
.pipe(
debounceTime(200),
tap(() => this.isLoading = true),
switchMap(value => {
const query = new ProjectRoleSearchQuery();
query.setKey(ProjectRoleSearchKey.PROJECTROLESEARCHKEY_DISPLAY_NAME);
query.setMethod(SearchMethod.SEARCHMETHOD_CONTAINS);
query.setValue(value);
return from(this.projectService.SearchProjectRoles(this.projectId, 10, 0, [query]));
}),
).subscribe((roles) => {
console.log(roles.toObject().resultList);
this.isLoading = false;
this.filteredRoles = roles.toObject().resultList;
}, error => {
console.log(error);
this.isLoading = false;
});
}
public displayFn(project?: ProjectRole.AsObject): string | undefined {
return project ? `${project.displayName}` : undefined;
}
public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) {
const input = event.input;
const value = event.value;
if ((value || '').trim()) {
const index = this.filteredRoles.findIndex((role) => {
if (role.key) {
return role.key === value;
}
});
if (index > -1) {
if (this.roles && this.roles.length > 0) {
this.roles.push(this.filteredRoles[index]);
} else {
this.roles = [this.filteredRoles[index]];
}
}
}
if (input) {
input.value = '';
}
}
}
public remove(role: ProjectRole.AsObject): void {
const index = this.roles.indexOf(role);
if (index >= 0) {
this.roles.splice(index, 1);
}
}
public selected(event: MatAutocompleteSelectedEvent): void {
const index = this.filteredRoles.findIndex((role) => role.key === event.option.value);
if (index !== -1) {
if (this.singleOutput) {
this.selectionChanged.emit(this.filteredRoles[index]);
} else {
if (this.roles && this.roles.length > 0) {
this.roles.push(this.filteredRoles[index]);
} else {
this.roles = [this.filteredRoles[index]];
}
this.selectionChanged.emit(this.roles);
this.nameInput.nativeElement.value = '';
this.myControl.setValue(null);
}
}
}
}
@@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { TranslateModule } from '@ngx-translate/core';
import { SearchRolesAutocompleteComponent } from './search-roles-autocomplete.component';
@NgModule({
declarations: [SearchRolesAutocompleteComponent],
imports: [
CommonModule,
MatAutocompleteModule,
MatChipsModule,
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
],
exports: [
SearchRolesAutocompleteComponent,
],
})
export class SearchRolesAutocompleteModule { }