mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-12 00:55:12 +00:00
feat(console): identity providers and login policies (#722)
* idp list, idp create route * idp modules, lazy import, i18n, routing * generic service, i18n * seperate lockout, age policy component * seperate component modules * routing * enum class * login policy * iam policy grid * login policy providers * idps login policy * add idp dialog component * add idp to loginpolicy * delete idp config, iam policy grid * remove idp from loginpolicy * idp detail component, generic idp create * lint * idp detail clientid-secrets, issuer, scopes * hide clientsecret on update * rm background style, idp config * app tooltip fix * lint * dont refresh on idp select * Update console/src/app/modules/idp-create/idp-create.component.html Co-authored-by: Fabiennne <[email protected]> Co-authored-by: Fabi <[email protected]>
This commit is contained in:
co-authored by
Fabiennne
Fabi
parent
845026e43f
commit
58b01cdf3f
@@ -0,0 +1,85 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||
[timestamp]="idpResult?.viewTimestamp" [selection]="selection">
|
||||
<ng-template appHasRole [appHasRole]="['iam.write']" actions>
|
||||
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}" class="icon-button"
|
||||
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<mat-icon svgIcon="mdi_account_cancel"></mat-icon>
|
||||
</button>
|
||||
<button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="icon-button"
|
||||
mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<mat-icon svgIcon="mdi_account_check_outline"></mat-icon>
|
||||
</button>
|
||||
<button color="warn" (click)="removeSelectedIdps()" matTooltip="{{'IDP.DELETE' | translate}}"
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()" [disabled]="disabled">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<a class="add-button" [routerLink]="createRouterLink" color="primary" mat-raised-button [disabled]="disabled">
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let idp">
|
||||
<mat-checkbox color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
|
||||
<img *ngIf="idp?.logoSrc?.startsWith('https://'); else genAvatar" [src]="idp.logoSrc"
|
||||
alt="ipp logo {{idp?.name}}" />
|
||||
<ng-template #genAvatar>
|
||||
<div class="avatar">
|
||||
<span>{{idp.name.charAt(0)}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let idp"> {{idp?.name}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="config">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CONFIG' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let idp">
|
||||
<div *ngFor="let elem of idp?.oidcConfig | keyvalue" class="flex-row">
|
||||
<span class="key">{{elem.key}}:</span>
|
||||
<span class="value">{{elem.value}}</span>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.STATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let idp"> {{ 'IDP.STATES.'+idp.state | translate }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CREATIONDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let idp">
|
||||
{{idp.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CHANGEDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let idp">
|
||||
{{idp.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"
|
||||
[routerLink]="row.id ? [serviceType == PolicyComponentServiceType.ADMIN ? '/iam' : '/org', 'idp', row.id ]: null">
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<mat-paginator #paginator class="paginator" [length]="idpResult?.totalResult || 0" [pageSize]="10"
|
||||
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></mat-paginator>
|
||||
</div>
|
||||
</app-refresh-table>
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.table,
|
||||
.paginator {
|
||||
width: 100%;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0 1rem;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.data-row {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: black;
|
||||
color: white;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.key {
|
||||
margin-right: 1rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-top: .5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { IdpTableComponent } from './idp-table.component';
|
||||
|
||||
describe('UserTableComponent', () => {
|
||||
let component: IdpTableComponent;
|
||||
let fixture: ComponentFixture<IdpTableComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IdpTableComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IdpTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { IdpSearchResponse as AdminIdpSearchResponse, IdpView as AdminIdpView } from 'src/app/proto/generated/admin_pb';
|
||||
import { IdpView as MgmtIdpView } from 'src/app/proto/generated/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||
|
||||
@Component({
|
||||
selector: 'app-idp-table',
|
||||
templateUrl: './idp-table.component.html',
|
||||
styleUrls: ['./idp-table.component.scss'],
|
||||
})
|
||||
export class IdpTableComponent implements OnInit {
|
||||
@Input() public serviceType!: PolicyComponentServiceType;
|
||||
@Input() service!: AdminService | ManagementService;
|
||||
@Input() disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
public dataSource: MatTableDataSource<AdminIdpView.AsObject | MgmtIdpView.AsObject>
|
||||
= new MatTableDataSource<AdminIdpView.AsObject | MgmtIdpView.AsObject>();
|
||||
public selection: SelectionModel<AdminIdpView.AsObject | MgmtIdpView.AsObject>
|
||||
= new SelectionModel<AdminIdpView.AsObject | MgmtIdpView.AsObject>(true, []);
|
||||
public idpResult!: AdminIdpSearchResponse.AsObject;
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
@Input() public displayedColumns: string[] = ['select', 'name', 'config', 'creationDate', 'changeDate', 'state'];
|
||||
|
||||
@Output() public changedSelection: EventEmitter<Array<AdminIdpView.AsObject | MgmtIdpView.AsObject>>
|
||||
= new EventEmitter();
|
||||
|
||||
constructor(public translate: TranslateService, private toast: ToastService) {
|
||||
this.selection.changed.subscribe(() => {
|
||||
this.changedSelection.emit(this.selection.selected);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getData(10, 0);
|
||||
}
|
||||
|
||||
public isAllSelected(): boolean {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.dataSource.data.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
public masterToggle(): void {
|
||||
this.isAllSelected() ?
|
||||
this.selection.clear() :
|
||||
this.dataSource.data.forEach(row => this.selection.select(row));
|
||||
}
|
||||
|
||||
|
||||
public changePage(event: PageEvent): void {
|
||||
this.getData(event.pageSize, event.pageIndex * event.pageSize);
|
||||
}
|
||||
|
||||
public deactivateSelectedIdps(): void {
|
||||
Promise.all(this.selection.selected.map(value => {
|
||||
return this.service.DeactivateIdpConfig(value.id);
|
||||
})).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
||||
this.getData(10, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public reactivateSelectedIdps(): void {
|
||||
Promise.all(this.selection.selected.map(value => {
|
||||
return this.service.ReactivateIdpConfig(value.id);
|
||||
})).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.SELECTEDREACTIVATED', true);
|
||||
this.getData(10, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public removeSelectedIdps(): void {
|
||||
Promise.all(this.selection.selected.map(value => {
|
||||
return this.service.RemoveIdpConfig(value.id);
|
||||
})).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.SELECTEDDEACTIVATED', true);
|
||||
this.getData(10, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private async getData(limit: number, offset: number): Promise<void> {
|
||||
this.loadingSubject.next(true);
|
||||
|
||||
// let query: AdminIdpSearchQuery | MgmtIdpSearchQuery;
|
||||
// if (this.service instanceof AdminService) {
|
||||
// query = new AdminIdpSearchQuery();
|
||||
// query.setKey(AdminIdpSearchKey.)
|
||||
// } else if (this.service instanceof ManagementService) {
|
||||
// return ['/org', 'idp', 'create'];
|
||||
// }
|
||||
|
||||
this.service.SearchIdps(limit, offset).then(resp => {
|
||||
this.idpResult = resp.toObject();
|
||||
this.dataSource.data = this.idpResult.resultList;
|
||||
console.log(this.idpResult.resultList);
|
||||
this.loadingSubject.next(false);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.loadingSubject.next(false);
|
||||
});
|
||||
}
|
||||
|
||||
public refreshPage(): void {
|
||||
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
|
||||
}
|
||||
|
||||
public get createRouterLink(): RouterLink | any {
|
||||
if (this.service instanceof AdminService) {
|
||||
return ['/iam', 'idp', 'create'];
|
||||
} else if (this.service instanceof ManagementService) {
|
||||
return ['/org', 'idp', 'create'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
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 { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe.module';
|
||||
|
||||
import { IdpTableComponent } from './idp-table.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [IdpTableComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
LocalizedDatePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
RouterModule,
|
||||
RefreshTableModule,
|
||||
HasRoleModule,
|
||||
],
|
||||
exports: [
|
||||
IdpTableComponent,
|
||||
],
|
||||
})
|
||||
export class IdpTableModule { }
|
||||
Reference in New Issue
Block a user