feat: console flat navigation, settings (#3581)

* instance routing

* instance naming

* org list

* rm isonsystem

* breadcrumb  type

* routing

* instance members

* fragment refresh org

* settings pages

* settings list, sidenav grouping, i18n

* org-settings, policy changes

* lint

* grid

* rename grid

* fallback to general

* cleanup

* general settings, remove cards

* sidenav for settings, label policy

* i18n

* header, nav backbuild

* general, project nav rehaul

* login text background adapt

* org nav anim

* org, instance settings, fix policy layout, roles

* i18n, active route for project

* lint
This commit is contained in:
Max Peintner
2022-05-09 15:01:36 +02:00
committed by GitHub
parent 94e420bb24
commit 06e3330d2e
188 changed files with 4046 additions and 3639 deletions
@@ -0,0 +1,106 @@
<cnsl-refresh-table
*ngIf="dataSource"
[hideRefresh]="true"
(refreshed)="refresh()"
[dataSize]="dataSource.data.length"
[loading]="loading$ | async"
>
<cnsl-filter-org actions (filterChanged)="applySearchQuery($any($event))" (filterOpen)="filterOpen = $event">
</cnsl-filter-org>
<ng-template actions cnslHasRole [hasRole]="['org.create', 'iam.write']">
<a [routerLink]="['/org', 'create']" color="primary" mat-raised-button class="cnsl-action-button">
<mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>
<cnsl-action-keys (actionTriggered)="gotoRouterLink(['/org', 'create'])"> </cnsl-action-keys>
</a>
</ng-template>
<table [dataSource]="dataSource" mat-table class="table" aria-label="Elements">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.ACTIVE' | translate }}
</th>
<td class="selection" mat-cell *matCellDef="let org">
<mat-radio-button (change)="selectOrg(org)" color="primary" [checked]="org.id === activeOrg.id"> </mat-radio-button>
</td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>{{ 'ORG.PAGES.ID' | translate }}</th>
<td mat-cell *matCellDef="let org" (change)="selectOrg(org)">{{ org.id }}</td>
</ng-container>
<ng-container matColumnDef="primaryDomain">
<th mat-header-cell *matHeaderCellDef>{{ 'ORG.PAGES.PRIMARYDOMAIN' | translate }}</th>
<td mat-cell *matCellDef="let org">
<span>{{ org.primaryDomain }}</span>
<button
color="primary"
class="cpy-button"
mat-icon-button
[disabled]="copied === org.primaryDomain"
[matTooltip]="(copied !== org.primaryDomain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="org.primaryDomain"
(copiedValue)="copied = $event"
>
<i *ngIf="copied !== org.primaryDomain" class="las la-clipboard"></i>
<i *ngIf="copied === org.primaryDomain" class="las la-clipboard-check"></i>
</button>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.NAME' | translate }}
</th>
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">{{ org.name }}</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef>{{ 'ORG.PAGES.STATE' | translate }}</th>
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">
<span
class="state"
[ngClass]="{
active: org.state === OrgState.ORG_STATE_ACTIVE,
inactive: org.state === OrgState.ORG_STATE_INACTIVE
}"
*ngIf="org.state"
>{{ 'ORG.STATE.' + org.state | translate }}</span
>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.CREATIONDATE' | translate }}
</th>
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">
{{ org.details?.creationDate | timestampToDate | localizedDate: 'fromNow' }}
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.DATECHANGED' | translate }}
</th>
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">
{{ org.details?.changeDate | timestampToDate | localizedDate: 'fromNow' }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<cnsl-paginator
#paginator
class="paginator"
[timestamp]="timestamp"
[length]="totalResult || 0"
[pageSize]="initialLimit"
[pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"
></cnsl-paginator>
</cnsl-refresh-table>
@@ -0,0 +1,39 @@
td {
cursor: pointer;
.cpy-button {
visibility: hidden;
i {
pointer-events: none;
}
}
&:hover {
.cpy-button {
visibility: visible;
}
}
}
.pointer {
outline: none;
cursor: pointer;
}
.filter {
margin-left: 1rem;
}
th {
.search-button {
visibility: hidden;
}
&:hover,
&.search-active {
.search-button {
visibility: visible;
}
}
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrgTableComponent } from './org-table.component';
describe('OrgsComponent', () => {
let component: OrgTableComponent;
let fixture: ComponentFixture<OrgTableComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrgTableComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(OrgTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,100 @@
import { Component, Input, ViewChild } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, catchError, finalize, from, map, Observable, of } from 'rxjs';
import { Org, OrgQuery, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
enum OrgListSearchKey {
NAME = 'NAME',
}
@Component({
selector: 'cnsl-org-table',
templateUrl: './org-table.component.html',
styleUrls: ['./org-table.component.scss'],
})
export class OrgTableComponent {
public orgSearchKey: OrgListSearchKey | undefined = undefined;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild('input') public filter!: Input;
public dataSource!: MatTableDataSource<Org.AsObject>;
public displayedColumns: string[] = ['name', 'state', 'primaryDomain', 'creationDate', 'changeDate'];
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public activeOrg!: Org.AsObject;
public OrgListSearchKey: any = OrgListSearchKey;
public initialLimit: number = 20;
public timestamp: Timestamp.AsObject | undefined = undefined;
public totalResult: number = 0;
public filterOpen: boolean = false;
public OrgState: any = OrgState;
public copied: string = '';
constructor(private authService: GrpcAuthService, private router: Router) {
this.loadOrgs(this.initialLimit, 0);
this.authService.getActiveOrg().then((org) => (this.activeOrg = org));
}
public loadOrgs(limit: number, offset: number, queries?: OrgQuery[]): void {
this.loadingSubject.next(true);
from(this.authService.listMyProjectOrgs(limit, offset, queries))
.pipe(
map((resp) => {
this.timestamp = resp.details?.viewTimestamp;
this.totalResult = resp.details?.totalResult ?? 0;
return resp.resultList;
}),
catchError(() => of([])),
finalize(() => this.loadingSubject.next(false)),
)
.subscribe((views) => {
this.dataSource = new MatTableDataSource(views);
});
}
public selectOrg(item: Org.AsObject, event?: any): void {
this.authService.setActiveOrg(item);
}
public refresh(): void {
this.loadOrgs(this.paginator.length, this.paginator.pageSize * this.paginator.pageIndex);
}
public applySearchQuery(searchQueries: OrgQuery[]): void {
this.loadOrgs(this.paginator.pageSize, this.paginator.pageSize * this.paginator.pageIndex, searchQueries);
}
public setFilter(key: OrgListSearchKey): void {
setTimeout(() => {
if (this.filter) {
(this.filter as any).nativeElement.focus();
}
}, 100);
if (this.orgSearchKey !== key) {
this.orgSearchKey = key;
} else {
this.orgSearchKey = undefined;
this.refresh();
}
}
public setAndNavigateToOrg(org: Org.AsObject): void {
this.authService.setActiveOrg(org);
this.router.navigate(['/org']);
}
public changePage(event: PageEvent): void {
this.loadOrgs(event.pageSize, event.pageIndex * event.pageSize);
}
public gotoRouterLink(rL: any) {
this.router.navigate(rL);
}
}
@@ -0,0 +1,49 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatRadioModule } from '@angular/material/radio';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
import { ActionKeysModule } from '../action-keys/action-keys.module';
import { FilterOrgModule } from '../filter-org/filter-org.module';
import { InputModule } from '../input/input.module';
import { PaginatorModule } from '../paginator/paginator.module';
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
import { OrgTableComponent } from './org-table.component';
@NgModule({
declarations: [OrgTableComponent],
imports: [
CommonModule,
MatTableModule,
TranslateModule,
RefreshTableModule,
ActionKeysModule,
FilterOrgModule,
TimestampToDatePipeModule,
LocalizedDatePipeModule,
MatSortModule,
MatIconModule,
PaginatorModule,
HasRoleModule,
RouterModule,
MatButtonModule,
MatTooltipModule,
CopyToClipboardModule,
MatRadioModule,
InputModule,
FormsModule,
],
exports: [OrgTableComponent],
})
export class OrgTableModule {}