fix(console): migrate from tslint to eslint, project delete from table (#2490)

* es lint

* modify tsconfig, auto lint some stuff

* lint

* lint

* lint

* lint

* html ts lint

* lint

* lint, tsconfig

* fix project delete, state table

* eslint config, remove cnslHint directive

* mfa selector, info row fixes

* lint

* fix login policy, granted orgs table state, lint

Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Max Peintner 2021-10-22 10:47:06 +02:00 committed by GitHub
parent bdf63800f7
commit b1caef81da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
233 changed files with 7098 additions and 4878 deletions

53
console/.eslintrc.json Normal file
View File

@ -0,0 +1,53 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/no-conflicting-lifecycle": "off",
"@angular-eslint/no-host-metadata-property": "off",
"@angular-eslint/component-selector": [
"error",
{
"prefix": "cnsl",
"style": "kebab-case",
"type": "element"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"prefix": "cnsl",
"style": "camelCase",
"type": "attribute"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@ -34,13 +34,13 @@
"./node_modules/tinycolor2/dist/tinycolor-min.js" "./node_modules/tinycolor2/dist/tinycolor-min.js"
], ],
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"@angular/common/locales/de", "@angular/common/locales/de",
"src/app/proto/generated/zitadel/admin_pb", "src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/management_pb", "src/app/proto/generated/zitadel/management_pb",
"src/app/proto/generated/**", "src/app/proto/generated/**",
"file-saver", "file-saver",
"qrcode" "qrcode"
], ],
"vendorChunk": true, "vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"buildOptimizer": false, "buildOptimizer": false,
@ -84,8 +84,8 @@
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "4mb", "maximumWarning": "5mb",
"maximumError": "5mb" "maximumError": "6mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
@ -102,8 +102,7 @@
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {},
},
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "console:build:production" "browserTarget": "console:build:production"
@ -142,15 +141,11 @@
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"tsConfig": [ "lintFilePatterns": [
"tsconfig.app.json", "src/**/*.ts",
"tsconfig.spec.json" "src/**/*.html"
],
"exclude": [
"**/node_modules/**",
"**/proto/generated/**"
] ]
} }
}, },
@ -174,6 +169,7 @@
}, },
"defaultProject": "console", "defaultProject": "console",
"cli": { "cli": {
"analytics": "2b4e8e6c-f053-4562-b7a6-00c6c06a6791" "analytics": "2b4e8e6c-f053-4562-b7a6-00c6c06a6791",
"defaultCollection": "@angular-eslint/schematics"
} }
} }

View File

@ -6,6 +6,6 @@ export class AppPage {
} }
getTitleText() { getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>; return element(by.css('cnsl-root .content span')).getText() as Promise<string>;
} }
} }

2222
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -48,6 +48,14 @@
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-eslint/builder": "12.5.0",
"@angular-eslint/eslint-plugin": "12.5.0",
"@angular-eslint/eslint-plugin-template": "12.5.0",
"@angular-eslint/schematics": "12.5.0",
"@angular-eslint/template-parser": "12.5.0",
"@typescript-eslint/eslint-plugin": "4.28.2",
"@typescript-eslint/parser": "4.28.2",
"eslint": "^7.26.0",
"@angular-devkit/build-angular": "~12.2.8", "@angular-devkit/build-angular": "~12.2.8",
"@angular/cli": "~12.2.8", "@angular/cli": "~12.2.8",
"@angular/compiler-cli": "~12.2.8", "@angular/compiler-cli": "~12.2.8",
@ -69,7 +77,6 @@
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.21.0", "stylelint-scss": "^3.21.0",
"ts-node": "~10.2.1", "ts-node": "~10.2.1",
"tslint": "~6.1.3",
"typescript": "^4.2.4" "typescript": "^4.2.4"
} }
} }

View File

@ -1,4 +1,4 @@
<ng-container *ngIf="(authService.user | async) || {} as user"> <ng-container *ngIf="$any(authService.user | async) || {} as user">
<ng-container *ngIf="((['iam.read$','iam.write$'] | hasRole)) as iamuser$"> <ng-container *ngIf="((['iam.read$','iam.write$'] | hasRole)) as iamuser$">
<mat-toolbar class="root-header"> <mat-toolbar class="root-header">
<button *ngIf="authenticationService.authenticated" aria-label="Toggle sidenav" mat-icon-button <button *ngIf="authenticationService.authenticated" aria-label="Toggle sidenav" mat-icon-button
@ -7,7 +7,7 @@
</button> </button>
<ng-container *ngIf="labelpolicy && !labelpolicy?.disableWatermark"> <ng-container *ngIf="labelpolicy && !labelpolicy?.disableWatermark">
<a class="title" [routerLink]="['/']"> <a class="title" [routerLink]="['/']">
<img class="logo" alt="zitadel logo" *ngIf="componentCssClass == 'dark-theme'; else lighttheme" <img class="logo" alt="zitadel logo" *ngIf="componentCssClass === 'dark-theme'; else lighttheme"
src="../assets/images/zitadel-logo-solo-light.svg" /> src="../assets/images/zitadel-logo-solo-light.svg" />
<ng-template #lighttheme> <ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" /> <img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" />
@ -48,7 +48,7 @@
<button class="show-all" mat-menu-item [routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' | <button class="show-all" mat-menu-item [routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' |
translate}}</button> translate}}</button>
<ng-template appHasRole [appHasRole]="['org.create','iam.write']"> <ng-template cnslHasRole [hasRole]="['org.create','iam.write']">
<button mat-menu-item [routerLink]="[ '/org/create' ]"> <button mat-menu-item [routerLink]="[ '/org/create' ]">
<mat-icon class="avatar">add</mat-icon> <mat-icon class="avatar">add</mat-icon>
{{'MENU.NEWORG' | translate}} {{'MENU.NEWORG' | translate}}
@ -60,20 +60,20 @@
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION' <a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION'
| translate}}</a> | translate}}</a>
<div (clickOutside)="closeAccountCard()" class="icon-container"> <div (clickOutside)="closeAccountCard()" class="icon-container">
<app-avatar <cnsl-avatar
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))" *ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount" [avatarUrl]="user.human?.profile?.avatarUrl || ''" [forColor]="user?.preferredLoginName" class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount" [avatarUrl]="user.human?.profile?.avatarUrl || ''" [forColor]="user?.preferredLoginName"
[name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)" [name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)"
[size]="38"> [size]="38">
</app-avatar> </cnsl-avatar>
<app-accounts-card @accounts class="a_card mat-elevation-z1" *ngIf="showAccount" <cnsl-accounts-card @accounts class="a_card mat-elevation-z1" *ngIf="showAccount"
(close)="showAccount = false" [user]="user" [iamuser]="iamuser$ | async"> (closedCard)="showAccount = false" [user]="user" [iamuser]="iamuser$ | async">
</app-accounts-card> </cnsl-accounts-card>
</div> </div>
</mat-toolbar> </mat-toolbar>
<mat-drawer-container class="main-container"> <mat-drawer-container class="main-container">
<mat-drawer #drawer class="sidenav" [mode]="(isHandset$ | async) ? 'over' : 'side'" <mat-drawer #drawer class="sidenav" [mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="!(isHandset$ | async) && authenticationService.authenticated"> [opened]="(isHandset$ | async) === false && authenticationService.authenticated">
<div class="side-column"> <div class="side-column">
<div class="list"> <div class="list">
<a @navitem class="nav-item" [routerLinkActive]="['active']" <a @navitem class="nav-item" [routerLinkActive]="['active']"
@ -92,8 +92,7 @@
</ng-container> </ng-container>
<div *ngIf="org" [@navAnimation]="org"> <div *ngIf="org" [@navAnimation]="org">
<ng-template appHasRole <ng-template cnslHasRole [hasRole]="['org.read']">
[appHasRole]="['org.read']">
<div @navitem class="divider"> <div @navitem class="divider">
<div class="line"></div> <div class="line"></div>
<span>{{org?.name ? org.name : ('MENU.ORGSECTION' | translate)}}</span> <span>{{org?.name ? org.name : ('MENU.ORGSECTION' | translate)}}</span>
@ -101,7 +100,7 @@
</div> </div>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['org.read']"> <ng-template cnslHasRole [hasRole]="['org.read']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.ORG' | translate}}" class="nav-item" <a @navitem matTooltip="{{'MENU.TOOLTIP.ORG' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/org']"> [routerLinkActive]="['active']" [routerLink]="[ '/org']">
<i class="icon las la-cog"></i> <i class="icon las la-cog"></i>
@ -109,7 +108,7 @@
</a> </a>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['project.read(:[0-9]*)?']"> <ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.SELFPROJECTS' | translate}}" class="nav-item" <a @navitem matTooltip="{{'MENU.TOOLTIP.SELFPROJECTS' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/projects']"> [routerLinkActive]="['active']" [routerLink]="[ '/projects']">
<i class="icon las la-layer-group"></i> <i class="icon las la-layer-group"></i>
@ -133,7 +132,7 @@
</a> </a>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['user.read(:[0-9]*)?']"> <ng-template cnslHasRole [hasRole]="['user.read(:[0-9]*)?']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.HUMANUSERS' | translate}}" class="nav-item" <a @navitem matTooltip="{{'MENU.TOOLTIP.HUMANUSERS' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/users/list/humans']" [routerLinkActive]="['active']" [routerLink]="[ '/users/list/humans']"
[routerLinkActiveOptions]="{ exact: true }"> [routerLinkActiveOptions]="{ exact: true }">
@ -149,7 +148,7 @@
</a> </a>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['user.grant.read(:[0-9]*)?']"> <ng-template cnslHasRole [hasRole]="['user.grant.read(:[0-9]*)?']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.AUTHZ' | translate}}" class="nav-item" <a @navitem matTooltip="{{'MENU.TOOLTIP.AUTHZ' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/grants']" [routerLinkActive]="['active']" [routerLink]="[ '/grants']"
[routerLinkActiveOptions]="{ exact: true }"> [routerLinkActiveOptions]="{ exact: true }">

View File

@ -15,7 +15,6 @@ import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolba
import { TextQueryMethod } from './proto/generated/zitadel/object_pb'; import { TextQueryMethod } from './proto/generated/zitadel/object_pb';
import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb'; import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb';
import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb'; import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb';
import { User } from './proto/generated/zitadel/user_pb';
import { AuthenticationService } from './services/authentication.service'; import { AuthenticationService } from './services/authentication.service';
import { GrpcAuthService } from './services/grpc-auth.service'; import { GrpcAuthService } from './services/grpc-auth.service';
import { ManagementService } from './services/mgmt.service'; import { ManagementService } from './services/mgmt.service';
@ -24,7 +23,7 @@ import { UpdateService } from './services/update.service';
@Component({ @Component({
selector: 'app-root', selector: 'cnsl-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
animations: [ animations: [
@ -48,7 +47,7 @@ export class AppComponent implements OnDestroy {
public showAccount: boolean = false; public showAccount: boolean = false;
public org!: Org.AsObject; public org!: Org.AsObject;
public orgs$: Observable<Org.AsObject[]> = of([]); public orgs$: Observable<Org.AsObject[]> = of([]);
public user!: User.AsObject; // public user!: User.AsObject;
public isDarkTheme: Observable<boolean> = of(true); public isDarkTheme: Observable<boolean> = of(true);
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false); public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
@ -350,7 +349,7 @@ export class AppComponent implements OnDestroy {
this.authService.user.subscribe(userprofile => { this.authService.user.subscribe(userprofile => {
if (userprofile) { if (userprofile) {
this.user = userprofile; // this.user = userprofile;
const cropped = navigator.language.split('-')[0] ?? 'en'; const cropped = navigator.language.split('-')[0] ?? 'en';
const fallbackLang = cropped.match(/en|de/) ? cropped : 'en'; const fallbackLang = cropped.match(/en|de/) ? cropped : 'en';

View File

@ -1,31 +1,31 @@
import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; import { Directive, EventEmitter, HostListener, Input, Output } from '@angular/core';
@Directive({ @Directive({
selector: '[appCopyToClipboard]', selector: '[cnslCopyToClipboard]',
}) })
export class CopyToClipboardDirective { export class CopyToClipboardDirective {
@Input() valueToCopy: string = ''; @Input() valueToCopy: string = '';
@Output() copiedValue: EventEmitter<string> = new EventEmitter(); @Output() copiedValue: EventEmitter<string> = new EventEmitter();
@HostListener('click', ['$event.target']) onMouseEnter(): void { @HostListener('click', ['$event.target']) onMouseEnter(): void {
this.copytoclipboard(this.valueToCopy); this.copytoclipboard(this.valueToCopy);
} }
public copytoclipboard(value: string): void { public copytoclipboard(value: string): void {
const selBox = document.createElement('textarea'); const selBox = document.createElement('textarea');
selBox.style.position = 'fixed'; selBox.style.position = 'fixed';
selBox.style.left = '0'; selBox.style.left = '0';
selBox.style.top = '0'; selBox.style.top = '0';
selBox.style.opacity = '0'; selBox.style.opacity = '0';
selBox.value = value; selBox.value = value;
document.body.appendChild(selBox); document.body.appendChild(selBox);
selBox.focus(); selBox.focus();
selBox.select(); selBox.select();
document.execCommand('copy'); document.execCommand('copy');
document.body.removeChild(selBox); document.body.removeChild(selBox);
this.copiedValue.emit(value); this.copiedValue.emit(value);
setTimeout(() => { setTimeout(() => {
this.copiedValue.emit(''); this.copiedValue.emit('');
}, 3000); }, 3000);
} }
} }

View File

@ -3,12 +3,12 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Directive({ @Directive({
selector: '[appHasFeature]', selector: '[cnslHasFeature]',
}) })
export class HasFeatureDirective { export class HasFeatureDirective {
private hasView: boolean = false; private hasView: boolean = false;
@Input() public set appHasFeature(features: string[] | RegExp[]) { @Input() public set hasFeature(features: string[] | RegExp[]) {
if (features && features.length > 0) { if (features && features.length > 0) {
this.authService.canUseFeature(features).subscribe(isAllowed => { this.authService.canUseFeature(features).subscribe(isAllowed => {
if (isAllowed && !this.hasView) { if (isAllowed && !this.hasView) {

View File

@ -3,28 +3,28 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Directive({ @Directive({
selector: '[appHasRole]', selector: '[cnslHasRole]',
}) })
export class HasRoleDirective { export class HasRoleDirective {
private hasView: boolean = false; private hasView: boolean = false;
@Input() public set appHasRole(roles: string[] | RegExp[]) { @Input() public set hasRole(roles: string[] | RegExp[]) {
if (roles && roles.length > 0) { if (roles && roles.length > 0) {
this.authService.isAllowed(roles).subscribe(isAllowed => { this.authService.isAllowed(roles).subscribe(isAllowed => {
if (isAllowed && !this.hasView) { if (isAllowed && !this.hasView) {
this.viewContainerRef.clear(); this.viewContainerRef.clear();
this.viewContainerRef.createEmbeddedView(this.templateRef); this.viewContainerRef.createEmbeddedView(this.templateRef);
} else if (this.hasView) { } else if (this.hasView) {
this.viewContainerRef.clear(); this.viewContainerRef.clear();
this.hasView = false; this.hasView = false;
}
});
} }
});
} }
}
constructor( constructor(
private authService: GrpcAuthService, private authService: GrpcAuthService,
protected templateRef: TemplateRef<any>, protected templateRef: TemplateRef<any>,
protected viewContainerRef: ViewContainerRef, protected viewContainerRef: ViewContainerRef,
) { } ) { }
} }

View File

@ -1,17 +1,17 @@
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core'; import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({ @Directive({
selector: '[appOutsideClick]', selector: '[cnslOutsideClick]',
}) })
export class OutsideClickDirective { export class OutsideClickDirective {
constructor(private elementRef: ElementRef) { } constructor(private elementRef: ElementRef) { }
@Output() public clickOutside: EventEmitter<HTMLElement> = new EventEmitter(); @Output() public clickOutside: EventEmitter<HTMLElement> = new EventEmitter();
@HostListener('document:click', ['$event.target']) onMouseEnter(targetElement: HTMLElement): void { @HostListener('document:click', ['$event.target']) onMouseEnter(targetElement: HTMLElement): void {
const clickedInside = this.elementRef.nativeElement.contains(targetElement); const clickedInside = this.elementRef.nativeElement.contains(targetElement);
if (!clickedInside) { if (!clickedInside) {
this.clickOutside.emit(targetElement); this.clickOutside.emit(targetElement);
}
} }
}
} }

View File

@ -1,32 +1,32 @@
import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core'; import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({ @Directive({
selector: '[appScrollable]', selector: '[cnslScrollable]',
}) })
export class ScrollableDirective { export class ScrollableDirective {
// when using this directive, add overflow-y scroll to css // when using this directive, add overflow-y scroll to css
@Output() scrollPosition: EventEmitter<any> = new EventEmitter(); @Output() scrollPosition: EventEmitter<any> = new EventEmitter();
constructor(public el: ElementRef) { } constructor(public el: ElementRef) { }
@HostListener('scroll', ['$event']) @HostListener('scroll', ['$event'])
public onScroll(event: any): void { public onScroll(event: any): void {
try { try {
const top = event.target.scrollTop; const top = event.target.scrollTop;
const height = this.el.nativeElement.scrollHeight; const height = this.el.nativeElement.scrollHeight;
const offset = this.el.nativeElement.offsetHeight; const offset = this.el.nativeElement.offsetHeight;
// emit bottom event // emit bottom event
if (top > height - offset - 1) { if (top > height - offset - 1) {
this.scrollPosition.emit('bottom'); this.scrollPosition.emit('bottom');
} }
// emit top event // emit top event
if (top === 0) { if (top === 0) {
this.scrollPosition.emit('top'); this.scrollPosition.emit('top');
} }
} catch (err) { } } catch (err) { }
} }
} }

View File

@ -1,22 +1,22 @@
<div class="card" appOutsideClick (clickOutside)="closeCard($event)"> <div class="card" cnslOutsideClick (clickOutside)="closeCard($event)">
<app-avatar <cnsl-avatar
*ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))" *ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar" [forColor]="user.preferredLoginName" [avatarUrl]="user.human?.profile?.avatarUrl || ''" class="avatar" [forColor]="user.preferredLoginName" [avatarUrl]="user.human?.profile?.avatarUrl || ''"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)" [name]="(user.human && user.human.profile && user.human.profile.displayName) ? user.human.profile.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="80"> [size]="80">
</app-avatar> </cnsl-avatar>
<span class="u-name">{{user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A'}}</span> <span class="u-name">{{user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A'}}</span>
<span class="u-email">{{user?.preferredLoginName}}</span> <span class="u-email" *ngIf="user.preferredLoginName">{{user.preferredLoginName}}</span>
<span class="iamuser" *ngIf="iamuser">IAM USER</span> <span class="iamuser" *ngIf="iamuser">IAM USER</span>
<button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button> <button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<div class="l-accounts"> <div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)"> <a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)">
<app-avatar *ngIf="session && session.displayName" class="small-avatar" [avatarUrl]="session.avatarUrl || ''" <cnsl-avatar *ngIf="session && session.displayName" class="small-avatar" [avatarUrl]="session.avatarUrl || ''"
[forColor]="session.loginName" [size]="32"> [forColor]="session.loginName" [size]="32">
</app-avatar> </cnsl-avatar>
<div class="col"> <div class="col">
<span class="user-title">{{session.displayName ? session.displayName : session.userName}} </span> <span class="user-title">{{session.displayName ? session.displayName : session.userName}} </span>

View File

@ -6,15 +6,15 @@ import { AuthenticationService } from 'src/app/services/authentication.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@Component({ @Component({
selector: 'app-accounts-card', selector: 'cnsl-accounts-card',
templateUrl: './accounts-card.component.html', templateUrl: './accounts-card.component.html',
styleUrls: ['./accounts-card.component.scss'], styleUrls: ['./accounts-card.component.scss'],
}) })
export class AccountsCardComponent implements OnInit { export class AccountsCardComponent implements OnInit {
@Input() public user!: User.AsObject; @Input() public user!: User.AsObject;
@Input() public iamuser: boolean = false; @Input() public iamuser: boolean | null = false;
@Output() public close: EventEmitter<void> = new EventEmitter(); @Output() public closedCard: EventEmitter<void> = new EventEmitter();
public sessions: Session.AsObject[] = []; public sessions: Session.AsObject[] = [];
public loadingUsers: boolean = false; public loadingUsers: boolean = false;
constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) { constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) {
@ -37,12 +37,12 @@ export class AccountsCardComponent implements OnInit {
public editUserProfile(): void { public editUserProfile(): void {
this.router.navigate(['users/me']); this.router.navigate(['users/me']);
this.close.emit(); this.closedCard.emit();
} }
public closeCard(element: HTMLElement): void { public closeCard(element: HTMLElement): void {
if (!element.classList.contains('dontcloseonclick')) { if (!element.classList.contains('dontcloseonclick')) {
this.close.emit(); this.closedCard.emit();
} }
} }
@ -69,6 +69,6 @@ export class AccountsCardComponent implements OnInit {
public logout(): void { public logout(): void {
this.authService.signout(); this.authService.signout();
this.close.emit(); this.closedCard.emit();
} }
} }

View File

@ -27,7 +27,7 @@
{{'ACTIONS.CANCEL' | translate}} {{'ACTIONS.CANCEL' | translate}}
</button> </button>
<button color="primary" mat-raised-button class="ok-button" [disabled]="type == undefined || dateControl.invalid" <button color="primary" mat-raised-button class="ok-button" [disabled]="type === undefined || dateControl.invalid"
(click)="closeDialogWithSuccess()"> (click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}} {{'ACTIONS.ADD' | translate}}
</button> </button>

View File

@ -4,36 +4,36 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { KeyType } from 'src/app/proto/generated/zitadel/auth_n_key_pb'; import { KeyType } from 'src/app/proto/generated/zitadel/auth_n_key_pb';
export enum AddKeyDialogType { export enum AddKeyDialogType {
MACHINE = 'MACHINE', MACHINE = 'MACHINE',
AUTHNKEY = 'AUTHNKEY', AUTHNKEY = 'AUTHNKEY',
} }
@Component({ @Component({
selector: 'app-add-key-dialog', selector: 'cnsl-add-key-dialog',
templateUrl: './add-key-dialog.component.html', templateUrl: './add-key-dialog.component.html',
styleUrls: ['./add-key-dialog.component.scss'], styleUrls: ['./add-key-dialog.component.scss'],
}) })
export class AddKeyDialogComponent { export class AddKeyDialogComponent {
public startDate: Date = new Date(); public startDate: Date = new Date();
types: KeyType[] = []; types: KeyType[] = [];
public type!: KeyType; public type!: KeyType;
public dateControl: FormControl = new FormControl('', []); public dateControl: FormControl = new FormControl('', []);
constructor( constructor(
public dialogRef: MatDialogRef<AddKeyDialogComponent>, public dialogRef: MatDialogRef<AddKeyDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
) { ) {
this.types = [KeyType.KEY_TYPE_JSON]; this.types = [KeyType.KEY_TYPE_JSON];
this.type = KeyType.KEY_TYPE_JSON; this.type = KeyType.KEY_TYPE_JSON;
const today = new Date(); const today = new Date();
this.startDate.setDate(today.getDate() + 1); this.startDate.setDate(today.getDate() + 1);
} }
public closeDialog(): void { public closeDialog(): void {
this.dialogRef.close(false); this.dialogRef.close(false);
} }
public closeDialogWithSuccess(): void { public closeDialogWithSuccess(): void {
this.dialogRef.close({ type: this.type, date: this.dateControl.value }); this.dialogRef.close({ type: this.type, date: this.dateControl.value });
} }
} }

View File

@ -10,7 +10,7 @@
<cnsl-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</cnsl-label> <cnsl-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()"> <mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()">
<mat-option *ngFor="let type of creationTypes" [value]="type.type" <mat-option *ngFor="let type of creationTypes" [value]="type.type"
[disabled]="(type.disabled$ | async) == false"> [disabled]="(type.disabled$ | async) === false">
{{ 'MEMBER.CREATIONTYPES.'+type.type | translate}} {{ 'MEMBER.CREATIONTYPES.'+type.type | translate}}
</mat-option> </mat-option>
</mat-select> </mat-select>
@ -19,16 +19,16 @@
<ng-container <ng-container
*ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED"> *ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED">
<p>{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</p> <p>{{'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate}}</p>
<app-search-project-autocomplete class="block" singleOutput="true" <cnsl-search-project-autocomplete class="block" [singleOutput]="true"
(selectionChanged)="selectProject($event)" (selectionChanged)="selectProject($event)"
[autocompleteType]="creationType === CreationType.PROJECT_OWNED ? ProjectAutocompleteType.PROJECT_OWNED : creationType === CreationType.PROJECT_GRANTED ? ProjectAutocompleteType.PROJECT_GRANTED : undefined"> [autocompleteType]="creationType === CreationType.PROJECT_OWNED ? ProjectAutocompleteType.PROJECT_OWNED : creationType === CreationType.PROJECT_GRANTED ? ProjectAutocompleteType.PROJECT_GRANTED : undefined">
</app-search-project-autocomplete> </cnsl-search-project-autocomplete>
</ng-container> </ng-container>
</ng-container> </ng-container>
<!-- if no context end --> <!-- if no context end -->
<app-search-user-autocomplete [users]="preselectedUsers" (selectionChanged)="users = $event"> <cnsl-search-user-autocomplete [users]="preselectedUsers" (selectionChanged)="users = $any($event)">
</app-search-user-autocomplete> </cnsl-search-user-autocomplete>
<cnsl-form-field class="full-width" appearance="outline" <cnsl-form-field class="full-width" appearance="outline"
*ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED || creationType === CreationType.IAM"> *ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED || creationType === CreationType.IAM">
@ -41,8 +41,8 @@
</cnsl-form-field> </cnsl-form-field>
<ng-container *ngIf="creationType === CreationType.ORG"> <ng-container *ngIf="creationType === CreationType.ORG">
<app-org-member-roles-autocomplete (selectionChanged)="setOrgMemberRoles($event)"> <cnsl-org-member-roles-autocomplete (selectionChanged)="setOrgMemberRoles($event)">
</app-org-member-roles-autocomplete> </cnsl-org-member-roles-autocomplete>
</ng-container> </ng-container>
</div> </div>
@ -51,7 +51,7 @@
{{'ACTIONS.CANCEL' | translate}} {{'ACTIONS.CANCEL' | translate}}
</button> </button>
<button [disabled]="users.length == 0 || roles.length == 0" color="primary" mat-raised-button class="ok-button" <button [disabled]="users.length === 0 || roles.length === 0" color="primary" mat-raised-button class="ok-button"
(click)="closeDialogWithSuccess()"> (click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}} {{'ACTIONS.ADD' | translate}}
</button> </button>

View File

@ -11,115 +11,115 @@ import { ToastService } from 'src/app/services/toast.service';
import { ProjectAutocompleteType } from '../search-project-autocomplete/search-project-autocomplete.component'; import { ProjectAutocompleteType } from '../search-project-autocomplete/search-project-autocomplete.component';
export enum CreationType { export enum CreationType {
PROJECT_OWNED = 0, PROJECT_OWNED = 0,
PROJECT_GRANTED = 1, PROJECT_GRANTED = 1,
ORG = 2, ORG = 2,
IAM = 3, IAM = 3,
} }
@Component({ @Component({
selector: 'app-member-create-dialog', selector: 'cnsl-member-create-dialog',
templateUrl: './member-create-dialog.component.html', templateUrl: './member-create-dialog.component.html',
styleUrls: ['./member-create-dialog.component.scss'], styleUrls: ['./member-create-dialog.component.scss'],
}) })
export class MemberCreateDialogComponent { export class MemberCreateDialogComponent {
private projectId: string = ''; private projectId: string = '';
private grantId: string = ''; private grantId: string = '';
public preselectedUsers: Array<User.AsObject> = []; public preselectedUsers: Array<User.AsObject> = [];
public creationType!: CreationType; public creationType!: CreationType;
/** /**
* Specifies options for creating members, * Specifies options for creating members,
* without ending $, to enable write event permission even if user is allowed * without ending $, to enable write event permission even if user is allowed
* to create members for only one specific project. * to create members for only one specific project.
*/ */
public creationTypes: Array<{ type: CreationType, disabled$: Observable<boolean>; }> = [ public creationTypes: Array<{ type: CreationType, disabled$: Observable<boolean>; }> = [
{ type: CreationType.IAM, disabled$: this.authService.isAllowed(['iam.member.write$']) }, { type: CreationType.IAM, disabled$: this.authService.isAllowed(['iam.member.write$']) },
{ type: CreationType.ORG, disabled$: this.authService.isAllowed(['org.member.write$']) }, { type: CreationType.ORG, disabled$: this.authService.isAllowed(['org.member.write$']) },
{ type: CreationType.PROJECT_OWNED, disabled$: this.authService.isAllowed(['project.member.write']) }, { type: CreationType.PROJECT_OWNED, disabled$: this.authService.isAllowed(['project.member.write']) },
{ type: CreationType.PROJECT_GRANTED, disabled$: this.authService.isAllowed(['project.grant.member.write']) }, { type: CreationType.PROJECT_GRANTED, disabled$: this.authService.isAllowed(['project.grant.member.write']) },
]; ];
public users: Array<User.AsObject> = []; public users: Array<User.AsObject> = [];
public roles: Array<Role.AsObject> | string[] = []; public roles: Array<Role.AsObject> | string[] = [];
public CreationType: any = CreationType; public CreationType: any = CreationType;
public ProjectAutocompleteType: any = ProjectAutocompleteType; public ProjectAutocompleteType: any = ProjectAutocompleteType;
public memberRoleOptions: string[] = []; public memberRoleOptions: string[] = [];
public showCreationTypeSelector: boolean = false; public showCreationTypeSelector: boolean = false;
constructor( constructor(
private mgmtService: ManagementService, private mgmtService: ManagementService,
private adminService: AdminService, private adminService: AdminService,
private authService: GrpcAuthService, private authService: GrpcAuthService,
public dialogRef: MatDialogRef<MemberCreateDialogComponent>, public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
private toastService: ToastService, private toastService: ToastService,
) { ) {
if (data?.projectId) { if (data?.projectId) {
this.projectId = data.projectId; this.projectId = data.projectId;
} }
if (data?.user) { if (data?.user) {
this.preselectedUsers = [data.user]; this.preselectedUsers = [data.user];
this.users = [data.user]; this.users = [data.user];
}
if (data?.creationType !== undefined) {
this.creationType = data.creationType;
this.loadRoles();
} else {
this.showCreationTypeSelector = true;
}
} }
public loadRoles(): void { if (data?.creationType !== undefined) {
switch (this.creationType) { this.creationType = data.creationType;
case CreationType.PROJECT_GRANTED: this.loadRoles();
this.mgmtService.listProjectGrantMemberRoles().then(resp => { } else {
this.memberRoleOptions = resp.resultList; this.showCreationTypeSelector = true;
}).catch(error => {
this.toastService.showError(error);
});
break;
case CreationType.PROJECT_OWNED:
this.mgmtService.listProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).catch(error => {
this.toastService.showError(error);
});
break;
case CreationType.IAM:
this.adminService.listIAMMemberRoles().then(resp => {
this.memberRoleOptions = resp.rolesList;
}).catch(error => {
this.toastService.showError(error);
});
break;
}
} }
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void { public loadRoles(): void {
if (project.projectId && project.grantId) { switch (this.creationType) {
this.projectId = project.projectId; case CreationType.PROJECT_GRANTED:
this.grantId = project.grantId; this.mgmtService.listProjectGrantMemberRoles().then(resp => {
} else if (project.id) { this.memberRoleOptions = resp.resultList;
this.projectId = project.id; }).catch(error => {
} this.toastService.showError(error);
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close({
users: this.users,
roles: this.roles,
creationType: this.creationType,
projectId: this.projectId,
grantId: this.grantId,
}); });
break;
case CreationType.PROJECT_OWNED:
this.mgmtService.listProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).catch(error => {
this.toastService.showError(error);
});
break;
case CreationType.IAM:
this.adminService.listIAMMemberRoles().then(resp => {
this.memberRoleOptions = resp.rolesList;
}).catch(error => {
this.toastService.showError(error);
});
break;
} }
}
public setOrgMemberRoles(roles: string[]): void { public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.roles = roles; if (project.projectId && project.grantId) {
this.projectId = project.projectId;
this.grantId = project.grantId;
} else if (project.id) {
this.projectId = project.id;
} }
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close({
users: this.users,
roles: this.roles,
creationType: this.creationType,
projectId: this.projectId,
grantId: this.grantId,
});
}
public setOrgMemberRoles(roles: string[]): void {
this.roles = roles;
}
} }

View File

@ -1,5 +1,5 @@
<div class="cnsl-app-card" [ngClass]="{'web': type == OIDCAppType.OIDC_APP_TYPE_WEB, <div class="cnsl-app-card" [ngClass]="{'web': type === OIDCAppType.OIDC_APP_TYPE_WEB,
'useragent': type == OIDCAppType.OIDC_APP_TYPE_USER_AGENT, 'useragent': type === OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
'native': type == OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}"> 'native': type === OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>

View File

@ -2,13 +2,13 @@ import { Component, Input } from '@angular/core';
import { OIDCAppType } from 'src/app/proto/generated/zitadel/app_pb'; import { OIDCAppType } from 'src/app/proto/generated/zitadel/app_pb';
@Component({ @Component({
selector: 'cnsl-app-card', selector: 'cnsl-app-card',
templateUrl: './app-card.component.html', templateUrl: './app-card.component.html',
styleUrls: ['./app-card.component.scss'], styleUrls: ['./app-card.component.scss'],
}) })
export class AppCardComponent { export class AppCardComponent {
@Input() public outline: boolean = false; @Input() public outline: boolean = false;
@Input() public type!: OIDCAppType; @Input() public type: OIDCAppType | undefined = undefined;
@Input() public isApiApp: boolean = false; @Input() public isApiApp: boolean = false;
public OIDCAppType: any = OIDCAppType; public OIDCAppType: any = OIDCAppType;
} }

View File

@ -2,7 +2,7 @@
<ng-container *ngFor="let method of authMethods; index as i"> <ng-container *ngFor="let method of authMethods; index as i">
<input type="radio" [disabled]="method.disabled" (change)="emitChange()" [value]="method.key" [id]="method.key" <input type="radio" [disabled]="method.disabled" (change)="emitChange()" [value]="method.key" [id]="method.key"
[(ngModel)]="selected" /> [(ngModel)]="selected" />
<label class="cnsl-radio-button" [ngClass]="{'first': i == 0, 'last': i == authMethods.length - 1}" <label class="cnsl-radio-button" [ngClass]="{'first': i === 0, 'last': i === authMethods.length - 1}"
[for]="method.key"> [for]="method.key">
<div class="recommended" [ngClass]="{'not': method.notRecommended}" <div class="recommended" [ngClass]="{'not': method.notRecommended}"
*ngIf="method.recommended || method.notRecommended"> *ngIf="method.recommended || method.notRecommended">
@ -11,25 +11,25 @@
<div class="cnsl-radio-header" [ngStyle]="{'background': method.background}"> <div class="cnsl-radio-header" [ngStyle]="{'background': method.background}">
<span>{{method.prefix}}</span> <span>{{method.prefix}}</span>
<div class="current" *ngIf="current == method.key">{{'APP.OIDC.CURRENT' | translate}}</div> <div class="current" *ngIf="current === method.key">{{'APP.OIDC.CURRENT' | translate}}</div>
</div> </div>
<p>{{method.titleI18nKey | translate}}</p> <p>{{method.titleI18nKey | translate}}</p>
<p class="type-desc">{{method.descI18nKey | translate}}</p> <p class="type-desc">{{method.descI18nKey | translate}}</p>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="app-specs"> <div class="app-specs">
<div class="row" *ngIf="isOIDC && method && method.responseType != undefined"> <div class="row" *ngIf="isOIDC && method && method.responseType !== undefined">
<span>{{'APP.OIDC.RESPONSETYPE' | translate}}</span> <span>{{'APP.OIDC.RESPONSETYPE' | translate}}</span>
<span>{{('APP.OIDC.RESPONSE.'+method.responseType.toString()) | translate}}</span> <span>{{('APP.OIDC.RESPONSE.'+method.responseType.toString()) | translate}}</span>
</div> </div>
<div class="row" *ngIf="isOIDC && method.grantType != undefined"> <div class="row" *ngIf="isOIDC && method.grantType !== undefined">
<span>{{'APP.GRANT' | translate}}</span> <span>{{'APP.GRANT' | translate}}</span>
<span>{{('APP.OIDC.GRANT.'+method.grantType.toString()) | translate}}</span> <span>{{('APP.OIDC.GRANT.'+method.grantType.toString()) | translate}}</span>
</div> </div>
<div class="row" *ngIf="isOIDC && method.authMethod != undefined"> <div class="row" *ngIf="isOIDC && method.authMethod !== undefined">
<span>{{'APP.AUTHMETHOD' | translate}}</span> <span>{{'APP.AUTHMETHOD' | translate}}</span>
<span>{{('APP.OIDC.AUTHMETHOD.'+method.authMethod.toString()) | translate}}</span> <span>{{('APP.OIDC.AUTHMETHOD.'+method.authMethod.toString()) | translate}}</span>
</div> </div>
<div class="row" *ngIf="!isOIDC && method.apiAuthMethod != undefined"> <div class="row" *ngIf="!isOIDC && method.apiAuthMethod !== undefined">
<span>{{'APP.AUTHMETHOD' | translate}}</span> <span>{{'APP.AUTHMETHOD' | translate}}</span>
<span>{{('APP.API.AUTHMETHOD.'+method.apiAuthMethod.toString()) | translate}}</span> <span>{{('APP.API.AUTHMETHOD.'+method.apiAuthMethod.toString()) | translate}}</span>
</div> </div>

View File

@ -1,39 +1,39 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { import {
APIAuthMethodType, APIAuthMethodType,
OIDCAuthMethodType, OIDCAuthMethodType,
OIDCGrantType, OIDCGrantType,
OIDCResponseType, OIDCResponseType,
} from 'src/app/proto/generated/zitadel/app_pb'; } from 'src/app/proto/generated/zitadel/app_pb';
export interface RadioItemAuthType { export interface RadioItemAuthType {
key: string; key: string;
titleI18nKey: string; titleI18nKey: string;
descI18nKey: string; descI18nKey: string;
disabled: boolean; disabled: boolean;
prefix: string; prefix: string;
background: string; background: string;
responseType?: OIDCResponseType; responseType?: OIDCResponseType;
grantType?: OIDCGrantType; grantType?: OIDCGrantType;
authMethod?: OIDCAuthMethodType; authMethod?: OIDCAuthMethodType;
apiAuthMethod?: | APIAuthMethodType; apiAuthMethod?: | APIAuthMethodType;
recommended?: boolean; recommended?: boolean;
notRecommended?: boolean; notRecommended?: boolean;
} }
@Component({ @Component({
selector: 'app-auth-method-radio', selector: 'cnsl-auth-method-radio',
templateUrl: './app-auth-method-radio.component.html', templateUrl: './app-auth-method-radio.component.html',
styleUrls: ['./app-auth-method-radio.component.scss'], styleUrls: ['./app-auth-method-radio.component.scss'],
}) })
export class AppAuthMethodRadioComponent { export class AppAuthMethodRadioComponent {
@Input() current: string = ''; @Input() current: string = '';
@Input() selected: string = ''; @Input() selected: string = '';
@Input() authMethods!: RadioItemAuthType[]; @Input() authMethods!: RadioItemAuthType[];
@Input() isOIDC: boolean = false; @Input() isOIDC: boolean = false;
@Output() selectedMethod: EventEmitter<string> = new EventEmitter(); @Output() selectedMethod: EventEmitter<string> = new EventEmitter();
public emitChange(): void { public emitChange(): void {
this.selectedMethod.emit(this.selected); this.selectedMethod.emit(this.selected);
} }
} }

View File

@ -2,16 +2,16 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RadioItemAppType, WEB_TYPE } from 'src/app/pages/projects/apps/authtypes'; import { RadioItemAppType, WEB_TYPE } from 'src/app/pages/projects/apps/authtypes';
@Component({ @Component({
selector: 'app-type-radio', selector: 'cnsl-type-radio',
templateUrl: './app-type-radio.component.html', templateUrl: './app-type-radio.component.html',
styleUrls: ['./app-type-radio.component.scss'], styleUrls: ['./app-type-radio.component.scss'],
}) })
export class AppTypeRadioComponent { export class AppTypeRadioComponent {
@Input() selected: RadioItemAppType = WEB_TYPE; @Input() selected: RadioItemAppType = WEB_TYPE;
@Input() types!: RadioItemAppType[]; @Input() types!: RadioItemAppType[];
@Output() selectedType: EventEmitter<RadioItemAppType> = new EventEmitter(); @Output() selectedType: EventEmitter<RadioItemAppType> = new EventEmitter();
public emitChange(): void { public emitChange(): void {
this.selectedType.emit(this.selected); this.selectedType.emit(this.selected);
} }
} }

View File

@ -1,5 +1,5 @@
<div class="avatar-circle dontcloseonclick" matRipple [matRippleColor]="'#ffffff20'" matRippleUnbounded="true" <div class="avatar-circle dontcloseonclick" matRipple [matRippleColor]="'#ffffff20'" [matRippleUnbounded]="true"
matRippleCentered="true" [matRippleCentered]="true"
[ngStyle]="{'height': size+'px', 'width': size+'px', 'fontSize': (fontSize-1)+'px', 'background': color}" [ngStyle]="{'height': size+'px', 'width': size+'px', 'fontSize': (fontSize-1)+'px', 'background': color}"
[ngClass]="{'active': active}"> [ngClass]="{'active': active}">
<img class="dontcloseonclick" *ngIf="avatarUrl; else creds" [src]="avatarUrl"/> <img class="dontcloseonclick" *ngIf="avatarUrl; else creds" [src]="avatarUrl"/>

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'app-avatar', selector: 'cnsl-avatar',
templateUrl: './avatar.component.html', templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.scss'], styleUrls: ['./avatar.component.scss'],
}) })
@ -77,7 +77,7 @@ export class AvatarComponent implements OnInit {
return colors[hash % colors.length]; return colors[hash % colors.length];
} }
// tslint:disable /* eslint-disable */
private hashCode(str: string, seed: number = 0): number { private hashCode(str: string, seed: number = 0): number {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) { for (let i = 0, ch; i < str.length; i++) {
@ -89,5 +89,5 @@ export class AvatarComponent implements OnInit {
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0); return 4294967296 * (2097151 & h2) + (h1 >>> 0);
} }
// tslint:enable /* eslint-enable */
} }

View File

@ -2,7 +2,7 @@ import { animate, style, transition, trigger } from '@angular/animations';
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
@Component({ @Component({
selector: 'app-card', selector: 'cnsl-card',
templateUrl: './card.component.html', templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'], styleUrls: ['./card.component.scss'],
animations: [ animations: [

View File

@ -5,17 +5,18 @@
</button> </button>
</div> </div>
<div class="scroll-container" appScrollable (scrollPosition)="scrollHandler($event)"> <div class="scroll-container" cnslScrollable (scrollPosition)="scrollHandler($event)">
<li class="item change-item-back" *ngFor="let hist of data | async; index as histindex"> <li class="item change-item-back" *ngFor="let hist of data | async; index as histindex">
<span *ngIf="hist.values[0].dates[0]" class="date">{{ <span *ngIf="hist.values[0].dates[0]" class="date">
hist.values[0]?.dates[0]| timestampToDate | localizedDate: 'dd. MMMM YYYY' }}</span> {{ hist.values[0].dates[0]| timestampToDate | localizedDate: 'dd. MMMM YYYY' }}
</span>
<div class="item" *ngFor="let dayelement of hist.values; index as i"> <div class="item" *ngFor="let dayelement of hist.values; index as i">
<div class="row"> <div class="row">
<app-avatar matTooltip="{{ dayelement.editorDisplayName }}" <cnsl-avatar matTooltip="{{ dayelement.editorDisplayName }}"
*ngIf="dayelement.editorDisplayName; else spacer" class="avatar" *ngIf="dayelement.editorDisplayName; else spacer" class="avatar"
[name]="dayelement.editorDisplayName" [size]="32" [forColor]="dayelement?.editorPreferredLoginName" [name]="dayelement.editorDisplayName" [size]="32" [forColor]="dayelement?.editorPreferredLoginName ?? 'A'"
[avatarUrl]="dayelement.editorAvatarUrl || ''"> [avatarUrl]="dayelement.editorAvatarUrl || ''">
</app-avatar> </cnsl-avatar>
<ng-template #spacer> <ng-template #spacer>
<div class="spacer"></div> <div class="spacer"></div>
</ng-template> </ng-template>

View File

@ -29,6 +29,9 @@ export interface MappedChange {
dates: Timestamp.AsObject[]; dates: Timestamp.AsObject[];
editorId: string; editorId: string;
editorName: string; editorName: string;
editorDisplayName: string;
editorAvatarUrl: string;
editorPreferredLoginName: string;
eventTypes: Array<{ key: string; localizedMessage: string; }>; eventTypes: Array<{ key: string; localizedMessage: string; }>;
sequences: number[]; sequences: number[];
}>; }>;
@ -41,7 +44,7 @@ type ListChanges = ListMyUserChangesResponse.AsObject |
ListAppChangesResponse.AsObject; ListAppChangesResponse.AsObject;
@Component({ @Component({
selector: 'app-changes', selector: 'cnsl-changes',
templateUrl: './changes.component.html', templateUrl: './changes.component.html',
styleUrls: ['./changes.component.scss'], styleUrls: ['./changes.component.scss'],
}) })
@ -253,7 +256,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
} }
// Order by ascending property value // Order by ascending property value
// tslint:disable /* eslint-disable */
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => { valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.value.localeCompare(b.value); return a.value.localeCompare(b.value);
}; };
@ -262,5 +265,5 @@ export class ChangesComponent implements OnInit, OnDestroy {
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => { keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0); return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
}; };
// tslint:enable /* eslint-enable */
} }

View File

@ -1,7 +1,7 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length" <cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[timestamp]="keyResult?.details?.viewTimestamp" [selection]="selection"> [timestamp]="keyResult?.details?.viewTimestamp" [selection]="selection">
<div actions> <div actions>
<a [disabled]="([('project.app.write:' + projectId), 'project.app.write'] | hasRole | async) == false" <a [disabled]="([('project.app.write:' + projectId), 'project.app.write'] | hasRole | async) === false"
color="primary" mat-raised-button (click)="openAddKey()"> color="primary" mat-raised-button (click)="openAddKey()">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a> </a>
@ -51,7 +51,7 @@
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let key"> <td mat-cell *matCellDef="let key">
<button <button
[disabled]="([('project.app.write:' + projectId), 'project.app.write'] | hasRole | async) == false" [disabled]="([('project.app.write:' + projectId), 'project.app.write'] | hasRole | async) === false"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}" mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}"
(click)="deleteKey(key)"> (click)="deleteKey(key)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
@ -68,4 +68,4 @@
<cnsl-paginator #paginator class="paginator" [timestamp]="keyResult?.details?.viewTimestamp" [length]="keyResult?.details?.totalResult || 0" [pageSize]="10" <cnsl-paginator #paginator class="paginator" [timestamp]="keyResult?.details?.viewTimestamp" [length]="keyResult?.details?.totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator> [pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</div> </div>
</app-refresh-table> </cnsl-refresh-table>

View File

@ -16,7 +16,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
@Component({ @Component({
selector: 'app-client-keys', selector: 'cnsl-client-keys',
templateUrl: './client-keys.component.html', templateUrl: './client-keys.component.html',
styleUrls: ['./client-keys.component.scss'], styleUrls: ['./client-keys.component.scss'],
}) })
@ -79,7 +79,7 @@ export class ClientKeysComponent implements OnInit {
width: '400px', width: '400px',
}); });
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
const type: KeyType = resp.type; const type: KeyType = resp.type;
@ -96,7 +96,7 @@ export class ClientKeysComponent implements OnInit {
} }
if (type) { if (type) {
return this.mgmtService.addAppKey( this.mgmtService.addAppKey(
this.projectId, this.projectId,
this.appId, this.appId,
type, type,

View File

@ -6,16 +6,15 @@
<div class="people"> <div class="people">
<div class="img-list" [@cardAnimation]="totalResult"> <div class="img-list" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner> <mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 10; else compact"> <ng-container *ngIf="totalResult < 10; else compact">
<ng-container *ngFor="let member of membersSubject | async; index as i"> <ng-container *ngFor="let member of membersSubject | async; index as i">
<div @animate (click)="emitShowDetail()" class="avatar-circle" <div @animate (click)="emitShowDetail()" class="avatar-circle"
matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}" [ngStyle]="{'z-index': 100 - i}"> matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}" [ngStyle]="{'z-index': 100 - i}">
<app-avatar *ngIf="member && member.displayName && member.firstName && member.lastName; else cog" <cnsl-avatar *ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''" [forColor]="member?.userName" class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''"
[forColor]="member?.preferredLoginName" [forColor]="member.preferredLoginName ?? 'A'"
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)" [size]="32"> [name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)" [size]="32">
</app-avatar> </cnsl-avatar>
<ng-template #cog> <ng-template #cog>
<div class="sa-icon"> <div class="sa-icon">
<i class="las la-user-cog"></i> <i class="las la-user-cog"></i>

View File

@ -4,46 +4,46 @@ import { BehaviorSubject } from 'rxjs';
import { Member } from 'src/app/proto/generated/zitadel/member_pb'; import { Member } from 'src/app/proto/generated/zitadel/member_pb';
@Component({ @Component({
selector: 'app-contributors', selector: 'cnsl-contributors',
templateUrl: './contributors.component.html', templateUrl: './contributors.component.html',
styleUrls: ['./contributors.component.scss'], styleUrls: ['./contributors.component.scss'],
animations: [ animations: [
trigger('cardAnimation', [ trigger('cardAnimation', [
transition('* => *', [ transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }), query('@animate', stagger('40ms', animateChild()), { optional: true }),
]), ]),
]), ]),
trigger('animate', [ trigger('animate', [
transition(':enter', [ transition(':enter', [
animate('.2s ease-in', keyframes([ animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }), style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }), style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }), style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])), ])),
]), ]),
]), ]),
], ],
}) })
export class ContributorsComponent { export class ContributorsComponent {
@Input() title: string = ''; @Input() title: string = '';
@Input() description: string = ''; @Input() description: string = '';
@Input() disabled: boolean = false; @Input() disabled: boolean = false;
@Input() totalResult: number = 0; @Input() totalResult: number = 0;
@Input() loading: boolean = false; @Input() loading: boolean | null = false;
@Input() membersSubject!: BehaviorSubject<Member.AsObject[]>; @Input() membersSubject!: BehaviorSubject<Member.AsObject[]>;
@Output() addClicked: EventEmitter<void> = new EventEmitter(); @Output() addClicked: EventEmitter<void> = new EventEmitter();
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter(); @Output() showDetailClicked: EventEmitter<void> = new EventEmitter();
@Output() refreshClicked: EventEmitter<void> = new EventEmitter(); @Output() refreshClicked: EventEmitter<void> = new EventEmitter();
public emitAddMember(): void { public emitAddMember(): void {
this.addClicked.emit(); this.addClicked.emit();
} }
public emitShowDetail(): void { public emitShowDetail(): void {
this.showDetailClicked.emit(); this.showDetailClicked.emit();
} }
public emitRefresh(): void { public emitRefresh(): void {
this.refreshClicked.emit(); this.refreshClicked.emit();
} }
} }

View File

@ -1,13 +1,12 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({ @Component({
selector: 'app-detail-layout', selector: 'cnsl-detail-layout',
templateUrl: './detail-layout.component.html', templateUrl: './detail-layout.component.html',
styleUrls: ['./detail-layout.component.scss'], styleUrls: ['./detail-layout.component.scss'],
}) })
export class DetailLayoutComponent { export class DetailLayoutComponent {
@Input() backRouterLink!: RouterLink; @Input() backRouterLink: any = undefined;
@Input() title: string | null = ''; @Input() title: string | null = '';
@Input() description: string | null = ''; @Input() description: string | null = '';
@Input() maxWidth: boolean = true; @Input() maxWidth: boolean = true;

View File

@ -1,4 +1,4 @@
<div *ngIf="currentMap"> <div *ngIf="currentMap">
<form [formGroup]="form" > <form [formGroup]="form" >
@ -8,24 +8,24 @@
<cnsl-form-field class="formfield" > <cnsl-form-field class="formfield" >
<cnsl-label>{{key.key}}</cnsl-label> <cnsl-label>{{key.key}}</cnsl-label>
<textarea class="text" cnslInput [formControlName]="key.key" [placeholder]="defaultmap[key.key]" [name]="key.key" [ngClass]="{'defaulttext': form.get(key.key)?.value === ''}"></textarea> <textarea class="text" cnslInput [formControlName]="key.key" [placeholder]="defaultmap[key.key]" [name]="key.key" [ngClass]="{'defaulttext': form.get(key.key)?.value === ''}"></textarea>
<div class="chips" *ngIf="warnText[key.key] == undefined"> <div class="chips" *ngIf="warnText[key.key] === undefined">
<ng-container *ngFor="let chip of chips" > <ng-container *ngFor="let chip of chips" >
<div class="chip" appCopyToClipboard [valueToCopy]="chip.value" (copiedValue)="copied = $event" (click)="addChip(key.key, chip.value)"> <div class="chip" cnslCopyToClipboard [valueToCopy]="chip.value" (copiedValue)="copied = $event" (click)="addChip(key.key, chip.value)">
<span class="key">{{chip.key | translate}}</span> <span class="key">{{chip.key | translate}}</span>
<span class="value">{{chip.value}}</span> <span class="value">{{chip.value}}</span>
<i *ngIf="copied != chip.value" class="las la-clipboard"></i> <i *ngIf="copied !== chip.value" class="las la-clipboard"></i>
<i *ngIf="copied == chip.value" class="las la-clipboard-check"></i> <i *ngIf="copied === chip.value" class="las la-clipboard-check"></i>
</div> </div>
</ng-container> </ng-container>
</div> </div>
</cnsl-form-field> </cnsl-form-field>
<div class="actions"> <div class="actions">
<button matTooltip="{{'ACTIONS.RESETDEFAULT'| translate }}" mat-icon-button [disabled]="form.get(key.key)?.value == defaultmap[key.key] || disabled" (click)="form.get(key.key)?.setValue(defaultmap[key.key])" (mouseenter) = "form.get(key.key)?.value != defaultmap[key.key] && setWarnText(key.key, defaultmap[key.key])" (mouseleave) ="setWarnText(key.key, undefined)"><i class="las la-history"></i></button> <button matTooltip="{{'ACTIONS.RESETDEFAULT'| translate }}" mat-icon-button [disabled]="form.get(key.key)?.value === defaultmap[key.key] || disabled" (click)="form.get(key.key)?.setValue(defaultmap[key.key])" (mouseenter) = "form.get(key.key)?.value !== defaultmap[key.key] && setWarnText(key.key, defaultmap[key.key])" (mouseleave) ="setWarnText(key.key, undefined)"><i class="las la-history"></i></button>
<button matTooltip="{{'ACTIONS.RESETCURRENT'| translate }}" mat-icon-button [disabled]="form.get(key.key)?.value == currentMap[key.key] || disabled" (click)="form.get(key.key)?.setValue(currentMap[key.key])" (mouseenter) = "form.get(key.key)?.value != currentMap[key.key] && setWarnText(key.key, currentMap[key.key])" (mouseleave) ="setWarnText(key.key, undefined)"><i class="las la-undo"></i></button> <button matTooltip="{{'ACTIONS.RESETCURRENT'| translate }}" mat-icon-button [disabled]="form.get(key.key)?.value === currentMap[key.key] || disabled" (click)="form.get(key.key)?.setValue(currentMap[key.key])" (mouseenter) = "form.get(key.key)?.value !== currentMap[key.key] && setWarnText(key.key, currentMap[key.key])" (mouseleave) ="setWarnText(key.key, undefined)"><i class="las la-undo"></i></button>
</div> </div>
</div> </div>
</div> </div>
<cnsl-info-section *ngIf="warnText[key.key] !== undefined" class="info" type="WARN">{{'ACTIONS.RESETTO'| translate }} <cite>'{{warnText[key.key]}}'</cite></cnsl-info-section> <cnsl-info-section *ngIf="warnText[key.key] !== undefined" class="info" [type]="InfoSectionType.WARN">{{'ACTIONS.RESETTO'| translate }} <cite>'{{warnText[key.key]}}'</cite></cnsl-info-section>
</ng-container> </ng-container>
</form> </form>
</div> </div>

View File

@ -3,6 +3,8 @@ import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { InfoSectionType } from '../info-section/info-section.component';
@Component({ @Component({
selector: 'cnsl-edit-text', selector: 'cnsl-edit-text',
templateUrl: './edit-text.component.html', templateUrl: './edit-text.component.html',
@ -23,6 +25,7 @@ export class EditTextComponent implements OnInit, OnDestroy {
@Input() public disabled: boolean = true; @Input() public disabled: boolean = true;
public copied: string = ''; public copied: string = '';
public InfoSectionType: any = InfoSectionType;
public ngOnInit(): void { public ngOnInit(): void {
this.current$.pipe(takeUntil(this.destroy$)).subscribe(value => { this.current$.pipe(takeUntil(this.destroy$)).subscribe(value => {

View File

@ -1,4 +1,4 @@
<app-detail-layout [backRouterLink]="[ serviceType === FeatureServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === FeatureServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="('FEATURES.TITLE' | translate)" [description]="'FEATURES.DESCRIPTION' | translate"> [title]="('FEATURES.TITLE' | translate)" [description]="'FEATURES.DESCRIPTION' | translate">
<h2>{{'FEATURES.TIER.TITLE' | translate}}</h2> <h2>{{'FEATURES.TIER.TITLE' | translate}}</h2>
@ -17,7 +17,7 @@
<ng-container *ngIf="serviceType === FeatureServiceType.MGMT"> <ng-container *ngIf="serviceType === FeatureServiceType.MGMT">
<mat-spinner class="spinner" diameter="20" *ngIf="customerLoading || stripeLoading"></mat-spinner> <mat-spinner class="spinner" diameter="20" *ngIf="customerLoading || stripeLoading"></mat-spinner>
<div class="detail" *ngIf="stripeCustomer || stripeCustomer == null"> <div class="detail" *ngIf="stripeCustomer || stripeCustomer === null">
<p class="title">{{'FEATURES.TIER.DETAILS' | translate}} <p class="title">{{'FEATURES.TIER.DETAILS' | translate}}
<a (click)="setCustomer()">{{'ACTIONS.EDIT' | translate}}</a> <a (click)="setCustomer()">{{'ACTIONS.EDIT' | translate}}</a>
</p> </p>
@ -32,7 +32,7 @@
</p> </p>
</div> </div>
<p class="error" *ngIf="(stripeCustomer || stripeCustomer == null) && !customerValid">{{'FEATURES.TIER.CUSTOMERINVALID' | translate}}</p> <p class="error" *ngIf="(stripeCustomer || stripeCustomer === null) && !customerValid">{{'FEATURES.TIER.CUSTOMERINVALID' | translate}}</p>
<div class="current-tier"> <div class="current-tier">
<a color="primary" [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL" target="_blank" <a color="primary" [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL" target="_blank"
@ -40,7 +40,7 @@
</div> </div>
</ng-container> </ng-container>
<ng-template appHasRole [appHasRole]="['iam.features.delete']"> <ng-template cnslHasRole [hasRole]="['iam.features.delete']">
<button *ngIf="serviceType === FeatureServiceType.MGMT && !isDefault" <button *ngIf="serviceType === FeatureServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetFeatures()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetFeatures()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
@ -268,12 +268,12 @@
</div> </div>
</div> </div>
<div class="btn-container" *ngIf="(['iam.features.write'] | hasRole | async) == true"> <div class="btn-container" *ngIf="(['iam.features.write'] | hasRole | async) === true">
<button (click)="savePolicy()" color="primary" <button (click)="savePolicy()" color="primary"
type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button> }}</button>
</div> </div>
</app-detail-layout> </cnsl-detail-layout>
<ng-template #templateRef let-active="active"> <ng-template #templateRef let-active="active">
<span class="state" [ngClass]="{'active': active, 'inactive': !active}"> <span class="state" [ngClass]="{'active': active, 'inactive': !active}">

View File

@ -26,7 +26,7 @@ export enum FeatureServiceType {
} }
@Component({ @Component({
selector: 'app-features', selector: 'cnsl-features',
templateUrl: './features.component.html', templateUrl: './features.component.html',
styleUrls: ['./features.component.scss'], styleUrls: ['./features.component.scss'],
}) })
@ -133,15 +133,16 @@ export class FeaturesComponent implements OnDestroy {
}); });
} }
private async getData(): Promise<GetFeaturesResponse.AsObject | GetOrgFeaturesResponse.AsObject | undefined> { private async getData(): Promise<GetFeaturesResponse.AsObject | GetOrgFeaturesResponse.AsObject> {
switch (this.serviceType) { switch (this.serviceType) {
case FeatureServiceType.MGMT: case FeatureServiceType.MGMT:
return this.managementService.getFeatures(); return this.managementService.getFeatures();
case FeatureServiceType.ADMIN: case FeatureServiceType.ADMIN:
if (this.org?.id) { if (this.org?.id) {
return this.adminService.getDefaultFeatures(); return this.adminService.getDefaultFeatures();
} else {
return Promise.reject();
} }
break;
} }
} }

View File

@ -17,7 +17,7 @@ function compare(a: Country, b: Country): number {
} }
@Component({ @Component({
selector: 'app-payment-info-dialog', selector: 'cnsl-payment-info-dialog',
templateUrl: './payment-info-dialog.component.html', templateUrl: './payment-info-dialog.component.html',
styleUrls: ['./payment-info-dialog.component.scss'], styleUrls: ['./payment-info-dialog.component.scss'],
}) })

View File

@ -5,16 +5,16 @@ let nextUniqueId = 0;
export const CNSL_ERROR = new InjectionToken<CnslErrorDirective>('CnslError'); export const CNSL_ERROR = new InjectionToken<CnslErrorDirective>('CnslError');
@Directive({ @Directive({
selector: '[cnsl-error]', selector: '[cnslError]',
host: { host: {
'class': 'cnsl-error', 'class': 'cnsl-error',
'role': 'alert', 'role': 'alert',
'[attr.id]': 'id', '[attr.id]': 'id',
}, },
providers: [{ provide: CNSL_ERROR, useExisting: CnslErrorDirective }], providers: [{ provide: CNSL_ERROR, useExisting: CnslErrorDirective }],
}) })
export class CnslErrorDirective { export class CnslErrorDirective {
@Input() id: string = `cnsl-error-${nextUniqueId++}`; @Input() id: string = `cnsl-error-${nextUniqueId++}`;
constructor() { } constructor() { }
} }

View File

@ -1,19 +1,19 @@
import { import {
AfterContentInit, AfterContentInit,
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ContentChild, ContentChild,
ContentChildren, ContentChildren,
ElementRef, ElementRef,
HostListener, HostListener,
Inject, Inject,
InjectionToken, InjectionToken,
OnDestroy, OnDestroy,
QueryList, QueryList,
ViewChild, ViewChild,
ViewEncapsulation, ViewEncapsulation,
} from '@angular/core'; } from '@angular/core';
import { NgControl } from '@angular/forms'; import { NgControl } from '@angular/forms';
import { MatFormFieldControl } from '@angular/material/form-field'; import { MatFormFieldControl } from '@angular/material/form-field';
@ -22,142 +22,140 @@ import { startWith, takeUntil } from 'rxjs/operators';
import { cnslFormFieldAnimations } from './animations'; import { cnslFormFieldAnimations } from './animations';
import { CNSL_ERROR, CnslErrorDirective } from './error.directive'; import { CNSL_ERROR, CnslErrorDirective } from './error.directive';
import { _CNSL_HINT, CnslHintDirective } from './hint.directive';
export const CNSL_FORM_FIELD = new InjectionToken<CnslFormFieldComponent>('CnslFormFieldComponent'); export const CNSL_FORM_FIELD = new InjectionToken<CnslFormFieldComponent>('CnslFormFieldComponent');
class CnslFormFieldBase { class CnslFormFieldBase {
constructor(public _elementRef: ElementRef) { } constructor(public _elementRef: ElementRef) { }
} }
@Component({ @Component({
selector: 'cnsl-form-field', selector: 'cnsl-form-field',
templateUrl: './form-field.component.html', templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'], styleUrls: ['./form-field.component.scss'],
providers: [ providers: [
{ provide: CNSL_FORM_FIELD, useExisting: CnslFormFieldComponent }, { provide: CNSL_FORM_FIELD, useExisting: CnslFormFieldComponent },
], ],
host: { host: {
'[class.ng-untouched]': '_shouldForward("untouched")', '[class.ng-untouched]': '_shouldForward("untouched")',
'[class.ng-touched]': '_shouldForward("touched")', '[class.ng-touched]': '_shouldForward("touched")',
'[class.ng-pristine]': '_shouldForward("pristine")', '[class.ng-pristine]': '_shouldForward("pristine")',
'[class.ng-dirty]': '_shouldForward("dirty")', '[class.ng-dirty]': '_shouldForward("dirty")',
'[class.ng-valid]': '_shouldForward("valid")', '[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")', '[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")', '[class.ng-pending]': '_shouldForward("pending")',
'[class.cnsl-form-field-disabled]': '_control.disabled', '[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled', '[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused', '[class.cnsl-focused]': '_control.focused',
'[class.cnsl-form-field-invalid]': '_control.errorState', '[class.cnsl-form-field-invalid]': '_control.errorState',
}, },
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: [cnslFormFieldAnimations.transitionMessages], animations: [cnslFormFieldAnimations.transitionMessages],
}) })
export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestroy, AfterContentInit, AfterViewInit { export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestroy, AfterContentInit, AfterViewInit {
focused: boolean = false; focused: boolean = false;
private _destroyed: Subject<void> = new Subject<void>(); private _destroyed: Subject<void> = new Subject<void>();
@ViewChild('connectionContainer', { static: true }) _connectionContainerRef!: ElementRef; @ViewChild('connectionContainer', { static: true }) _connectionContainerRef!: ElementRef;
@ViewChild('inputContainer') _inputContainerRef!: ElementRef; @ViewChild('inputContainer') _inputContainerRef!: ElementRef;
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>; @ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>; @ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
get _control(): MatFormFieldControl<any> { get _control(): MatFormFieldControl<any> {
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic; return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
}
set _control(value: MatFormFieldControl<any>) {
this._explicitFormFieldControl = value;
}
private _explicitFormFieldControl!: MatFormFieldControl<any>;
readonly stateChanges: Subject<void> = new Subject<void>();
_subscriptAnimationState: string = '';
@ContentChildren(CNSL_ERROR as any, { descendants: true }) _errorChildren!: QueryList<CnslErrorDirective>;
@HostListener('blur', ['false'])
_focusChanged(isFocused: boolean): void {
if (isFocused !== this.focused && (!isFocused)) {
this.focused = isFocused;
this.stateChanges.next();
} }
set _control(value: MatFormFieldControl<any>) { }
this._explicitFormFieldControl = value;
}
private _explicitFormFieldControl!: MatFormFieldControl<any>;
readonly stateChanges: Subject<void> = new Subject<void>();
_subscriptAnimationState: string = ''; constructor(public _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef,
@Inject(ElementRef)
// Use `ElementRef` here so Angular has something to inject.
_labelOptions: any) {
super(_elementRef);
@ContentChildren(CNSL_ERROR as any, { descendants: true }) _errorChildren!: QueryList<CnslErrorDirective>; }
@ContentChildren(_CNSL_HINT, { descendants: true }) _hintChildren!: QueryList<CnslHintDirective>;
@HostListener('blur', ['false']) public ngAfterViewInit(): void {
_focusChanged(isFocused: boolean): void { // Avoid animations on load.
if (isFocused !== this.focused && (!isFocused)) { this._subscriptAnimationState = 'enter';
this.focused = isFocused; this._changeDetectorRef.detectChanges();
this.stateChanges.next(); }
}
public ngOnDestroy(): void {
this._destroyed.next();
this._destroyed.complete();
}
public ngAfterContentInit(): void {
this._validateControlChild();
const control = this._control;
control.stateChanges.pipe(startWith(null)).subscribe(() => {
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
// Run change detection if the value changes.
if (control.ngControl && control.ngControl.valueChanges) {
control.ngControl.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(() => this._changeDetectorRef.markForCheck());
} }
constructor(public _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, // Update the aria-described by when the number of errors changes.
@Inject(ElementRef) this._errorChildren.changes.pipe(startWith(null)).subscribe(() => {
// Use `ElementRef` here so Angular has something to inject. this._syncDescribedByIds();
_labelOptions: any) { this._changeDetectorRef.markForCheck();
super(_elementRef); });
}
/** Throws an error if the form field's control is missing. */
protected _validateControlChild(): void {
if (!this._control) {
throw Error('cnsl-form-field must contain a MatFormFieldControl.');
} }
}
public ngAfterViewInit(): void { private _syncDescribedByIds(): void {
// Avoid animations on load. if (this._control) {
this._subscriptAnimationState = 'enter'; const ids: string[] = [];
this._changeDetectorRef.detectChanges();
if (this._control.userAriaDescribedBy &&
typeof this._control.userAriaDescribedBy === 'string') {
ids.push(...this._control.userAriaDescribedBy.split(' '));
}
if (this._errorChildren) {
ids.push(...this._errorChildren.map(error => error.id));
}
this._control.setDescribedByIds(ids);
} }
}
public ngOnDestroy(): void { /** Determines whether a class from the NgControl should be forwarded to the host element. */
this._destroyed.next(); _shouldForward(prop: keyof NgControl): boolean {
this._destroyed.complete(); const ngControl = this._control ? this._control.ngControl : null;
} return ngControl && ngControl[prop];
}
public ngAfterContentInit(): void { /** Determines whether to display hints or errors. */
this._validateControlChild(); _getDisplayedMessages(): 'error' | 'hint' {
return (this._errorChildren && this._errorChildren.length > 0) ? 'error' : 'hint';
const control = this._control; }
control.stateChanges.pipe(startWith(null)).subscribe(() => {
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
// Run change detection if the value changes.
if (control.ngControl && control.ngControl.valueChanges) {
control.ngControl.valueChanges
.pipe(takeUntil(this._destroyed))
.subscribe(() => this._changeDetectorRef.markForCheck());
}
// Update the aria-described by when the number of errors changes.
this._errorChildren.changes.pipe(startWith(null)).subscribe(() => {
this._syncDescribedByIds();
this._changeDetectorRef.markForCheck();
});
}
/** Throws an error if the form field's control is missing. */
protected _validateControlChild(): void {
if (!this._control) {
throw Error('cnsl-form-field must contain a MatFormFieldControl.');
}
}
private _syncDescribedByIds(): void {
if (this._control) {
const ids: string[] = [];
if (this._control.userAriaDescribedBy &&
typeof this._control.userAriaDescribedBy === 'string') {
ids.push(...this._control.userAriaDescribedBy.split(' '));
}
if (this._errorChildren) {
ids.push(...this._errorChildren.map(error => error.id));
}
this._control.setDescribedByIds(ids);
}
}
/** Determines whether a class from the NgControl should be forwarded to the host element. */
_shouldForward(prop: keyof NgControl): boolean {
const ngControl = this._control ? this._control.ngControl : null;
return ngControl && ngControl[prop];
}
/** Determines whether to display hints or errors. */
_getDisplayedMessages(): 'error' | 'hint' {
return (this._errorChildren && this._errorChildren.length > 0) ? 'error' : 'hint';
}
} }

View File

@ -6,25 +6,22 @@ import { LabelModule } from 'src/app/modules/label/label.module';
import { LabelComponent } from '../label/label.component'; import { LabelComponent } from '../label/label.component';
import { CnslErrorDirective } from './error.directive'; import { CnslErrorDirective } from './error.directive';
import { CnslFormFieldComponent } from './form-field.component'; import { CnslFormFieldComponent } from './form-field.component';
import { CnslHintDirective } from './hint.directive';
@NgModule({ @NgModule({
declarations: [ declarations: [
CnslFormFieldComponent, CnslFormFieldComponent,
CnslErrorDirective, CnslErrorDirective,
CnslHintDirective, ],
], imports: [
imports: [ CommonModule,
CommonModule, MatRippleModule,
MatRippleModule, LabelModule,
LabelModule, ],
], exports: [
exports: [ CnslFormFieldComponent,
CnslFormFieldComponent, LabelComponent,
LabelComponent, CnslErrorDirective,
CnslErrorDirective, ],
CnslHintDirective,
],
}) })
export class FormFieldModule { } export class FormFieldModule { }

View File

@ -1,33 +0,0 @@
import { Directive, InjectionToken, Input } from '@angular/core';
let nextUniqueId = 0;
/**
* Injection token that can be used to reference instances of `MatHint`. It serves as
* alternative token to the actual `MatHint` class which could cause unnecessary
* retention of the class and its directive metadata.
*
* *Note*: This is not part of the public API as the MDC-based form-field will not
* need a lightweight token for `MatHint` and we want to reduce breaking changes.
*/
export const _CNSL_HINT = new InjectionToken<CnslHintDirective>('CnslHintDirective');
/** Hint text to be shown underneath the form field control. */
@Directive({
selector: 'cnsl-hint',
host: {
'class': 'cnsl-hint',
'[class.cnsl-form-field-hint-end]': 'align === "end"',
'[attr.id]': 'id',
// Remove align attribute to prevent it from interfering with layout.
'[attr.align]': 'null',
},
providers: [{ provide: _CNSL_HINT, useExisting: CnslHintDirective }],
})
export class CnslHintDirective {
/** Whether to align the hint label at the start or end of the line. */
@Input() align: 'start' | 'end' = 'start';
/** Unique ID for the hint. Used for the aria-describedby on the form field control. */
@Input() id: string = `mat-hint-${nextUniqueId++}`;
}

View File

@ -17,7 +17,7 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
import { JWT, OIDC, RadioItemIdpType } from './idptypes'; import { JWT, OIDC, RadioItemIdpType } from './idptypes';
@Component({ @Component({
selector: 'app-idp-create', selector: 'cnsl-idp-create',
templateUrl: './idp-create.component.html', templateUrl: './idp-create.component.html',
styleUrls: ['./idp-create.component.scss'], styleUrls: ['./idp-create.component.scss'],
}) })

View File

@ -1,4 +1,4 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length" <cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[emitRefreshOnPreviousRoutes]="['/iam/idp/create']" [timestamp]="idpResult?.details?.viewTimestamp" [emitRefreshOnPreviousRoutes]="['/iam/idp/create']" [timestamp]="idpResult?.details?.viewTimestamp"
[selection]="selection"> [selection]="selection">
<div actions> <div actions>
@ -23,15 +23,15 @@
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null" <mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()" [indeterminate]="selection.hasValue() && !isAllSelected()"
[disabled]="serviceType==PolicyComponentServiceType.MGMT"> [disabled]="serviceType === PolicyComponentServiceType.MGMT">
</mat-checkbox> </mat-checkbox>
</th> </th>
<td mat-cell *matCellDef="let idp"> <td mat-cell *matCellDef="let idp">
<mat-checkbox color="primary" (click)="$event.stopPropagation()" class="chbox" <mat-checkbox color="primary" (click)="$event.stopPropagation()" class="chbox"
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.owner == IDPOwnerType.IDP_OWNER_TYPE_SYSTEM" [disabled]="serviceType === PolicyComponentServiceType.MGMT && idp?.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM"
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)"> (change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
<img src="../../../assets/images/google.png" <img src="../../../assets/images/google.png"
*ngIf="idp.stylingType == IDPSTYLINGTYPE.IDPSTYLINGTYPE_GOOGLE" alt="google" /> *ngIf="idp.stylingType === IDPSTYLINGTYPE.IDPSTYLINGTYPE_GOOGLE" alt="google" />
</mat-checkbox> </mat-checkbox>
</td> </td>
</ng-container> </ng-container>
@ -91,7 +91,7 @@
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let idp"> <td mat-cell *matCellDef="let idp">
<button <button
[disabled]="serviceType==PolicyComponentServiceType.MGMT && idp?.providerType == IDPOwnerType.IDP_OWNER_TYPE_ORG" [disabled]="serviceType === PolicyComponentServiceType.MGMT && idp?.providerType === IDPOwnerType.IDP_OWNER_TYPE_ORG"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}"
(click)="removeIdp(idp)"> (click)="removeIdp(idp)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
@ -107,4 +107,4 @@
</div> </div>
<cnsl-paginator #paginator class="paginator" [timestamp]="idpResult?.details?.viewTimestamp" [length]="idpResult?.details?.totalResult || 0" [pageSize]="10" <cnsl-paginator #paginator class="paginator" [timestamp]="idpResult?.details?.viewTimestamp" [length]="idpResult?.details?.totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator> [pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</app-refresh-table> </cnsl-refresh-table>

View File

@ -17,7 +17,7 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
@Component({ @Component({
selector: 'app-idp-table', selector: 'cnsl-idp-table',
templateUrl: './idp-table.component.html', templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'], styleUrls: ['./idp-table.component.scss'],
}) })

View File

@ -1,176 +1,173 @@
<app-detail-layout [backRouterLink]="backroutes" [title]="idp?.name" <cnsl-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate"
[description]="'IDP.DETAIL.DESCRIPTION' | translate"> [description]="'IDP.DETAIL.DESCRIPTION' | translate">
<div *ngIf="canWrite | async" actions> <div *ngIf="canWrite | async" actions>
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="idpactions"> <button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="idpactions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span> <span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon> <mat-icon class="icon">keyboard_arrow_down</mat-icon>
</button>
<mat-menu #idpactions="matMenu" xPosition="before">
<button mat-menu-item *ngIf="idp?.state !== IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_INACTIVE)">
{{'ACTIONS.DEACTIVATE' | translate}}
</button> </button>
<mat-menu #idpactions="matMenu" xPosition="before"> <button mat-menu-item *ngIf="idp?.state === IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_ACTIVE)">
{{'ACTIONS.REACTIVATE' | translate}}
</button>
<button mat-menu-item matTooltip="{{'IDP.DELETE' | translate}}" (click)="deleteIdp()">
<span [style.color]="'var(--warn)'">{{'IDP.DELETE_TITLE' | translate}}</span>
</button>
</mat-menu>
</div>
<button mat-menu-item <div class="container">
*ngIf="idp?.state !== IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_INACTIVE)">
{{'ACTIONS.DEACTIVATE' | translate}}
</button>
<button mat-menu-item *ngIf="idp?.state == IDPState.IDP_STATE_INACTIVE"
(click)="changeState(IDPState.IDP_STATE_ACTIVE)">
{{'ACTIONS.REACTIVATE' | translate}}
</button>
<button mat-menu-item matTooltip="{{'IDP.DELETE' | translate}}"
(click)="deleteIdp()">
<span [style.color]="'var(--warn)'">{{'IDP.DELETE_TITLE' | translate}}</span>
</button>
</mat-menu>
</div>
<div class="container"> <cnsl-info-row *ngIf="idp" [idp]="idp"></cnsl-info-row>
<form class="idp-form" (ngSubmit)="updateIdp()">
<ng-container [formGroup]="idpForm">
<div class="idp-content">
<cnsl-info-row *ngIf="idp" [idp]="idp"></cnsl-info-row> <cnsl-form-field appearance="outline" class="formfield">
<form class="idp-form" (ngSubmit)="updateIdp()"> <cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<ng-container [formGroup]="idpForm"> <input cnslInput formControlName="name" />
<div class="idp-content"> </cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.STYLE' | translate }}</cnsl-label>
<mat-select formControlName="stylingType">
<mat-option *ngFor="let field of styleFields" [value]="field">
{{ 'IDP.STYLEFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-info-section class="auto-reg-info">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label> <div>
<input cnslInput formControlName="name" /> <p class="auto-reg-desc">{{'IDP.AUTOREGISTER_DESC' | translate}}</p>
</cnsl-form-field> <mat-checkbox formControlName="autoRegister" [disabled]="(canWrite | async) === false">
<cnsl-form-field class="formfield" appearance="outline"> {{'IDP.AUTOREGISTER' | translate}}
<cnsl-label>{{ 'IDP.STYLE' | translate }}</cnsl-label> </mat-checkbox>
<mat-select formControlName="stylingType">
<mat-option *ngFor="let field of styleFields" [value]="field">
{{ 'IDP.STYLEFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-info-section class="auto-reg-info">
<div>
<p class="auto-reg-desc">{{'IDP.AUTOREGISTER_DESC' | translate}}</p>
<mat-checkbox formControlName="autoRegister" [disabled]="(canWrite | async) === false">
{{'IDP.AUTOREGISTER' | translate}}
</mat-checkbox>
</div>
</cnsl-info-section>
</div>
</ng-container>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button" [disabled]="idpForm.invalid || (canWrite | async) === false"
type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div> </div>
</form> </cnsl-info-section>
</div>
</ng-container>
<ng-container *ngIf="idp?.oidcConfig && oidcConfigForm"> <div class="btn-wrapper">
<h2>{{'IDP.OIDC.TITLE' | translate}}</h2> <button color="primary" mat-raised-button class="continue-button"
<p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p> [disabled]="idpForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
<form (ngSubmit)="updateOidcConfig()"> <ng-container *ngIf="idp?.oidcConfig && oidcConfigForm">
<ng-container [formGroup]="oidcConfigForm"> <h2>{{'IDP.OIDC.TITLE' | translate}}</h2>
<div class="idp-content"> <p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
<input cnslInput formControlName="clientId" />
</cnsl-form-field>
<mat-checkbox class="idp-desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false"
[ngModelOptions]="{standalone: true}">
Update Client Secret
</mat-checkbox>
<cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
<div class="line">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<input cnslInput [matChipInputFor]="chipScopesList" <form (ngSubmit)="updateOidcConfig()">
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true" <ng-container [formGroup]="oidcConfigForm">
(matChipInputTokenEnd)="addScope($event)"> <div class="idp-content">
</cnsl-form-field> <cnsl-form-field appearance="outline" class="formfield">
<button (click)="addScope($event)" mat-icon-button> <cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<mat-icon>add</mat-icon> <input cnslInput formControlName="issuer" />
</button> </cnsl-form-field>
</div> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-form-field appearance="outline" class="formfield fullwidth"> <cnsl-label>{{ 'IDP.CLIENTID' | translate }}</cnsl-label>
<mat-chip-list class="chip-list" #chipScopesList aria-label="scope selection"> <input cnslInput formControlName="clientId" />
<mat-chip class="chip" *ngFor="let scope of scopesList?.value" selectable="false" </cnsl-form-field>
removable (removed)="removeScope(scope)" [disabled]="(canWrite | async) === false"> <mat-checkbox class="idp-desc" [(ngModel)]="showIdSecretSection" [disabled]="(canWrite | async) === false"
{{scope}} <mat-icon matChipRemove>cancel</mat-icon> [ngModelOptions]="{standalone: true}">
</mat-chip> Update Client Secret
</mat-chip-list> </mat-checkbox>
</cnsl-form-field> <cnsl-form-field appearance="outline" class="formfield" *ngIf="showIdSecretSection">
<cnsl-label>{{ 'IDP.CLIENTSECRET' | translate }}</cnsl-label>
<input cnslInput formControlName="clientSecret" />
</cnsl-form-field>
<div class="line">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.SCOPESLIST' | translate }}</cnsl-label>
<cnsl-form-field class="formfield" appearance="outline"> <input cnslInput [matChipInputFor]="chipScopesList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label> [matChipInputAddOnBlur]="true" (matChipInputTokenEnd)="addScope($event)">
<mat-select formControlName="displayNameMapping"> </cnsl-form-field>
<mat-option *ngFor="let field of mappingFields" [value]="field"> <button (click)="addScope($any($event))" mat-icon-button>
{{ 'IDP.MAPPINGFIELD.'+field | translate }} <mat-icon>add</mat-icon>
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</ng-container>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="oidcConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button> </button>
</div> </div>
</form> <cnsl-form-field appearance="outline" class="formfield fullwidth">
<mat-chip-list class="chip-list" #chipScopesList aria-label="scope selection">
<mat-chip class="chip" *ngFor="let scope of scopesList?.value" selectable="false" removable
(removed)="removeScope(scope)" [disabled]="(canWrite | async) === false">
{{scope}} <mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.IDPDISPLAYNAMMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="displayNameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield" appearance="outline">
<cnsl-label>{{ 'IDP.USERNAMEMAPPING' | translate }}</cnsl-label>
<mat-select formControlName="usernameMapping">
<mat-option *ngFor="let field of mappingFields" [value]="field">
{{ 'IDP.MAPPINGFIELD.'+field | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</ng-container> </ng-container>
<ng-container *ngIf="idp?.jwtConfig && jwtConfigForm"> <div class="btn-wrapper">
<h2>{{'IDP.JWT.TITLE' | translate}}</h2> <button color="primary" mat-raised-button class="continue-button"
<p>{{'IDP.JWT.DESCRIPTION' | translate}}</p> [disabled]="oidcConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container>
<form (ngSubmit)="updateJwtConfig()"> <ng-container *ngIf="idp?.jwtConfig && jwtConfigForm">
<ng-container [formGroup]="jwtConfigForm"> <h2>{{'IDP.JWT.TITLE' | translate}}</h2>
<div class="idp-content"> <p>{{'IDP.JWT.DESCRIPTION' | translate}}</p>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <form (ngSubmit)="updateJwtConfig()">
<cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label> <ng-container [formGroup]="jwtConfigForm">
<input cnslInput formControlName="headerName" /> <div class="idp-content">
</cnsl-form-field> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTENDPOINT' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtEndpoint" /> <input cnslInput formControlName="headerName" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield"> <cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.JWT.JWTENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="keysEndpoint" /> <input cnslInput formControlName="jwtEndpoint" />
</cnsl-form-field> </cnsl-form-field>
</div>
</ng-container>
<div class="btn-wrapper"> <cnsl-form-field appearance="outline" class="formfield">
<button color="primary" mat-raised-button class="continue-button" <cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label>
[disabled]="jwtConfigForm.invalid || (canWrite | async) === false" type="submit"> <input cnslInput formControlName="keysEndpoint" />
{{ 'ACTIONS.SAVE' | translate }} </cnsl-form-field>
</button> </div>
</div> </ng-container>
</form>
</ng-container>
</div>
</app-detail-layout> <div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="jwtConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</ng-container>
</div>
</cnsl-detail-layout>

View File

@ -27,7 +27,7 @@ import { PolicyComponentServiceType } from '../policies/policy-component-types.e
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
@Component({ @Component({
selector: 'app-idp', selector: 'cnsl-idp',
templateUrl: './idp.component.html', templateUrl: './idp.component.html',
styleUrls: ['./idp.component.scss'], styleUrls: ['./idp.component.scss'],
}) })

View File

@ -2,27 +2,30 @@
<div class="info"> <div class="info">
<p class="title">{{ 'USER.PAGES.STATE' | translate }}</p> <p class="title">{{ 'USER.PAGES.STATE' | translate }}</p>
<p *ngIf="user && user.state !== undefined" class="state" <p *ngIf="user && user.state !== undefined" class="state"
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">{{'USER.DATA.STATE'+user.state [ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">
| translate}}</p> {{'USER.DATA.STATE'+user.state
| translate}}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'USER.DETAILS.DATECREATED' | translate }}</p> <p class="title">{{ 'USER.DETAILS.DATECREATED' | translate }}</p>
<p class="desc">{{user?.details?.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p *ngIf="user && user.details && user.details.creationDate" class="desc">{{user.details.creationDate |
timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'USER.DETAILS.DATECHANGED' | translate }}</p> <p class="title">{{ 'USER.DETAILS.DATECHANGED' | translate }}</p>
<p class="desc">{{user?.details?.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p *ngIf="user && user.details && user.details.changeDate" class="desc">{{user.details.changeDate | timestampToDate
| localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info width"> <div class="info width">
<p class="title">{{ 'USER.PAGES.LOGINNAMES' | translate }}</p> <p class="title">{{ 'USER.PAGES.LOGINNAMES' | translate }}</p>
<div class="copy-row" *ngFor="let login of user?.loginNamesList"> <div class="copy-row" *ngFor="let login of user?.loginNamesList">
<button [disabled]="copied == login" <button [disabled]="copied === login"
[matTooltip]="(copied != login ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" [matTooltip]="(copied !== login ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" cnslCopyToClipboard
appCopyToClipboard [valueToCopy]="login" (copiedValue)="copied = $event"> [valueToCopy]="login" (copiedValue)="copied = $event">
{{login}} {{login}}
</button> </button>
</div> </div>
</div> </div>
@ -32,35 +35,38 @@
<div class="info"> <div class="info">
<p class="title">{{ 'APP.PAGES.STATE' | translate }}</p> <p class="title">{{ 'APP.PAGES.STATE' | translate }}</p>
<p *ngIf="app && app.state !== undefined" class="state" <p *ngIf="app && app.state !== undefined" class="state"
[ngClass]="{'active': app.state === AppState.APP_STATE_ACTIVE, 'inactive': app.state === AppState.APP_STATE_INACTIVE}">{{'APP.PAGES.DETAIL.STATE.'+app.state [ngClass]="{'active': app.state === AppState.APP_STATE_ACTIVE, 'inactive': app.state === AppState.APP_STATE_INACTIVE}">
| translate}}</p> {{'APP.PAGES.DETAIL.STATE.'+app.state
| translate}}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'APP.PAGES.DATECREATED' | translate }}</p> <p class="title">{{ 'APP.PAGES.DATECREATED' | translate }}</p>
<p class="desc">{{app?.details?.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p *ngIf="app && app.details && app.details.creationDate" class="desc">{{app.details.creationDate | timestampToDate
| localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p> <p class="title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p>
<p class="desc">{{app?.details?.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p *ngIf="app && app.details && app.details.changeDate" class="desc">{{app.details.changeDate | timestampToDate |
localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info" > <div class="info">
<p class="title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p> <p class="title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p>
<div class="copy-row" *ngIf="app?.oidcConfig?.clientId"> <div class="copy-row" *ngIf="app?.oidcConfig?.clientId">
<button [disabled]="copied == app.oidcConfig?.clientId" <button *ngIf="app.oidcConfig && app.oidcConfig?.clientId" [disabled]="copied === app.oidcConfig?.clientId"
[matTooltip]="(copied != app.oidcConfig?.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" [matTooltip]="(copied !== app.oidcConfig?.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate"
appCopyToClipboard [valueToCopy]="app.oidcConfig?.clientId" (copiedValue)="copied = $event"> cnslCopyToClipboard [valueToCopy]="app.oidcConfig.clientId" (copiedValue)="copied = $event">
{{app.oidcConfig?.clientId}} {{app.oidcConfig?.clientId}}
</button> </button>
</div> </div>
<div class="copy-row" *ngIf="app?.apiConfig?.clientId"> <div class="copy-row" *ngIf="app?.apiConfig?.clientId">
<button [disabled]="copied == app.apiConfig?.clientId" <button *ngIf="app && app.apiConfig && app.apiConfig.clientId" [disabled]="copied === app.apiConfig?.clientId"
[matTooltip]="(copied != app.apiConfig?.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" [matTooltip]="(copied !== app.apiConfig?.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate"
appCopyToClipboard [valueToCopy]="app.apiConfig?.clientId" (copiedValue)="copied = $event"> cnslCopyToClipboard [valueToCopy]="app.apiConfig.clientId" (copiedValue)="copied = $event">
{{app.apiConfig?.clientId}} {{app.apiConfig?.clientId}}
</button> </button>
</div> </div>
</div> </div>
@ -68,15 +74,14 @@
<div class="info"> <div class="info">
<p class="title">{{ 'APP.PAGES.URLS' | translate }}</p> <p class="title">{{ 'APP.PAGES.URLS' | translate }}</p>
<div class="copy-row" *ngFor="let environmentV of (environmentMap | keyvalue)"> <div class="copy-row" *ngFor="let environmentV of (environmentMap | keyvalue)">
<div *ngIf="environmentV.value" class="environment"> <div *ngIf="environmentV.value" class="environment">
<span class="key">{{environmentV.key}}</span> <span class="key">{{environmentV.key}}</span>
<button [disabled]="copied == environmentV.value" <button [disabled]="copied === environmentV.value"
[matTooltip]="(copied != environmentV.value ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" [matTooltip]="(copied !== environmentV.value ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate"
appCopyToClipboard [valueToCopy]="environmentV.value" cnslCopyToClipboard [valueToCopy]="environmentV.value" (copiedValue)="copied = environmentV.key">
(copiedValue)="copied = environmentV.key"> {{environmentV.value}}
{{environmentV.value}} </button>
</button> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -85,28 +90,31 @@
<div class="info width"> <div class="info width">
<p class="title">{{ 'IDP.ID' | translate }}</p> <p class="title">{{ 'IDP.ID' | translate }}</p>
<div class="copy-row"> <div class="copy-row">
<button [disabled]="copied == idp.id" <button [disabled]="copied === idp.id"
[matTooltip]="(copied != idp.id ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" [matTooltip]="(copied !== idp.id ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate" cnslCopyToClipboard
appCopyToClipboard [valueToCopy]="idp.id" (copiedValue)="copied = $event"> [valueToCopy]="idp.id" (copiedValue)="copied = $event">
{{idp.id}} {{idp.id}}
</button> </button>
</div> </div>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'IDP.DETAIL.DATECREATED' | translate }}</p> <p class="title">{{ 'IDP.DETAIL.DATECREATED' | translate }}</p>
<p class="desc">{{idp?.details?.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p class="desc" *ngIf="idp && idp.details && idp.details.creationDate">{{idp.details.creationDate | timestampToDate
| localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'IDP.DETAIL.DATECHANGED' | translate }}</p> <p class="title">{{ 'IDP.DETAIL.DATECHANGED' | translate }}</p>
<p class="desc">{{idp?.details?.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p> <p class="desc" *ngIf="idp && idp.details && idp.details.changeDate">{{idp.details.changeDate | timestampToDate |
localizedDate: 'dd. MMMM YYYY, HH:mm' }}</p>
</div> </div>
<div class="info"> <div class="info">
<p class="title">{{ 'IDP.STATE' | translate }}</p> <p class="title">{{ 'IDP.STATE' | translate }}</p>
<p *ngIf="idp && idp.state !== undefined" class="state" <p *ngIf="idp && idp.state !== undefined" class="state"
[ngClass]="{'active': idp.state === IDPState.IDP_STATE_ACTIVE, 'inactive': idp.state === IDPState.IDP_STATE_INACTIVE}">{{'IDP.STATES.'+idp.state [ngClass]="{'active': idp.state === IDPState.IDP_STATE_ACTIVE, 'inactive': idp.state === IDPState.IDP_STATE_INACTIVE}">
| translate}}</p> {{'IDP.STATES.'+idp.state
| translate}}</p>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="info-section-row" [ngClass]="{'info': type == 'INFO', 'warn': type == 'WARN'}"> <div class="info-section-row" [ngClass]="{'info': type === 'INFO', 'warn': type === 'WARN'}">
<i *ngIf="type == 'INFO'" class="icon las la-info"></i> <i *ngIf="type === 'INFO'" class="icon las la-info"></i>
<i *ngIf="type == 'WARN'" class="icon las la-exclamation"></i> <i *ngIf="type === 'WARN'" class="icon las la-exclamation"></i>
<div class="info-section-content"> <div class="info-section-content">
<ng-content></ng-content> <ng-content></ng-content>

View File

@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
enum InfoSectionType { export enum InfoSectionType {
INFO = 'INFO', INFO = 'INFO',
SUCCESS = 'SUCCESS', SUCCESS = 'SUCCESS',
WARN = 'WARN', WARN = 'WARN',
@ -14,5 +14,5 @@ enum InfoSectionType {
export class InfoSectionComponent { export class InfoSectionComponent {
@Input() type: InfoSectionType = InfoSectionType.INFO; @Input() type: InfoSectionType = InfoSectionType.INFO;
@Input() featureLink: string = ''; @Input() featureLink: string | string[] = '';
} }

View File

@ -184,7 +184,7 @@ export class InputDirective extends _MatInputMixinBase implements MatFormFieldCo
* Implemented as part of MatFormFieldControl. * Implemented as part of MatFormFieldControl.
* @docs-private * @docs-private
*/ */
// tslint:disable-next-line:no-input-rename // eslint-disable-next-line @angular-eslint/no-input-rename
@Input('aria-describedby') userAriaDescribedBy!: string; @Input('aria-describedby') userAriaDescribedBy!: string;
/** /**
@ -319,10 +319,10 @@ export class InputDirective extends _MatInputMixinBase implements MatFormFieldCo
// In Ivy the `host` bindings will be merged when this class is extended, whereas in // In Ivy the `host` bindings will be merged when this class is extended, whereas in
// ViewEngine they're overwritten. // ViewEngine they're overwritten.
/** Callback for the cases where the focused state of the input changes. */ /** Callback for the cases where the focused state of the input changes. */
// tslint:disable:no-host-decorator-in-concrete /* eslint-disable */
@HostListener('focus', ['true']) @HostListener('focus', ['true'])
@HostListener('blur', ['false']) @HostListener('blur', ['false'])
// tslint:enable:no-host-decorator-in-concrete /* eslint-enable */
_focusChanged(isFocused: boolean): void { _focusChanged(isFocused: boolean): void {
if (isFocused !== this.focused && (!this.readonly || !isFocused)) { if (isFocused !== this.focused && (!this.readonly || !isFocused)) {
this.focused = isFocused; this.focused = isFocused;
@ -333,7 +333,7 @@ export class InputDirective extends _MatInputMixinBase implements MatFormFieldCo
// We have to use a `HostListener` here in order to support both Ivy and ViewEngine. // We have to use a `HostListener` here in order to support both Ivy and ViewEngine.
// In Ivy the `host` bindings will be merged when this class is extended, whereas in // In Ivy the `host` bindings will be merged when this class is extended, whereas in
// ViewEngine they're overwritten. // ViewEngine they're overwritten.
// tslint:disable-next-line:no-host-decorator-in-concrete // eslint-disable-next-line
@HostListener('input') @HostListener('input')
_onInput(): void { _onInput(): void {
// This is a noop function and is used to let Angular know whenever the value changes. // This is a noop function and is used to let Angular know whenever the value changes.
@ -441,7 +441,7 @@ export class InputDirective extends _MatInputMixinBase implements MatFormFieldCo
this.focus(); this.focus();
} }
} }
// tslint:disable /* eslint-disable */
static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_disabled: BooleanInput;
static ngAcceptInputType_readonly: BooleanInput; static ngAcceptInputType_readonly: BooleanInput;
static ngAcceptInputType_required: BooleanInput; static ngAcceptInputType_required: BooleanInput;
@ -449,5 +449,5 @@ export class InputDirective extends _MatInputMixinBase implements MatFormFieldCo
// Accept `any` to avoid conflicts with other directives on `<input>` that may // Accept `any` to avoid conflicts with other directives on `<input>` that may
// accept different types. // accept different types.
static ngAcceptInputType_value: any; static ngAcceptInputType_value: any;
// tslint:enable /* eslint-enable */
} }

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="row"> <div class="row">
<ng-container *ngFor="let link of links"> <ng-container *ngFor="let link of links">
<ng-template *ngIf="link.withRole" appHasRole [appHasRole]="link.withRole"> <ng-template *ngIf="link.withRole" cnslHasRole [hasRole]="link.withRole">
<div class="step card"> <div class="step card">
<ng-content select="[icon]"></ng-content> <ng-content select="[icon]"></ng-content>
<h6>{{ link.i18nTitle | translate }}</h6> <h6>{{ link.i18nTitle | translate }}</h6>

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input } from '@angular/core';
export interface CnslLinks { export interface CnslLinks {
@ -7,7 +7,7 @@ export interface CnslLinks {
routerLink?: any; routerLink?: any;
href?: string; href?: string;
iconClasses?: string; iconClasses?: string;
withRole?: Array<string | RegExp>; withRole?: string[] | RegExp[];
} }
@Component({ @Component({
@ -15,11 +15,6 @@ export interface CnslLinks {
templateUrl: './links.component.html', templateUrl: './links.component.html',
styleUrls: ['./links.component.scss'], styleUrls: ['./links.component.scss'],
}) })
export class LinksComponent implements OnInit { export class LinksComponent {
@Input() links: Array<CnslLinks> = []; @Input() links: Array<CnslLinks> = [];
constructor() { }
ngOnInit(): void {
}
} }

View File

@ -1,7 +1,7 @@
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length" <cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[timestamp]="keyResult?.details?.viewTimestamp" [selection]="selection"> [timestamp]="keyResult?.details?.viewTimestamp" [selection]="selection">
<div actions> <div actions>
<a [disabled]="([('user.write:' + userId), 'user.write'] | hasRole | async) == false" color="primary" <a [disabled]="([('user.write:' + userId), 'user.write'] | hasRole | async) === false" color="primary"
mat-raised-button (click)="openAddKey()"> mat-raised-button (click)="openAddKey()">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a> </a>
@ -50,7 +50,7 @@
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let key"> <td mat-cell *matCellDef="let key">
<button [disabled]="([('user.write:' + userId), 'user.write'] | hasRole | async) == false" <button [disabled]="([('user.write:' + userId), 'user.write'] | hasRole | async) === false"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}" mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}"
(click)="deleteKey(key)"> (click)="deleteKey(key)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
@ -67,4 +67,4 @@
<cnsl-paginator #paginator class="paginator" [timestamp]="keyResult?.details?.viewTimestamp" [length]="keyResult?.details?.totalResult || 0" [pageSize]="10" <cnsl-paginator #paginator class="paginator" [timestamp]="keyResult?.details?.viewTimestamp" [length]="keyResult?.details?.totalResult || 0" [pageSize]="10"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator> [pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</div> </div>
</app-refresh-table> </cnsl-refresh-table>

View File

@ -16,125 +16,125 @@ import { ToastService } from 'src/app/services/toast.service';
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
@Component({ @Component({
selector: 'app-machine-keys', selector: 'cnsl-machine-keys',
templateUrl: './machine-keys.component.html', templateUrl: './machine-keys.component.html',
styleUrls: ['./machine-keys.component.scss'], styleUrls: ['./machine-keys.component.scss'],
}) })
export class MachineKeysComponent implements OnInit { export class MachineKeysComponent implements OnInit {
@Input() userId!: string; @Input() userId!: string;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent; @ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<Key.AsObject> = new MatTableDataSource<Key.AsObject>(); public dataSource: MatTableDataSource<Key.AsObject> = new MatTableDataSource<Key.AsObject>();
public selection: SelectionModel<Key.AsObject> = new SelectionModel<Key.AsObject>(true, []); public selection: SelectionModel<Key.AsObject> = new SelectionModel<Key.AsObject>(true, []);
public keyResult!: ListMachineKeysResponse.AsObject; public keyResult!: ListMachineKeysResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = ['select', 'id', 'type', 'creationDate', 'expirationDate', 'actions']; @Input() public displayedColumns: string[] = ['select', 'id', 'type', 'creationDate', 'expirationDate', 'actions'];
@Output() public changedSelection: EventEmitter<Array<Key.AsObject>> = new EventEmitter(); @Output() public changedSelection: EventEmitter<Array<Key.AsObject>> = new EventEmitter();
constructor(public translate: TranslateService, private mgmtService: ManagementService, private dialog: MatDialog, constructor(public translate: TranslateService, private mgmtService: ManagementService, private dialog: MatDialog,
private toast: ToastService) { private toast: ToastService) {
this.selection.changed.subscribe(() => { this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected); this.changedSelection.emit(this.selection.selected);
}); });
} }
public ngOnInit(): void { public ngOnInit(): void {
this.getData(10, 0); this.getData(10, 0);
} }
public isAllSelected(): boolean { public isAllSelected(): boolean {
const numSelected = this.selection.selected.length; const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length; const numRows = this.dataSource.data.length;
return numSelected === numRows; return numSelected === numRows;
} }
public masterToggle(): void { public masterToggle(): void {
this.isAllSelected() ? this.isAllSelected() ?
this.selection.clear() : this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row)); this.dataSource.data.forEach(row => this.selection.select(row));
} }
public changePage(event: PageEvent): void { public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize); this.getData(event.pageSize, event.pageIndex * event.pageSize);
} }
public deleteKey(key: Key.AsObject): void { public deleteKey(key: Key.AsObject): void {
this.mgmtService.removeMachineKey(key.id, this.userId).then(() => { this.mgmtService.removeMachineKey(key.id, this.userId).then(() => {
this.selection.clear(); this.selection.clear();
this.toast.showInfo('USER.TOAST.SELECTEDKEYSDELETED', true); this.toast.showInfo('USER.TOAST.SELECTEDKEYSDELETED', true);
this.getData(10, 0); this.getData(10, 0);
}).catch(error => { }).catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
public openAddKey(): void { public openAddKey(): void {
const dialogRef = this.dialog.open(AddKeyDialogComponent, { const dialogRef = this.dialog.open(AddKeyDialogComponent, {
data: {}, data: {},
width: '400px', width: '400px',
}); });
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe(resp => {
if (resp) { if (resp) {
const type: KeyType = resp.type; const type: KeyType = resp.type;
let date: Timestamp | undefined; let date: Timestamp | undefined;
if (resp.date as Moment) { if (resp.date as Moment) {
const ts = new Timestamp(); const ts = new Timestamp();
const milliseconds = resp.date.toDate().getTime(); const milliseconds = resp.date.toDate().getTime();
const seconds = Math.abs(milliseconds / 1000); const seconds = Math.abs(milliseconds / 1000);
const nanos = (milliseconds - seconds * 1000) * 1000 * 1000; const nanos = (milliseconds - seconds * 1000) * 1000 * 1000;
ts.setSeconds(seconds); ts.setSeconds(seconds);
ts.setNanos(nanos); ts.setNanos(nanos);
date = ts; date = ts;
}
if (type) {
return this.mgmtService.addMachineKey(this.userId, type, date).then((response) => {
if (response) {
setTimeout(() => {
this.refreshPage();
}, 1000);
this.dialog.open(ShowKeyDialogComponent, {
data: {
key: response,
type: AddKeyDialogType.MACHINE,
},
width: '400px',
});
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
}
});
}
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
if (this.userId) {
this.mgmtService.listMachineKeys(this.userId, limit, offset).then(resp => {
this.keyResult = resp;
if (resp.resultList) {
this.dataSource.data = resp.resultList;
}
this.loadingSubject.next(false);
}).catch((error: any) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} }
}
public refreshPage(): void { if (type) {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize); this.mgmtService.addMachineKey(this.userId, type, date).then((response) => {
if (response) {
setTimeout(() => {
this.refreshPage();
}, 1000);
this.dialog.open(ShowKeyDialogComponent, {
data: {
key: response,
type: AddKeyDialogType.MACHINE,
},
width: '400px',
});
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
}
});
}
private async getData(limit: number, offset: number): Promise<void> {
this.loadingSubject.next(true);
if (this.userId) {
this.mgmtService.listMachineKeys(this.userId, limit, offset).then(resp => {
this.keyResult = resp;
if (resp.resultList) {
this.dataSource.data = resp.resultList;
}
this.loadingSubject.next(false);
}).catch((error: any) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} }
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
}
} }

View File

@ -1,4 +1,4 @@
<app-refresh-table *ngIf="dataSource" (refreshed)="changePage()" [dataSize]="dataSource.totalResult" <cnsl-refresh-table *ngIf="dataSource" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
[timestamp]="dataSource.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async"> [timestamp]="dataSource.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
<ng-container actions *ngIf="selection.hasValue()"> <ng-container actions *ngIf="selection.hasValue()">
@ -21,9 +21,9 @@
<td class="selection" mat-cell *matCellDef="let row"> <td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox [disabled]="!canWrite" color="primary" (click)="$event.stopPropagation()" <mat-checkbox [disabled]="!canWrite" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)"> (change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
<app-avatar *ngIf="row?.displayName && row.firstName && row.lastName; else cog" class="avatar" <cnsl-avatar *ngIf="row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
[name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [avatarUrl]="row.avatarUrl|| ''" [forColor]="row?.preferredLoginName" [size]="32"> [name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [avatarUrl]="row.avatarUrl|| ''" [forColor]="row?.preferredLoginName" [size]="32">
</app-avatar> </cnsl-avatar>
<ng-template #cog> <ng-template #cog>
<div class="sa-icon"> <div class="sa-icon">
<i class="las la-user-cog"></i> <i class="las la-user-cog"></i>
@ -94,4 +94,4 @@
<cnsl-paginator *ngIf="dataSource" class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [pageSize]="INITIALPAGESIZE" <cnsl-paginator *ngIf="dataSource" class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [pageSize]="INITIALPAGESIZE"
[length]="dataSource.totalResult" [pageSizeOptions]="[25, 50, 100, 250]" (page)="changePage($event)"> [length]="dataSource.totalResult" [pageSizeOptions]="[25, 50, 100, 250]" (page)="changePage($event)">
</cnsl-paginator> </cnsl-paginator>
</app-refresh-table> </cnsl-refresh-table>

View File

@ -6,76 +6,79 @@ import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { IamMembersDataSource } from 'src/app/pages/iam/iam-members/iam-members-datasource'; import { IamMembersDataSource } from 'src/app/pages/iam/iam-members/iam-members-datasource';
import { OrgMembersDataSource } from 'src/app/pages/orgs/org-members/org-members-datasource'; import { OrgMembersDataSource } from 'src/app/pages/orgs/org-members/org-members-datasource';
import {
ProjectGrantMembersDataSource,
} from 'src/app/pages/projects/owned-projects/project-grant-detail/project-grant-members-datasource';
import { Member } from 'src/app/proto/generated/zitadel/member_pb'; import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { PageEvent, PaginatorComponent } from '../paginator/paginator.component'; import { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
import { ProjectMembersDataSource } from '../project-members/project-members-datasource'; import { ProjectMembersDataSource } from '../project-members/project-members-datasource';
type MemberDatasource = OrgMembersDataSource | ProjectMembersDataSource | IamMembersDataSource; type MemberDatasource = OrgMembersDataSource | ProjectMembersDataSource | ProjectGrantMembersDataSource | IamMembersDataSource;
@Component({ @Component({
selector: 'app-members-table', selector: 'cnsl-members-table',
templateUrl: './members-table.component.html', templateUrl: './members-table.component.html',
styleUrls: ['./members-table.component.scss'], styleUrls: ['./members-table.component.scss'],
}) })
export class MembersTableComponent implements OnInit, OnDestroy { export class MembersTableComponent implements OnInit, OnDestroy {
public INITIALPAGESIZE: number = 25; public INITIALPAGESIZE: number = 25;
@Input() public canDelete: boolean = false; @Input() public canDelete: boolean | null = false;
@Input() public canWrite: boolean = false; @Input() public canWrite: boolean | null = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent; @ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatTable) public table!: MatTable<Member.AsObject>; @ViewChild(MatTable) public table!: MatTable<Member.AsObject>;
@Input() public dataSource!: MemberDatasource; @Input() public dataSource!: MemberDatasource;
public selection: SelectionModel<any> = new SelectionModel<any>(true, []); public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
@Input() public memberRoleOptions: string[] = []; @Input() public memberRoleOptions: string[] = [];
@Input() public factoryLoadFunc!: Function; @Input() public factoryLoadFunc!: Function;
@Input() public refreshTrigger!: Observable<void>; @Input() public refreshTrigger!: Observable<void>;
@Output() public updateRoles: EventEmitter<{ member: Member.AsObject, change: MatSelectChange; }> = new EventEmitter(); @Output() public updateRoles: EventEmitter<{ member: Member.AsObject, change: MatSelectChange; }> = new EventEmitter();
@Output() public changedSelection: EventEmitter<any[]> = new EventEmitter(); @Output() public changedSelection: EventEmitter<any[]> = new EventEmitter();
@Output() public deleteMember: EventEmitter<Member.AsObject> = new EventEmitter(); @Output() public deleteMember: EventEmitter<Member.AsObject> = new EventEmitter();
private destroyed: Subject<void> = new Subject(); private destroyed: Subject<void> = new Subject();
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
public displayedColumns: string[] = ['select', 'userId', 'firstname', 'lastname', 'loginname', 'email', 'roles']; public displayedColumns: string[] = ['select', 'userId', 'firstname', 'lastname', 'loginname', 'email', 'roles'];
constructor() { constructor() {
this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe(_ => { this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe(_ => {
this.changedSelection.emit(this.selection.selected); this.changedSelection.emit(this.selection.selected);
}); });
}
public ngOnInit(): void {
this.refreshTrigger.pipe(takeUntil(this.destroyed)).subscribe(() => {
this.changePage(this.paginator);
});
if (this.canDelete) {
this.displayedColumns.push('actions');
} }
}
public ngOnInit(): void { public ngOnDestroy(): void {
this.refreshTrigger.pipe(takeUntil(this.destroyed)).subscribe(() => { this.destroyed.next();
this.changePage(this.paginator); }
});
if (this.canDelete) { public isAllSelected(): boolean {
this.displayedColumns.push('actions'); const numSelected = this.selection.selected.length;
} const numRows = this.dataSource.membersSubject.value.length;
} return numSelected === numRows;
}
public ngOnDestroy(): void { public masterToggle(): void {
this.destroyed.next(); this.isAllSelected() ?
} this.selection.clear() :
this.dataSource.membersSubject.value.forEach(row => this.selection.select(row));
}
public isAllSelected(): boolean { public changePage(event?: PageEvent): any {
const numSelected = this.selection.selected.length; this.selection.clear();
const numRows = this.dataSource.membersSubject.value.length; return this.factoryLoadFunc(event ?? this.paginator);
return numSelected === numRows; }
}
public masterToggle(): void { public triggerDeleteMember(member: any): void {
this.isAllSelected() ? this.deleteMember.emit(member);
this.selection.clear() : }
this.dataSource.membersSubject.value.forEach(row => this.selection.select(row));
}
public changePage(event?: PageEvent): any {
this.selection.clear();
return this.factoryLoadFunc(event ?? this.paginator);
}
public triggerDeleteMember(member: any): void {
this.deleteMember.emit(member);
}
} }

View File

@ -4,19 +4,19 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@Component({ @Component({
selector: 'app-meta-layout', selector: 'cnsl-meta-layout',
templateUrl: './meta-layout.component.html', templateUrl: './meta-layout.component.html',
styleUrls: ['./meta-layout.component.scss'], styleUrls: ['./meta-layout.component.scss'],
}) })
export class MetaLayoutComponent { export class MetaLayoutComponent {
constructor(private breakpointObserver: BreakpointObserver) { constructor(private breakpointObserver: BreakpointObserver) {
this.isSmallScreen$.subscribe(small => this.hidden = small); this.isSmallScreen$.subscribe(small => this.hidden = small);
} }
public hidden: boolean = false; public hidden: boolean = false;
public isSmallScreen$: Observable<boolean> = this.breakpointObserver public isSmallScreen$: Observable<boolean> = this.breakpointObserver
.observe('(max-width: 1000px)') .observe('(max-width: 1000px)')
.pipe(map(result => { .pipe(map(result => {
return result.matches; return result.matches;
})); }));
} }

View File

@ -2,7 +2,7 @@ import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({ @Component({
selector: 'app-name-dialog', selector: 'cnsl-name-dialog',
templateUrl: './name-dialog.component.html', templateUrl: './name-dialog.component.html',
styleUrls: ['./name-dialog.component.scss'], styleUrls: ['./name-dialog.component.scss'],
}) })

View File

@ -2,56 +2,56 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Timestamp } from 'src/app/proto/generated/google/protobuf/timestamp_pb'; import { Timestamp } from 'src/app/proto/generated/google/protobuf/timestamp_pb';
export interface PageEvent { export interface PageEvent {
length: number; length: number;
pageSize: number; pageSize: number;
pageIndex: number; pageIndex: number;
pageSizeOptions: Array<number>; pageSizeOptions: Array<number>;
} }
@Component({ @Component({
selector: 'cnsl-paginator', selector: 'cnsl-paginator',
templateUrl: './paginator.component.html', templateUrl: './paginator.component.html',
styleUrls: ['./paginator.component.scss'], styleUrls: ['./paginator.component.scss'],
}) })
export class PaginatorComponent { export class PaginatorComponent {
@Input() public timestamp!: Timestamp.AsObject; @Input() public timestamp: Timestamp.AsObject | undefined = undefined;
@Input() public length: number = 0; @Input() public length: number = 0;
@Input() public pageSize: number = 10; @Input() public pageSize: number = 10;
@Input() public pageIndex: number = 0; @Input() public pageIndex: number = 0;
@Input() public pageSizeOptions: Array<number> = [10, 25, 50]; @Input() public pageSizeOptions: Array<number> = [10, 25, 50];
@Output() public page: EventEmitter<PageEvent> = new EventEmitter(); @Output() public page: EventEmitter<PageEvent> = new EventEmitter();
constructor() { } constructor() { }
public previous(): void { public previous(): void {
if (this.previousPossible) { if (this.previousPossible) {
this.pageIndex = this.pageIndex - 1; this.pageIndex = this.pageIndex - 1;
this.emitChange(); this.emitChange();
}
} }
}
public next(): void { public next(): void {
if (this.nextPossible) { if (this.nextPossible) {
this.pageIndex = this.pageIndex + 1; this.pageIndex = this.pageIndex + 1;
this.emitChange(); this.emitChange();
}
} }
}
get previousPossible(): boolean { get previousPossible(): boolean {
const temp = this.pageIndex - 1; const temp = this.pageIndex - 1;
return (temp >= 0); return (temp >= 0);
} }
get nextPossible(): boolean { get nextPossible(): boolean {
const temp = this.pageIndex + 1; const temp = this.pageIndex + 1;
return (temp <= (this.length / this.pageSize)); return (temp <= (this.length / this.pageSize));
} }
public emitChange(): void { public emitChange(): void {
this.page.emit({ this.page.emit({
length: this.length, length: this.length,
pageSize: this.pageSize, pageSize: this.pageSize,
pageIndex: this.pageIndex, pageIndex: this.pageIndex,
pageSizeOptions: this.pageSizeOptions, pageSizeOptions: this.pageSizeOptions,
}); });
} }
} }

View File

@ -1,10 +1,10 @@
<div class="validation-col" *ngIf="this.policy"> <div class="validation-col" *ngIf="this.policy">
<div class="val" *ngIf="this.policy.minLength"> <div class="val" *ngIf="this.policy.minLength">
<i *ngIf="password?.value?.length == 0; else showSpinner" class="las la-times red"></i> <i *ngIf="password?.value?.length === 0; else showSpinner" class="las la-times red"></i>
<ng-template #showSpinner> <ng-template #showSpinner>
<div *ngIf="(password?.errors?.minlength || password?.value?.length == 0) as currentError; else trueminlength" <div *ngIf="(password?.errors?.minlength || password?.value?.length === 0) as currentError; else trueminlength"
class="sp-wrapper"> class="sp-wrapper">
<mat-progress-spinner class="spinner" diameter="20" [color]="currentError ? 'warn': 'valid'" <mat-progress-spinner class="spinner" diameter="20" [color]="currentError ? 'warn': 'valid'"
mode="determinate" [value]="(password?.value?.length / policy.minLength) * 100"> mode="determinate" [value]="(password?.value?.length / policy.minLength) * 100">

View File

@ -1,18 +1,13 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
@Component({ @Component({
selector: 'app-password-complexity-view', selector: 'cnsl-password-complexity-view',
templateUrl: './password-complexity-view.component.html', templateUrl: './password-complexity-view.component.html',
styleUrls: ['./password-complexity-view.component.scss'], styleUrls: ['./password-complexity-view.component.scss'],
}) })
export class PasswordComplexityViewComponent implements OnInit { export class PasswordComplexityViewComponent {
@Input() public password!: FormControl; @Input() public password: AbstractControl | null = null;
@Input() public policy!: PasswordComplexityPolicy.AsObject; @Input() public policy!: PasswordComplexityPolicy.AsObject;
constructor() { }
ngOnInit(): void {
}
} }

View File

@ -4,7 +4,7 @@
<p class="desc"> {{'LOGINPOLICY.ADDIDP.DESCRIPTION' | translate}}</p> <p class="desc"> {{'LOGINPOLICY.ADDIDP.DESCRIPTION' | translate}}</p>
<div mat-dialog-content> <div mat-dialog-content>
<cnsl-form-field *ngIf="serviceType == PolicyComponentServiceType.MGMT" class="full-width" appearance="outline"> <cnsl-form-field *ngIf="serviceType === PolicyComponentServiceType.MGMT" class="full-width" appearance="outline">
<cnsl-label>{{ 'IDP.TYPE' | translate }}</cnsl-label> <cnsl-label>{{ 'IDP.TYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()"> <mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()">
<mat-option *ngFor="let type of idpTypes" [value]="type"> <mat-option *ngFor="let type of idpTypes" [value]="type">

View File

@ -8,7 +8,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../../../policy-component-types.enum'; import { PolicyComponentServiceType } from '../../../policy-component-types.enum';
@Component({ @Component({
selector: 'app-add-idp-dialog', selector: 'cnsl-add-idp-dialog',
templateUrl: './add-idp-dialog.component.html', templateUrl: './add-idp-dialog.component.html',
styleUrls: ['./add-idp-dialog.component.scss'], styleUrls: ['./add-idp-dialog.component.scss'],
}) })
@ -23,7 +23,7 @@ export class AddIdpDialogComponent {
]; ];
public idp: IDP.AsObject | undefined = undefined; public idp: IDP.AsObject | undefined = undefined;
public availableIdps: Array<IDP.AsObject[] | IDP.AsObject> | string[] = []; public availableIdps: IDP.AsObject[] = [];
constructor( constructor(
private mgmtService: ManagementService, private mgmtService: ManagementService,

View File

@ -1,21 +1,18 @@
<div class="idps"> <div class="idps">
<div class="idp mat-elevation-z1" <div class="idp mat-elevation-z1" *ngFor="let idp of idps">
*ngFor="let idp of idps"> <img src="../../../assets/images/google.png" *ngIf="idp.idpName.toLowerCase() === 'google'" alt="google" />
<img src="../../../assets/images/google.png" <div>
*ngIf="idp.idpName.toLowerCase() == 'google'" alt="google" /> <span class="name">{{idp.idpName}}</span>
<div> <span class="meta-info">{{ 'IDP.TYPES.'+idp.idpType | translate }}</span>
<span class="name">{{idp.idpName}}</span> <!-- <span class="meta-info">{{ 'IDP.ID' | translate }}: {{idp.idpId}}</span> -->
<span class="meta-info">{{ 'IDP.TYPES.'+idp.idpType | translate }}</span> </div>
<!-- <span class="meta-info">{{ 'IDP.ID' | translate }}: {{idp.idpId}}</span> --> <span class="fill-space"></span>
</div> <button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeIdp(idp)" class="rm">
<span class="fill-space"></span> <mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon>
<button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeIdp(idp)" class="rm"> </button>
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon>
</button>
</div> </div>
<button mat-raised-button color="primary" [disabled]="disabled" <button mat-raised-button color="primary" [disabled]="disabled" class="new-idp" (click)="openDialog()">
class="new-idp" (click)="openDialog()" > <span>{{'IDP.ADD' | translate}}</span>
<span>{{'IDP.ADD' | translate}}</span> <mat-icon class="icon">add</mat-icon>
<mat-icon class="icon">add</mat-icon> </button>
</button>
</div> </div>

View File

@ -1,177 +1,208 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.LOGIN_POLICY.TITLE' | translate" [title]="'POLICY.LOGIN_POLICY.TITLE' | translate"
[description]="(serviceType==PolicyComponentServiceType.MGMT ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEMGMT' : PolicyComponentServiceType.ADMIN ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEADMIN' : '') | translate"> [description]="(serviceType === PolicyComponentServiceType.MGMT ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEMGMT' : PolicyComponentServiceType.ADMIN ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEADMIN' : '') | translate">
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<div class="spinner-wr"> <div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner> <mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div> </div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT"> <ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="!isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" <button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn"
mat-stroked-button> (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
</button> </button>
</ng-template> </ng-template>
<ng-template appHasRole [appHasRole]="['policy.write']"> <ng-template cnslHasRole [hasRole]="['policy.write']">
<button *ngIf="isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()" <button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}"
mat-raised-button> (click)="savePolicy()" mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}} {{'POLICY.CREATECUSTOM' | translate}}
</button> </button>
</ng-template> </ng-template>
</ng-container> </ng-container>
<cnsl-card title="{{ 'IDP.LIST.ACTIVETITLE' | translate }}" [expanded]="true">
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<app-card title="{{ 'IDP.LIST.ACTIVETITLE' | translate }}" <cnsl-login-policy-idps [serviceType]="serviceType" [service]="service"
[expanded]="true"> [disabled]="disabled || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false)">
</cnsl-login-policy-idps>
</cnsl-card>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<cnsl-card title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}"
description="{{'MFA.LIST.MULTIFACTORDESCRIPTION' | translate}}" [expanded]="false">
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<cnsl-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.MultiFactor"
[disabled]="(([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType === PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false)">
</cnsl-mfa-table>
</cnsl-card>
<cnsl-card title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}"
description="{{'MFA.LIST.SECONDFACTORDESCRIPTION' | translate}}" [expanded]="false">
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<cnsl-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType === PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) === false || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false)">
</cnsl-mfa-table>
</cnsl-card>
<cnsl-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}" [expanded]="false">
<div class="content" *ngIf="loginData">
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) === false; else usernameInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span>
</cnsl-info-section>
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) === false)"
ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) === false; else regInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.registration'})"></span>
</cnsl-info-section>
<ng-template #regInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.allowExternalIdp">
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false; else idpInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section> </cnsl-info-section>
<cnsl-login-policy-idps [serviceType]="serviceType" [service]="service" [disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false)"></cnsl-login-policy-idps> <ng-template #idpInfo>
</app-card> <cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<app-card title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}" description="{{'MFA.LIST.MULTIFACTORDESCRIPTION' | translate}}" [expanded]="false"> <cnsl-info-section
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false; else factorsInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section> </cnsl-info-section>
<app-mfa-table [service]="service" [serviceType]="serviceType" <ng-template #factorsInfo>
[componentType]="LoginMethodComponentType.MultiFactor" <cnsl-info-section class="info">
[disabled]="(([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false) || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)"> {{'POLICY.DATA.FORCEMFA_DESC' | translate}}
</app-mfa-table>
</app-card>
<app-card title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}" description="{{'MFA.LIST.SECONDFACTORDESCRIPTION' | translate}}" [expanded]="false">
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<app-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) == false || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false)">
</app-mfa-table>
</app-card>
<app-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}" [expanded]="false">
<div class="content" *ngIf="loginData">
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false; else usernameInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span>
</cnsl-info-section>
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false)"
ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) == false; else regInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.registration'})"></span>
</cnsl-info-section>
<ng-template #regInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp">
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false; else idpInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<ng-template #idpInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false; else factorsInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<ng-template #factorsInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.FORCEMFA_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false" ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false; else passwordResetInfo" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.password_reset'})"></span>
</cnsl-info-section> </cnsl-info-section>
</ng-template>
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="row">
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType"
[disabled]="disabled || (serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false)">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.passwordless'})"></span>
</cnsl-info-section>
</div>
</div> </div>
</app-card> <div class="row">
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit" <mat-slide-toggle class="toggle" color="primary"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> [disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>
<div class="divider"></div> <cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) === false; else passwordResetInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.password_reset'})"></span>
</cnsl-info-section>
<ng-template appHasRole [appHasRole]="['org.idp.read']"> <ng-template #passwordResetInfo>
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}" <cnsl-info-section class="info">
[expanded]="false"> {{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<app-idp-table [service]="service" [serviceType]="serviceType" <div class="row">
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false || ((serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false))"> <cnsl-form-field class="form-field" label="Access Code" required="true">
</app-idp-table> <cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
</app-card> <mat-select [(ngModel)]="loginData.passwordlessType"
</ng-template> [disabled]="disabled || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) === false)">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid> <cnsl-info-section
</app-detail-layout> *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.passwordless'})"></span>
</cnsl-info-section>
</div>
</div>
</cnsl-card>
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<div class="divider"></div>
<ng-template cnslHasRole [hasRole]="['org.idp.read']">
<cnsl-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}"
[expanded]="false">
<cnsl-idp-table [service]="service" [serviceType]="serviceType"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType === PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) === false || ((serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false))">
</cnsl-idp-table>
</cnsl-card>
</ng-template>
<cnsl-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security">
</cnsl-policy-grid>
</cnsl-detail-layout>

View File

@ -16,12 +16,13 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies'; import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './mfa-table/mfa-table.component'; import { LoginMethodComponentType } from './mfa-table/mfa-table.component';
@Component({ @Component({
selector: 'app-login-policy', selector: 'cnsl-login-policy',
templateUrl: './login-policy.component.html', templateUrl: './login-policy.component.html',
styleUrls: ['./login-policy.component.scss'], styleUrls: ['./login-policy.component.scss'],
}) })
@ -39,6 +40,7 @@ export class LoginPolicyComponent implements OnDestroy {
public disabled: boolean = true; public disabled: boolean = true;
public currentPolicy: GridPolicy = LOGIN_POLICY; public currentPolicy: GridPolicy = LOGIN_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private toast: ToastService, private toast: ToastService,

View File

@ -6,7 +6,7 @@
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label> <cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
<mat-select [(ngModel)]="newMfaType"> <mat-select [(ngModel)]="newMfaType">
<mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa"> <mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa">
{{(data.componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': {{(data.componentType === LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}} LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</mat-option> </mat-option>
</mat-select> </mat-select>

View File

@ -3,29 +3,29 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb'; import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
enum LoginMethodComponentType { enum LoginMethodComponentType {
MultiFactor = 1, MultiFactor = 1,
SecondFactor = 2, SecondFactor = 2,
} }
@Component({ @Component({
selector: 'app-dialog-add-type', selector: 'cnsl-dialog-add-type',
templateUrl: './dialog-add-type.component.html', templateUrl: './dialog-add-type.component.html',
styleUrls: ['./dialog-add-type.component.scss'], styleUrls: ['./dialog-add-type.component.scss'],
}) })
export class DialogAddTypeComponent { export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType; public LoginMethodComponentType: any = LoginMethodComponentType;
public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = []; public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType; public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>, constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types; this.availableMfaTypes = data.types;
} }
public closeDialog(): void { public closeDialog(): void {
this.dialogRef.close(); this.dialogRef.close();
} }
public closeDialogWithCode(): void { public closeDialogWithCode(): void {
this.dialogRef.close(this.newMfaType); this.dialogRef.close(this.newMfaType);
} }
} }

View File

@ -1,19 +1,19 @@
<div class="sp_wrapper"> <div class="sp_wrapper">
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner> <mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
</div> </div>
<div class="mfa-list"> <div class="mfa-list">
<div class="mfa mat-elevation-z1" *ngFor="let mfa of mfas"> <div class="mfa mat-elevation-z1" *ngFor="let mfa of mfas">
<span> <span>
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': {{(componentType === LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}} LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</span> </span>
<button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm"> <button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon> <mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">remove_circle</mat-icon>
</button>
</div>
<button mat-raised-button color="primary" class="new-mfa" [disabled]="disabled" (click)="!disabled ? addMfa(): null">
<span>{{'ACTIONS.ADD' | translate}}</span>
<mat-icon class="icon">add</mat-icon>
</button> </button>
</div>
<button mat-raised-button color="primary" class="new-mfa" [disabled]="disabled" (click)="!disabled ? addMfa(): null">
<span>{{'ACTIONS.ADD' | translate}}</span>
<mat-icon class="icon">add</mat-icon>
</button>
</div> </div>

View File

@ -30,7 +30,7 @@ export enum LoginMethodComponentType {
} }
@Component({ @Component({
selector: 'app-mfa-table', selector: 'cnsl-mfa-table',
templateUrl: './mfa-table.component.html', templateUrl: './mfa-table.component.html',
styleUrls: ['./mfa-table.component.scss'], styleUrls: ['./mfa-table.component.scss'],
}) })

View File

@ -1,4 +1,4 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.LOGIN_TEXTS.TITLE' | translate" [title]="'POLICY.LOGIN_TEXTS.TITLE' | translate"
[description]="'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate"> [description]="'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate">
@ -35,23 +35,23 @@
</cnsl-form-field> </cnsl-form-field>
</form> </form>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.login'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.login'})"></span>
</cnsl-info-section> </cnsl-info-section>
<div class="divider"></div> <div class="divider"></div>
<div class="content" > <div class="content" >
<cnsl-edit-text label="one" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) == false" [default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues( <cnsl-edit-text label="one" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false" [default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues(
$event)"></cnsl-edit-text> $event)"></cnsl-edit-text>
</div> </div>
<div class="actions"> <div class="actions">
<button class="reset-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) == false" (click)="resetDefault()" color="warn" type="submit" <button class="reset-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false" (click)="resetDefault()" color="warn" type="submit"
mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button> mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) == false" (click)="saveCurrentMessage()" color="primary" type="submit" <button class="save-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false" (click)="saveCurrentMessage()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid> <cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -19,12 +19,13 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { GridPolicy, LOGIN_TEXTS_POLICY } from '../../policy-grid/policies'; import { GridPolicy, LOGIN_TEXTS_POLICY } from '../../policy-grid/policies';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { mapRequestValues } from './helper'; import { mapRequestValues } from './helper';
// tslint:disable /* eslint-disable */
const KeyNamesArray = [ const KeyNamesArray = [
'emailVerificationDoneText', 'emailVerificationDoneText',
'emailVerificationText', 'emailVerificationText',
@ -61,7 +62,7 @@ const KeyNamesArray = [
'passwordlessText', 'passwordlessText',
'externalRegistrationUserOverviewText' 'externalRegistrationUserOverviewText'
]; ];
// tslint:enable /* eslint-enable */
const REQUESTMAP = { const REQUESTMAP = {
[PolicyComponentServiceType.MGMT]: { [PolicyComponentServiceType.MGMT]: {
@ -88,7 +89,7 @@ const REQUESTMAP = {
}, },
}; };
@Component({ @Component({
selector: 'app-login-texts', selector: 'cnsl-login-texts',
templateUrl: './login-texts.component.html', templateUrl: './login-texts.component.html',
styleUrls: ['./login-texts.component.scss'], styleUrls: ['./login-texts.component.scss'],
}) })
@ -114,6 +115,7 @@ export class LoginTextsComponent implements OnDestroy {
public currentPolicy: GridPolicy = LOGIN_TEXTS_POLICY; public currentPolicy: GridPolicy = LOGIN_TEXTS_POLICY;
public destroy$: Subject<void> = new Subject(); public destroy$: Subject<void> = new Subject();
public InfoSectionType: any = InfoSectionType;
public form: FormGroup = new FormGroup({ public form: FormGroup = new FormGroup({
currentSubMap: new FormControl('emailVerificationDoneText'), currentSubMap: new FormControl('emailVerificationDoneText'),

View File

@ -1,10 +1,10 @@
<app-detail-layout [maxWidth]="false" [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [maxWidth]="false" [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.MESSAGE_TEXTS.TITLE' | translate" [title]="'POLICY.MESSAGE_TEXTS.TITLE' | translate"
[description]="'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate"> [description]="'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate">
<div class="top-actions"> <div class="top-actions">
<div class="message-type"> <div class="message-type">
<button (click)="setCurrentType(type.value)" [ngClass]="{'active': currentType == type.value}" mat-button *ngFor="let type of MESSAGETYPES | keyvalue">{{'POLICY.MESSAGE_TEXTS.TYPES.'+type.value | translate}}</button> <button (click)="setCurrentType($any(type).value)" [ngClass]="{'active': currentType === type.value}" mat-button *ngFor="let type of MESSAGETYPES | keyvalue">{{'POLICY.MESSAGE_TEXTS.TYPES.'+type.value | translate}}</button>
</div> </div>
<cnsl-form-field class="language"> <cnsl-form-field class="language">
@ -19,23 +19,23 @@
</cnsl-form-field> </cnsl-form-field>
</div> </div>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.message'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.message'})"></span>
</cnsl-info-section> </cnsl-info-section>
<div class="content" > <div class="content" >
<cnsl-edit-text [chips]="chips[currentType]" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) == false" label="one" [default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues( <cnsl-edit-text [chips]="chips[currentType]" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false" label="one" [default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues(
$event)"></cnsl-edit-text> $event)"></cnsl-edit-text>
</div> </div>
<div class="actions"> <div class="actions">
<button class="reset-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) == false" (click)="resetDefault()" color="warn" type="submit" <button class="reset-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false" (click)="resetDefault()" color="warn" type="submit"
mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button> mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button" [disabled]="!updateRequest || serviceType == PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) == false" (click)="saveCurrentMessage()" color="primary" type="submit" <button class="save-button" [disabled]="!updateRequest || serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false" (click)="saveCurrentMessage()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid> <cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -40,6 +40,7 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { GridPolicy, MESSAGE_TEXTS_POLICY } from '../../policy-grid/policies'; import { GridPolicy, MESSAGE_TEXTS_POLICY } from '../../policy-grid/policies';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@ -267,7 +268,7 @@ const REQUESTMAP = {
}, },
}; };
@Component({ @Component({
selector: 'app-message-texts', selector: 'cnsl-message-texts',
templateUrl: './message-texts.component.html', templateUrl: './message-texts.component.html',
styleUrls: ['./message-texts.component.scss'], styleUrls: ['./message-texts.component.scss'],
}) })
@ -285,6 +286,7 @@ export class MessageTextsComponent implements OnDestroy {
public updateRequest!: SetCustomInitMessageTextRequest | SetDefaultInitMessageTextRequest; public updateRequest!: SetCustomInitMessageTextRequest | SetDefaultInitMessageTextRequest;
public InfoSectionType: any = InfoSectionType;
public chips: { public chips: {
[messagetype: string]: Array<{ key: string; value: string; }>; [messagetype: string]: Array<{ key: string; value: string; }>;
} = { } = {
@ -446,6 +448,8 @@ export class MessageTextsComponent implements OnDestroy {
return this.stripDetails((this.service as ManagementService).getCustomDomainClaimedMessageText(req)); return this.stripDetails((this.service as ManagementService).getCustomDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS: case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails((this.service as ManagementService).getCustomPasswordlessRegistrationMessageText(req)); return this.stripDetails((this.service as ManagementService).getCustomPasswordlessRegistrationMessageText(req));
default:
return undefined;
} }
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) { } else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
switch (type) { switch (type) {
@ -461,7 +465,11 @@ export class MessageTextsComponent implements OnDestroy {
return this.stripDetails((this.service as AdminService).getCustomDomainClaimedMessageText(req)); return this.stripDetails((this.service as AdminService).getCustomDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS: case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails((this.service as AdminService).getCustomPasswordlessRegistrationMessageText(req)); return this.stripDetails((this.service as AdminService).getCustomPasswordlessRegistrationMessageText(req));
default:
return undefined;
} }
} else {
return undefined;
} }
} }
@ -548,36 +556,36 @@ export class MessageTextsComponent implements OnDestroy {
}); });
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe(resp => {
if (resp) { if (resp && this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.serviceType === PolicyComponentServiceType.MGMT) { const handler = (prom: Promise<any>): Promise<any> => {
const handler = (prom: Promise<any>): Promise<any> => { return prom.then(() => {
return prom.then(() => { setTimeout(() => {
setTimeout(() => { this.loadData(this.currentType);
this.loadData(this.currentType); }, 1000);
}, 1000); }).catch(error => {
}).catch(error => { this.toast.showError(error);
this.toast.showError(error); });
}); };
};
switch (this.currentType) {
case MESSAGETYPES.INIT:
return handler((this.service as ManagementService).resetCustomInitMessageTextToDefault(this.locale));
case MESSAGETYPES.VERIFYPHONE:
return handler((this.service as ManagementService).resetCustomVerifyPhoneMessageTextToDefault(this.locale));
case MESSAGETYPES.VERIFYEMAIL:
return handler((this.service as ManagementService).resetCustomVerifyEmailMessageTextToDefault(this.locale));
case MESSAGETYPES.PASSWORDRESET:
return handler((this.service as ManagementService).resetCustomPasswordResetMessageTextToDefault(this.locale));
case MESSAGETYPES.DOMAINCLAIMED:
return handler((this.service as ManagementService).resetCustomDomainClaimedMessageTextToDefault(this.locale));
case MESSAGETYPES.DOMAINCLAIMED:
return handler((this.service as ManagementService)
.resetCustomPasswordlessRegistrationMessageTextToDefault(this.locale));
}
switch (this.currentType) {
case MESSAGETYPES.INIT:
return handler((this.service as ManagementService).resetCustomInitMessageTextToDefault(this.locale));
case MESSAGETYPES.VERIFYPHONE:
return handler((this.service as ManagementService).resetCustomVerifyPhoneMessageTextToDefault(this.locale));
case MESSAGETYPES.VERIFYEMAIL:
return handler((this.service as ManagementService).resetCustomVerifyEmailMessageTextToDefault(this.locale));
case MESSAGETYPES.PASSWORDRESET:
return handler((this.service as ManagementService).resetCustomPasswordResetMessageTextToDefault(this.locale));
case MESSAGETYPES.DOMAINCLAIMED:
return handler((this.service as ManagementService).resetCustomDomainClaimedMessageTextToDefault(this.locale));
case MESSAGETYPES.DOMAINCLAIMED:
return handler((this.service as ManagementService)
.resetCustomPasswordlessRegistrationMessageTextToDefault(this.locale));
default:
return Promise.reject();
} }
} else {
return Promise.reject();
} }
}); });
} }

View File

@ -1,8 +1,8 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.IAM_POLICY.TITLE' | translate" [description]="'POLICY.IAM_POLICY.DESCRIPTION' | translate"> [title]="'POLICY.IAM_POLICY.TITLE' | translate" [description]="'POLICY.IAM_POLICY.DESCRIPTION' | translate">
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<ng-template appHasRole [appHasRole]="['iam.policy.delete']"> <ng-template cnslHasRole [hasRole]="['iam.policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" <button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
@ -24,5 +24,5 @@
}}</button> }}</button>
</div> </div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="login"></app-policy-grid> <cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="login"></cnsl-policy-grid>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -15,7 +15,7 @@ import { GridPolicy, IAM_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({ @Component({
selector: 'app-org-iam-policy', selector: 'cnsl-org-iam-policy',
templateUrl: './org-iam-policy.component.html', templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'], styleUrls: ['./org-iam-policy.component.scss'],
}) })
@ -65,7 +65,7 @@ export class OrgIamPolicyComponent implements OnDestroy {
}); });
} }
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | undefined> { private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | any> {
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy(); return this.managementService.getOrgIAMPolicy();
@ -74,6 +74,8 @@ export class OrgIamPolicyComponent implements OnDestroy {
return this.adminService.getCustomOrgIAMPolicy(this.org.id); return this.adminService.getCustomOrgIAMPolicy(this.org.id);
} }
break; break;
default:
return Promise.reject();
} }
} }

View File

@ -1,6 +1,6 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.PWD_AGE.TITLE' | translate" [description]="'POLICY.PWD_AGE.DESCRIPTION' | translate"> [title]="'POLICY.PWD_AGE.TITLE' | translate" [description]="'POLICY.PWD_AGE.DESCRIPTION' | translate">
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" <button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
@ -41,4 +41,4 @@
<button (click)="savePolicy()" color="primary" type="submit" <button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -4,7 +4,7 @@ import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { GetPasswordAgePolicyResponse as AdminGetPasswordAgePolicyResponse } from 'src/app/proto/generated/zitadel/admin_pb'; import { GetPasswordAgePolicyResponse as AdminGetPasswordAgePolicyResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import { import {
GetPasswordAgePolicyResponse as MgmtGetPasswordAgePolicyResponse, GetPasswordAgePolicyResponse as MgmtGetPasswordAgePolicyResponse,
} from 'src/app/proto/generated/zitadel/management_pb'; } from 'src/app/proto/generated/zitadel/management_pb';
import { PasswordAgePolicy } from 'src/app/proto/generated/zitadel/policy_pb'; import { PasswordAgePolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
@ -15,138 +15,138 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({ @Component({
selector: 'app-password-age-policy', selector: 'cnsl-password-age-policy',
templateUrl: './password-age-policy.component.html', templateUrl: './password-age-policy.component.html',
styleUrls: ['./password-age-policy.component.scss'], styleUrls: ['./password-age-policy.component.scss'],
}) })
export class PasswordAgePolicyComponent implements OnDestroy { export class PasswordAgePolicyComponent implements OnDestroy {
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT; public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: AdminService | ManagementService; public service!: AdminService | ManagementService;
public ageData!: PasswordAgePolicy.AsObject | PasswordAgePolicy.AsObject; public ageData!: PasswordAgePolicy.AsObject | PasswordAgePolicy.AsObject;
private sub: Subscription = new Subscription(); private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType; public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private toast: ToastService, private toast: ToastService,
private injector: Injector, private injector: Injector,
) { ) {
this.sub = this.route.data.pipe(switchMap(data => { this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType; this.serviceType = data.serviceType;
switch (this.serviceType) { switch (this.serviceType) {
case PolicyComponentServiceType.MGMT: case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>); this.service = this.injector.get(ManagementService as Type<ManagementService>);
break; break;
case PolicyComponentServiceType.ADMIN: case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>); this.service = this.injector.get(AdminService as Type<AdminService>);
break; break;
} }
return this.route.params; return this.route.params;
})).subscribe(() => { })).subscribe(() => {
this.getData().then(resp => { this.getData().then(resp => {
if (resp.policy) { if (resp.policy) {
this.ageData = resp.policy; this.ageData = resp.policy;
}
});
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData():
Promise<MgmtGetPasswordAgePolicyResponse.AsObject | AdminGetPasswordAgePolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordAgePolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordAgePolicy();
} }
} });
});
}
public removePolicy(): void { public ngOnDestroy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) { this.sub.unsubscribe();
(this.service as ManagementService).resetPasswordAgePolicyToDefault().then(() => { }
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.getData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public incrementExpireWarnDays(): void { private async getData():
if (this.ageData?.expireWarnDays !== undefined) { Promise<MgmtGetPasswordAgePolicyResponse.AsObject | AdminGetPasswordAgePolicyResponse.AsObject> {
this.ageData.expireWarnDays++;
}
}
public decrementExpireWarnDays(): void { switch (this.serviceType) {
if (this.ageData?.expireWarnDays && this.ageData?.expireWarnDays > 0) { case PolicyComponentServiceType.MGMT:
this.ageData.expireWarnDays--; return (this.service as ManagementService).getPasswordAgePolicy();
} case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordAgePolicy();
} }
}
public incrementMaxAgeDays(): void { public removePolicy(): void {
if (this.ageData?.maxAgeDays !== undefined) { if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.ageData.maxAgeDays++; (this.service as ManagementService).resetPasswordAgePolicyToDefault().then(() => {
} this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.getData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
} }
}
public decrementMaxAgeDays(): void { public incrementExpireWarnDays(): void {
if (this.ageData?.maxAgeDays && this.ageData?.maxAgeDays > 0) { if (this.ageData?.expireWarnDays !== undefined) {
this.ageData.maxAgeDays--; this.ageData.expireWarnDays++;
}
} }
}
public savePolicy(): void { public decrementExpireWarnDays(): void {
switch (this.serviceType) { if (this.ageData?.expireWarnDays && this.ageData?.expireWarnDays > 0) {
case PolicyComponentServiceType.MGMT: this.ageData.expireWarnDays--;
if (this.ageData.isDefault) {
(this.service as ManagementService).addCustomPasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
(this.service as ManagementService).updateCustomPasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
} }
}
public get isDefault(): boolean { public incrementMaxAgeDays(): void {
if (this.ageData && this.serviceType === PolicyComponentServiceType.MGMT) { if (this.ageData?.maxAgeDays !== undefined) {
return (this.ageData as PasswordAgePolicy.AsObject).isDefault; this.ageData.maxAgeDays++;
}
}
public decrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays && this.ageData?.maxAgeDays > 0) {
this.ageData.maxAgeDays--;
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if (this.ageData.isDefault) {
(this.service as ManagementService).addCustomPasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else { } else {
return false; (this.service as ManagementService).updateCustomPasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} }
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordAgePolicy(
this.ageData.maxAgeDays,
this.ageData.expireWarnDays,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
} }
}
public get isDefault(): boolean {
if (this.ageData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.ageData as PasswordAgePolicy.AsObject).isDefault;
} else {
return false;
}
}
} }

View File

@ -1,9 +1,9 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.PWD_COMPLEXITY.TITLE' | translate" [description]="'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate"> [title]="'POLICY.PWD_COMPLEXITY.TITLE' | translate" [description]="'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate">
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'password_complexity_policy'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'password_complexity_policy'})"></span>
</cnsl-info-section> </cnsl-info-section>
@ -11,8 +11,8 @@
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner> <mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div> </div>
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false" <button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
</button> </button>
@ -24,11 +24,11 @@
<span class="left-desc">{{'POLICY.DATA.MINLENGTH' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.MINLENGTH' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="length-wrapper"> <div class="length-wrapper">
<button mat-icon-button (click)="decrementLength()" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> <button mat-icon-button (click)="decrementLength()" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
<mat-icon>remove</mat-icon> <mat-icon>remove</mat-icon>
</button> </button>
<span>{{complexityData?.minLength}}</span> <span>{{complexityData?.minLength}}</span>
<button mat-icon-button (click)="incrementLength()" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> <button mat-icon-button (click)="incrementLength()" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</div> </div>
@ -37,14 +37,14 @@
<mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon> <mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASNUMBER' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.HASNUMBER' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> <mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
<mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon> <mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASSYMBOL' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.HASSYMBOL' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> <mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
@ -52,7 +52,7 @@
<span class="left-desc">{{'POLICY.DATA.HASLOWERCASE' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl <mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl
[(ngModel)]="complexityData.hasLowercase" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> [(ngModel)]="complexityData.hasLowercase" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="row"> <div class="row">
@ -60,15 +60,15 @@
<span class="left-desc">{{'POLICY.DATA.HASUPPERCASE' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl <mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl
[(ngModel)]="complexityData.hasUppercase" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"> [(ngModel)]="complexityData.hasUppercase" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false">
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
</div> </div>
<div class="btn-container"> <div class="btn-container">
<button (click)="savePolicy()" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate <button (click)="savePolicy()" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button> }}</button>
</div> </div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid> <cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></cnsl-policy-grid>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -13,11 +13,12 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { COMPLEXITY_POLICY, GridPolicy } from '../../policy-grid/policies'; import { COMPLEXITY_POLICY, GridPolicy } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({ @Component({
selector: 'app-password-policy', selector: 'cnsl-password-policy',
templateUrl: './password-complexity-policy.component.html', templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'], styleUrls: ['./password-complexity-policy.component.scss'],
}) })
@ -32,6 +33,7 @@ export class PasswordComplexityPolicyComponent implements OnDestroy {
public loading: boolean = false; public loading: boolean = false;
public currentPolicy: GridPolicy = COMPLEXITY_POLICY; public currentPolicy: GridPolicy = COMPLEXITY_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,

View File

@ -1,14 +1,14 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.PWD_LOCKOUT.TITLE' | translate" [description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate"> [title]="'POLICY.PWD_LOCKOUT.TITLE' | translate" [description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate">
<cnsl-info-section class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'lockout_policy'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'lockout_policy'})"></span>
</cnsl-info-section> </cnsl-info-section>
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template cnslHasRole [hasRole]="['policy.delete']">
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" <button [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetPolicy()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetPolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
</button> </button>
@ -19,11 +19,11 @@
<span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span> <span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="length-wrapper"> <div class="length-wrapper">
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" mat-icon-button (click)="decrementMaxAttempts()"> <button [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false" mat-icon-button (click)="decrementMaxAttempts()">
<mat-icon>remove</mat-icon> <mat-icon>remove</mat-icon>
</button> </button>
<span>{{lockoutData?.maxPasswordAttempts}}</span> <span>{{lockoutData?.maxPasswordAttempts}}</span>
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" mat-icon-button (click)="incrementMaxAttempts()"> <button [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false" mat-icon-button (click)="incrementMaxAttempts()">
<mat-icon>add</mat-icon> <mat-icon>add</mat-icon>
</button> </button>
</div> </div>
@ -31,7 +31,7 @@
</div> </div>
<div class="btn-container"> <div class="btn-container">
<button (click)="savePolicy()" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate <button (click)="savePolicy()" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button> }}</button>
</div> </div>
</app-detail-layout> </cnsl-detail-layout>

View File

@ -12,10 +12,11 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({ @Component({
selector: 'app-password-lockout-policy', selector: 'cnsl-password-lockout-policy',
templateUrl: './password-lockout-policy.component.html', templateUrl: './password-lockout-policy.component.html',
styleUrls: ['./password-lockout-policy.component.scss'], styleUrls: ['./password-lockout-policy.component.scss'],
}) })
@ -27,6 +28,7 @@ export class PasswordLockoutPolicyComponent implements OnDestroy {
public lockoutData!: LockoutPolicy.AsObject; public lockoutData!: LockoutPolicy.AsObject;
private sub: Subscription = new Subscription(); private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType; public PolicyComponentServiceType: any = PolicyComponentServiceType;
public InfoSectionType: any = InfoSectionType;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,

View File

@ -1,10 +1,10 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" <cnsl-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.PRIVACY_POLICY.TITLE' | translate" [title]="'POLICY.PRIVACY_POLICY.TITLE' | translate"
[description]="'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate"> [description]="'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate">
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'privacy_policy'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'privacy_policy'})"></span>
</cnsl-info-section> </cnsl-info-section>
@ -25,12 +25,11 @@
</div> </div>
<div class="actions"> <div class="actions">
<button *ngIf="!privacyPolicy?.isDefault" class="reset-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) == false" (click)="resetDefault()" color="warn" type="submit" <button *ngIf="!privacyPolicy?.isDefault" class="reset-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) === false" (click)="resetDefault()" color="warn" type="submit"
mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button> mat-stroked-button><i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) == false" (click)="saveCurrentMessage()" color="primary" type="submit" <button class="save-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) === false" (click)="saveCurrentMessage()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button> mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div> </div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid> <cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</cnsl-detail-layout>
</app-detail-layout>

View File

@ -18,13 +18,14 @@ import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { CnslLinks } from '../../links/links.component'; import { CnslLinks } from '../../links/links.component';
import { GridPolicy, PRIVACY_POLICY } from '../../policy-grid/policies'; import { GridPolicy, PRIVACY_POLICY } from '../../policy-grid/policies';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({ @Component({
selector: 'app-privacy-policy', selector: 'cnsl-privacy-policy',
templateUrl: './privacy-policy.component.html', templateUrl: './privacy-policy.component.html',
styleUrls: ['./privacy-policy.component.scss'], styleUrls: ['./privacy-policy.component.scss'],
}) })
@ -39,6 +40,7 @@ export class PrivacyPolicyComponent implements OnDestroy {
public privacyPolicy!: PrivacyPolicy.AsObject; public privacyPolicy!: PrivacyPolicy.AsObject;
public form!: FormGroup; public form!: FormGroup;
public currentPolicy: GridPolicy = PRIVACY_POLICY; public currentPolicy: GridPolicy = PRIVACY_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,

View File

@ -1,33 +1,33 @@
<div class="preview" *ngIf="policy"> <div class="preview" *ngIf="policy">
<span class="label">{{label}}</span> <span class="label">{{label}}</span>
<div class="dashed" [ngClass]="{'dark': theme === Theme.DARK, 'light': theme === Theme.LIGHT}" [style.background]="theme == Theme.DARK ? policy.backgroundColorDark : policy.backgroundColor"> <div class="dashed" [ngClass]="{'dark': theme === Theme.DARK, 'light': theme === Theme.LIGHT}" [style.background]="theme === Theme.DARK ? policy.backgroundColorDark : policy.backgroundColor">
<div class="login-wrapper" [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor"> <div class="login-wrapper" [style.color]="theme === Theme.DARK ? policy.fontColorDark : policy.fontColor">
<ng-container *ngIf="preview === Preview.PREVIEW; else currentimgs"> <ng-container *ngIf="preview === Preview.PREVIEW; else currentimgs">
<img *ngIf="images['previewLogo'] && theme == Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" /> <img *ngIf="images['previewLogo'] && theme === Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" />
<img *ngIf="images['previewDarkLogo'] && theme == Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" /> <img *ngIf="images['previewDarkLogo'] && theme === Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" />
</ng-container> </ng-container>
<ng-template #currentimgs> <ng-template #currentimgs>
<img *ngIf="images['logo'] && theme == Theme.LIGHT" [src]="images['logo']" alt="logo-mock" /> <img *ngIf="images['logo'] && theme === Theme.LIGHT" [src]="images['logo']" alt="logo-mock" />
<img *ngIf="images['darkLogo'] && theme == Theme.DARK" [src]="images['darkLogo']" alt="logo-mock" /> <img *ngIf="images['darkLogo'] && theme === Theme.DARK" [src]="images['darkLogo']" alt="logo-mock" />
</ng-template> </ng-template>
<h1 [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor">{{'POLICY.PRIVATELABELING.PREVIEW.TITLE' | translate}}</h1> <h1 [style.color]="theme === Theme.DARK ? policy.fontColorDark : policy.fontColor">{{'POLICY.PRIVATELABELING.PREVIEW.TITLE' | translate}}</h1>
<p [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor" class="desc-text">{{'POLICY.PRIVATELABELING.PREVIEW.SECOND' | translate}}</p> <p [style.color]="theme === Theme.DARK ? policy.fontColorDark : policy.fontColor" class="desc-text">{{'POLICY.PRIVATELABELING.PREVIEW.SECOND' | translate}}</p>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>Loginname</cnsl-label> <cnsl-label>Loginname</cnsl-label>
<input cnslInput [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor" value="road.runner"/> <input cnslInput [style.color]="theme === Theme.DARK ? policy.fontColorDark : policy.fontColor" value="road.runner"/>
</cnsl-form-field> </cnsl-form-field>
<div class="error-msg" [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor"> <div class="error-msg" [style.color]="theme === Theme.DARK ? policy.warnColorDark : policy.warnColor">
<i class="las la-exclamation-circle" [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor"></i> <i class="las la-exclamation-circle" [style.color]="theme === Theme.DARK ? policy.warnColorDark : policy.warnColor"></i>
<span [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor">{{'POLICY.PRIVATELABELING.PREVIEW.ERROR' | translate}}</span> <span [style.color]="theme === Theme.DARK ? policy.warnColorDark : policy.warnColor">{{'POLICY.PRIVATELABELING.PREVIEW.ERROR' | translate}}</span>
</div> </div>
<div class="btn-wrapper"> <div class="btn-wrapper">
<button mat-stroked-button [style.color]="theme == Theme.DARK ? policy.primaryColorDark : policy.primaryColor">{{'POLICY.PRIVATELABELING.PREVIEW.SECONDARYBUTTON' | translate}}</button> <button mat-stroked-button [style.color]="theme === Theme.DARK ? policy.primaryColorDark : policy.primaryColor">{{'POLICY.PRIVATELABELING.PREVIEW.SECONDARYBUTTON' | translate}}</button>
<button *ngIf="theme == Theme.DARK" mat-raised-button [style.background]="policy.primaryColorDark" [style.color]="policy.primaryColorDark == '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button> <button *ngIf="theme === Theme.DARK" mat-raised-button [style.background]="policy.primaryColorDark" [style.color]="policy.primaryColorDark === '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button>
<button *ngIf="theme == Theme.LIGHT" mat-raised-button [style.background]="policy.primaryColor" [style.color]="policy.primaryColor == '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button> <button *ngIf="theme === Theme.LIGHT" mat-raised-button [style.background]="policy.primaryColor" [style.color]="policy.primaryColor === '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@
<p class="desc">{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}</p> <p class="desc">{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}</p>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section> <cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section *ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false" [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span>
</cnsl-info-section> </cnsl-info-section>
@ -34,14 +34,14 @@
<span class="fill-space"></span> <span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['policy.delete']"> <ng-template cnslHasRole [hasRole]="['policy.delete']">
<button class="reset-button" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" <button class="reset-button" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button> matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}} {{'POLICY.RESET' | translate}}
</button> </button>
</ng-template> </ng-template>
<button class="activate-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button color="primary" (click)="activatePolicy()"> <button class="activate-button" [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false" mat-raised-button color="primary" (click)="activatePolicy()">
{{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}} {{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}}
</button> </button>
</div> </div>
@ -99,8 +99,8 @@
<label class="file-label"> <label class="file-label">
<i class="icon las la-image"></i> <i class="icon las la-image"></i>
<span>{{isHoveringOverDarkLogo ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span> <span>{{isHoveringOverDarkLogo ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)"> <input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $any($event.target).files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="$event.preventDefault(); selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a> <a class="btn" *ngIf="serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="$event.preventDefault(); selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label> </label>
</div> </div>
</div> </div>
@ -138,8 +138,8 @@
<label class="file-label"> <label class="file-label">
<i class="icon las la-image"></i> <i class="icon las la-image"></i>
<span>{{isHoveringOverDarkIcon ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span> <span>{{isHoveringOverDarkIcon ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)"> <input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $any($event.target).files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="$event.preventDefault(); selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a> <a class="btn" *ngIf="serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="$event.preventDefault(); selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label> </label>
</div> </div>
</div> </div>
@ -156,9 +156,7 @@
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<!-- <span class="title">{{ theme === Theme.DARK ? ('POLICY.PRIVATELABELING.DARK' | translate) : ('POLICY.PRIVATELABELING.LIGHT' | translate)}}</span> --> <ng-container *ngIf="theme === Theme.DARK">
<ng-container *ngIf="theme==Theme.DARK">
<div class="colors" *ngIf="data && previewData"> <div class="colors" *ngIf="data && previewData">
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color> <cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
@ -178,7 +176,7 @@
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="theme==Theme.LIGHT"> <ng-container *ngIf="theme === Theme.LIGHT">
<div class="colors" *ngIf="data && previewData"> <div class="colors" *ngIf="data && previewData">
<div class="color"> <div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color> <cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
@ -213,12 +211,12 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="fonts"> <div class="fonts">
<cnsl-info-section class="info-section">{{'POLICY.PRIVATELABELING.FONTINLOGINONLY' | translate}}</cnsl-info-section> <cnsl-info-section class="info-section">{{'POLICY.PRIVATELABELING.FONTINLOGINONLY' | translate}}</cnsl-info-section>
<div class="font-preview mat-elevation-z3" *ngIf="preview == Preview.PREVIEW && previewData.fontUrl || preview == Preview.CURRENT && data.fontUrl"> <div class="font-preview mat-elevation-z3" *ngIf="preview === Preview.PREVIEW && previewData.fontUrl || preview === Preview.CURRENT && data.fontUrl">
<mat-icon class="icon">text_fields</mat-icon> <mat-icon class="icon">text_fields</mat-icon>
<span>ABC • abc • 123</span> <span>ABC • abc • 123</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button> <button [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
</div> </div>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)" <div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
@ -227,8 +225,8 @@
<label class="file-label"> <label class="file-label">
<i class="icon las la-file"></i> <i class="icon las la-file"></i>
<span >{{isHoveringOverFont ? ('POLICY.PRIVATELABELING.RELEASEFONT' | translate): ('POLICY.PRIVATELABELING.DROPFONT' | translate)}}</span> <span >{{isHoveringOverFont ? ('POLICY.PRIVATELABELING.RELEASEFONT' | translate): ('POLICY.PRIVATELABELING.DROPFONT' | translate)}}</span>
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)"> <input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($any($event.target).files)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFontFile.click()">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a> <a class="btn" *ngIf="serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFontFile.click()">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
</label> </label>
</div> </div>
</div> </div>
@ -246,25 +244,25 @@
<div class="adv-container" *ngIf="previewData"> <div class="adv-container" *ngIf="previewData">
<ng-container <ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false"> *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type="WARN" <cnsl-info-section [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN"
> >
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span>
</cnsl-info-section> </cnsl-info-section>
</ng-container> </ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" <mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false"
[(ngModel)]="previewData.hideLoginNameSuffix" (change)="savePolicy()"> [(ngModel)]="previewData.hideLoginNameSuffix" (change)="savePolicy()">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}} {{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle> </mat-slide-toggle>
<ng-container <ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false"> *ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type="WARN"> <cnsl-info-section [featureLink]="['/org/features']" class="info" type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.watermark'})"></span> <span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.watermark'})"></span>
</cnsl-info-section> </cnsl-info-section>
</ng-container> </ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false" <mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) === false"
[(ngModel)]="previewData.disableWatermark" (change)="savePolicy()"> [(ngModel)]="previewData.disableWatermark" (change)="savePolicy()">
{{'POLICY.DATA.DISABLEWATERMARK' | translate}} {{'POLICY.DATA.DISABLEWATERMARK' | translate}}
</mat-slide-toggle> </mat-slide-toggle>
@ -275,8 +273,8 @@
<div class="preview-wrapper"> <div class="preview-wrapper">
<!-- <cnsl-preview class="prev" label="CURRENT CONFIG" [policy]="data"></cnsl-preview> --> <!-- <cnsl-preview class="prev" label="CURRENT CONFIG" [policy]="data"></cnsl-preview> -->
<div class="col"> <div class="col">
<button color="primary" mat-raised-button class="preview-changer" (click)="preview = preview == Preview.PREVIEW ? Preview.CURRENT : Preview.PREVIEW" matTooltip="{{'POLICY.PRIVATELABELING.CHANGEVIEW' | translate}}" [ngClass]="{'active': preview === Preview.PREVIEW}" > <button color="primary" mat-raised-button class="preview-changer" (click)="preview = preview === Preview.PREVIEW ? Preview.CURRENT : Preview.PREVIEW" matTooltip="{{'POLICY.PRIVATELABELING.CHANGEVIEW' | translate}}" [ngClass]="{'active': preview === Preview.PREVIEW}" >
<span><span [ngClass]="{'strong': preview == Preview.PREVIEW}">P</span> / <span [ngClass]="{'strong': preview == Preview.CURRENT}">C</span></span> <span><span [ngClass]="{'strong': preview === Preview.PREVIEW}">P</span> / <span [ngClass]="{'strong': preview === Preview.CURRENT}">C</span></span>
</button> </button>
<cnsl-preview *ngIf="preview === Preview.CURRENT" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="CURRENT" [policy]="data"></cnsl-preview> <cnsl-preview *ngIf="preview === Preview.CURRENT" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="CURRENT" [policy]="data"></cnsl-preview>
@ -285,5 +283,5 @@
</div> </div>
</div> </div>
<app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid> <cnsl-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</div> </div>

View File

@ -25,6 +25,7 @@ import { StorageKey, StorageLocation, StorageService } from 'src/app/services/st
import { ThemeService } from 'src/app/services/theme.service'; import { ThemeService } from 'src/app/services/theme.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { GridPolicy, PRIVATELABEL_POLICY } from '../../policy-grid/policies'; import { GridPolicy, PRIVATELABEL_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
@ -51,7 +52,7 @@ export enum ColorType {
const MAX_ALLOWED_SIZE = 0.5 * 1024 * 1024; const MAX_ALLOWED_SIZE = 0.5 * 1024 * 1024;
@Component({ @Component({
selector: 'app-private-labeling-policy', selector: 'cnsl-private-labeling-policy',
templateUrl: './private-labeling-policy.component.html', templateUrl: './private-labeling-policy.component.html',
styleUrls: ['./private-labeling-policy.component.scss'], styleUrls: ['./private-labeling-policy.component.scss'],
}) })
@ -88,6 +89,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
public loadingImages: boolean = false; public loadingImages: boolean = false;
private org!: Org.AsObject; private org!: Org.AsObject;
public currentPolicy: GridPolicy = PRIVATELABEL_POLICY; public currentPolicy: GridPolicy = PRIVATELABEL_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor( constructor(
private authService: GrpcAuthService, private authService: GrpcAuthService,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -164,16 +166,18 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
} }
} }
public onDropFont(filelist: FileList): Promise<any> | void { public onDropFont(filelist: FileList | null): Promise<any> | void {
const file = filelist.item(0); if (filelist) {
if (file) { const file = filelist.item(0);
const formData = new FormData(); if (file) {
formData.append('file', file); const formData = new FormData();
switch (this.serviceType) { formData.append('file', file);
case PolicyComponentServiceType.MGMT: switch (this.serviceType) {
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData, this.org.id)); case PolicyComponentServiceType.MGMT:
case PolicyComponentServiceType.ADMIN: return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData, this.org.id));
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData, this.org.id)); case PolicyComponentServiceType.ADMIN:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData, this.org.id));
}
} }
} }
} }

View File

@ -8,8 +8,8 @@
<div class="row-lyt"> <div class="row-lyt">
<ng-container *ngFor="let policy of filteredPolicies"> <ng-container *ngFor="let policy of filteredPolicies">
<ng-template appHasRole <ng-template cnslHasRole
[appHasRole]="type == PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type == PolicyComponentServiceType.MGMT ? policy.orgWithRole : []"> [hasRole]="type === PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type === PolicyComponentServiceType.MGMT ? policy.orgWithRole : []">
<div class="p-item card"> <div class="p-item card">
<div class="avatar {{policy.color}}"> <div class="avatar {{policy.color}}">
<mat-icon *ngIf="policy.svgIcon" class="icon" [svgIcon]="policy.svgIcon"></mat-icon> <mat-icon *ngIf="policy.svgIcon" class="icon" [svgIcon]="policy.svgIcon"></mat-icon>
@ -28,7 +28,7 @@
</div> </div>
<div class="btn-wrapper"> <div class="btn-wrapper">
<button <button
[routerLink]="type == PolicyComponentServiceType.ADMIN ? policy.iamRouterLink : type == PolicyComponentServiceType.MGMT ? policy.orgRouterLink : null" [routerLink]="type === PolicyComponentServiceType.ADMIN ? policy.iamRouterLink : type === PolicyComponentServiceType.MGMT ? policy.orgRouterLink : null"
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button> mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import { PolicyComponentServiceType, PolicyComponentType } from 'src/app/modules
import { GridPolicy, POLICIES } from './policies'; import { GridPolicy, POLICIES } from './policies';
@Component({ @Component({
selector: 'app-policy-grid', selector: 'cnsl-policy-grid',
templateUrl: './policy-grid.component.html', templateUrl: './policy-grid.component.html',
styleUrls: ['./policy-grid.component.scss'], styleUrls: ['./policy-grid.component.scss'],
}) })

View File

@ -24,13 +24,11 @@ export class ProjectMembersDataSource extends DataSource<Member.AsObject> {
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
constructor(private mgmtService: ManagementService) { constructor(private service: ManagementService) {
super(); super();
} }
public loadMembers(projectId: string, public loadMembers(projectId: string, projectType: ProjectType, pageIndex: number, pageSize: number, grantId?: string): void {
projectType: ProjectType,
pageIndex: number, pageSize: number, grantId?: string): void {
const offset = pageIndex * pageSize; const offset = pageIndex * pageSize;
this.loadingSubject.next(true); this.loadingSubject.next(true);
@ -40,9 +38,9 @@ export class ProjectMembersDataSource extends DataSource<Member.AsObject> {
Promise<ListProjectGrantMembersResponse.AsObject> Promise<ListProjectGrantMembersResponse.AsObject>
| undefined = | undefined =
projectType === ProjectType.PROJECTTYPE_OWNED ? projectType === ProjectType.PROJECTTYPE_OWNED ?
this.mgmtService.listProjectMembers(projectId, pageSize, offset) : this.service.listProjectMembers(projectId, pageSize, offset) :
projectType === ProjectType.PROJECTTYPE_GRANTED && grantId ? projectType === ProjectType.PROJECTTYPE_GRANTED && grantId ?
this.mgmtService.listProjectGrantMembers(projectId, this.service.listProjectGrantMembers(projectId,
grantId, pageSize, offset) : undefined; grantId, pageSize, offset) : undefined;
if (promise) { if (promise) {
from(promise).pipe( from(promise).pipe(

View File

@ -1,29 +1,28 @@
<app-detail-layout *ngIf="project" <cnsl-detail-layout *ngIf="project"
[backRouterLink]="[ '/projects', (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '']" [backRouterLink]="[ '/projects', (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '']"
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}" title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}"> description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}">
<p class="desc">{{'MEMBER.DOCSINFO' | translate}} <a href="https://docs.zitadel.ch/docs/manuals/admin-managers" <p class="desc">{{'MEMBER.DOCSINFO' | translate}} <a href="https://docs.zitadel.ch/docs/manuals/admin-managers"
target="_blank">ZITADEL Managers</a>.</p> target="_blank">ZITADEL Managers</a>.</p>
<app-members-table *ngIf="project" [dataSource]="dataSource" [memberRoleOptions]="memberRoleOptions" <cnsl-members-table *ngIf="project" [dataSource]="dataSource" [memberRoleOptions]="memberRoleOptions"
(updateRoles)="updateRoles($event.member, $event.change)" [factoryLoadFunc]="changePageFactory" (updateRoles)="updateRoles($event.member, $event.change)" [factoryLoadFunc]="changePageFactory"
(changedSelection)="selection = $event" [refreshTrigger]="changePage" (changedSelection)="selection = $event" [refreshTrigger]="changePage"
[canWrite]="['project.member.write$', 'project.member.write:'+ (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: ''] | hasRole | async" [canWrite]="['project.member.write$', 'project.member.write:'+ (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: ''] | hasRole | async"
[canDelete]="['project.member.delete$', 'project.member.delete:'+(projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: ''] | hasRole | async" [canDelete]="['project.member.delete$', 'project.member.delete:'+(projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: ''] | hasRole | async"
(deleteMember)="removeProjectMember($event)"> (deleteMember)="removeProjectMember($event)">
<ng-template appHasRole selectactions <ng-template cnslHasRole selectactions
[appHasRole]="['project.member.delete:' + (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '', 'project.member.delete']"> [hasRole]="['project.member.delete:' + (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '', 'project.member.delete']">
<button (click)="removeProjectMemberSelection()" color="warn" <button (click)="removeProjectMemberSelection()" color="warn"
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" mat-raised-button> matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" mat-raised-button>
<i class="las la-trash"></i> <i class="las la-trash"></i>
{{'ACTIONS.SELECTIONDELETE' | translate}} {{'ACTIONS.SELECTIONDELETE' | translate}}
</button> </button>
</ng-template> </ng-template>
<ng-template appHasRole writeactions <ng-template cnslHasRole writeactions
[appHasRole]="['project.member.write:'+(projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '','project.member.write']"> [hasRole]="['project.member.write:'+(projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '','project.member.write']">
<a color="primary" (click)="openAddMember()" color="primary" mat-raised-button> <a color="primary" (click)="openAddMember()" color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a> </a>
</ng-template> </ng-template>
</app-members-table> </cnsl-members-table>
</app-detail-layout> </cnsl-detail-layout>
<!-- TODO: check for both project.member and project.grant.member permissions -->

View File

@ -14,205 +14,209 @@ import { CreationType, MemberCreateDialogComponent } from '../add-member-dialog/
import { ProjectMembersDataSource, ProjectType } from './project-members-datasource'; import { ProjectMembersDataSource, ProjectType } from './project-members-datasource';
@Component({ @Component({
selector: 'app-project-members', selector: 'cnsl-project-members',
templateUrl: './project-members.component.html', templateUrl: './project-members.component.html',
styleUrls: ['./project-members.component.scss'], styleUrls: ['./project-members.component.scss'],
}) })
export class ProjectMembersComponent { export class ProjectMembersComponent {
public INITIALPAGESIZE: number = 25; public INITIALPAGESIZE: number = 25;
public project!: Project.AsObject | GrantedProject.AsObject; public project!: Project.AsObject | GrantedProject.AsObject;
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED; public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public grantId: string = ''; public grantId: string = '';
public projectName: string = ''; public projectName: string = '';
public dataSource!: ProjectMembersDataSource; public dataSource!: ProjectMembersDataSource;
public memberRoleOptions: string[] = []; public memberRoleOptions: string[] = [];
public changePageFactory!: Function; public changePageFactory!: Function;
public changePage: EventEmitter<void> = new EventEmitter(); public changePage: EventEmitter<void> = new EventEmitter();
public selection: Array<Member.AsObject> = []; public selection: Array<Member.AsObject> = [];
public ProjectType: any = ProjectType; public ProjectType: any = ProjectType;
constructor( constructor(
private mgmtService: ManagementService, private mgmtService: ManagementService,
private dialog: MatDialog, private dialog: MatDialog,
private toast: ToastService, private toast: ToastService,
private route: ActivatedRoute) { private route: ActivatedRoute) {
this.route.data.pipe(take(1)).subscribe(data => { this.route.data.pipe(take(1)).subscribe(data => {
this.projectType = data.type; this.projectType = data.type;
this.getRoleOptions(); this.getRoleOptions();
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
this.grantId = params.grantid; this.grantId = params.grantid;
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) { if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.getProjectByID(params.projectid).then(resp => { this.mgmtService.getProjectByID(params.projectid).then(resp => {
if (resp.project) { if (resp.project) {
this.project = resp.project; this.project = resp.project;
this.projectName = this.project.name; this.projectName = this.project.name;
this.dataSource = new ProjectMembersDataSource(this.mgmtService); this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.id, this.projectType, 0, this.INITIALPAGESIZE); this.dataSource.loadMembers(this.project.id, this.projectType, 0, this.INITIALPAGESIZE);
this.changePageFactory = (event?: PageEvent) => { this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers( return this.dataSource.loadMembers(
(this.project as Project.AsObject).id, (this.project as Project.AsObject).id,
this.projectType, this.projectType,
event?.pageIndex ?? 0, event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE, event?.pageSize ?? this.INITIALPAGESIZE,
this.grantId, this.grantId,
); );
}; };
} }
}); });
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) { } else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.getGrantedProjectByID(params.projectid, params.grantid).then(resp => { this.mgmtService.getGrantedProjectByID(params.projectid, params.grantid).then(resp => {
if (resp.grantedProject) { if (resp.grantedProject) {
this.project = resp.grantedProject; this.project = resp.grantedProject;
this.projectName = this.project.projectName; this.projectName = this.project.projectName;
this.dataSource = new ProjectMembersDataSource(this.mgmtService); this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.projectId, this.dataSource.loadMembers(this.project.projectId,
this.projectType, this.projectType,
0, 0,
this.INITIALPAGESIZE, this.INITIALPAGESIZE,
this.grantId, this.grantId,
); );
this.changePageFactory = (event?: PageEvent) => { this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers( return this.dataSource.loadMembers(
(this.project as GrantedProject.AsObject).projectId, (this.project as GrantedProject.AsObject).projectId,
this.projectType, this.projectType,
event?.pageIndex ?? 0, event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE, event?.pageSize ?? this.INITIALPAGESIZE,
this.grantId, this.grantId,
); );
}; };
} }
}); });
} }
}); });
});
}
public getRoleOptions(): void {
if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.listProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.listProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).catch(error => {
this.toast.showError(error);
});
}
}
public removeProjectMemberSelection(): void {
Promise.all(this.selection.map(member => {
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
return this.mgmtService.removeProjectMember((this.project as Project.AsObject).id, member.userId)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
return this.mgmtService.removeProjectGrantMember(
(this.project as GrantedProject.AsObject).projectId,
this.grantId,
member.userId,
).then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
return Promise.reject();
}
})).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
});
}
public removeProjectMember(member: Member.AsObject | Member.AsObject): void {
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.removeProjectMember((this.project as Project.AsObject).id, member.userId).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.removeProjectGrantMember((this.project as GrantedProject.AsObject).projectId, this.grantId,
member.userId).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
}); });
} }
}
public getRoleOptions(): void { public openAddMember(): void {
if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) { const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
this.mgmtService.listProjectGrantMemberRoles().then(resp => { data: {
this.memberRoleOptions = resp.resultList; creationType: CreationType.PROJECT_OWNED,
}).catch(error => { },
this.toast.showError(error); width: '400px',
}); });
} else if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.listProjectMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).catch(error => {
this.toast.showError(error);
});
}
}
public removeProjectMemberSelection(): void { dialogRef.afterClosed().subscribe(resp => {
Promise.all(this.selection.map(member => { if (resp) {
const users: User.AsObject[] = resp.users;
const roles: string[] = resp.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) { if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
return this.mgmtService.removeProjectMember((this.project as Project.AsObject).id, member.userId) return this.mgmtService.addProjectMember((this.project as Project.AsObject).id, user.id, roles);
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) { } else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
return this.mgmtService.removeProjectGrantMember( return this.mgmtService.addProjectGrantMember(
(this.project as GrantedProject.AsObject).projectId, (this.project as GrantedProject.AsObject).projectId,
this.grantId, this.grantId,
member.userId, user.id,
).then(() => { roles,
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true); );
}).catch(error => { } else {
this.toast.showError(error); return Promise.reject();
});
} }
})).then(() => { })).then(() => {
setTimeout(() => { setTimeout(() => {
this.changePage.emit(); this.changePage.emit();
}, 1000); }, 1000);
}); this.toast.showInfo('PROJECT.TOAST.MEMBERSADDED', true);
} }).catch(error => {
this.toast.showError(error);
public removeProjectMember(member: Member.AsObject | Member.AsObject): void { });
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.removeProjectMember((this.project as Project.AsObject).id, member.userId).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.removeProjectGrantMember((this.project as GrantedProject.AsObject).projectId, this.grantId,
member.userId).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
this.toast.showInfo('PROJECT.TOAST.MEMBERREMOVED', true);
}).catch(error => {
this.toast.showError(error);
});
} }
} }
});
}
public openAddMember(): void { updateRoles(member: Member.AsObject, selectionChange: MatSelectChange): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, { if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
data: { this.mgmtService.updateProjectMember((this.project as Project.AsObject).id, member.userId, selectionChange.value)
creationType: CreationType.PROJECT_OWNED, .then(() => {
}, this.toast.showInfo('PROJECT.TOAST.MEMBERCHANGED', true);
width: '400px', }).catch(error => {
this.toast.showError(error);
}); });
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
dialogRef.afterClosed().subscribe(resp => { this.mgmtService.updateProjectGrantMember((this.project as GrantedProject.AsObject).projectId,
if (resp) { this.grantId, member.userId, selectionChange.value)
const users: User.AsObject[] = resp.users; .then(() => {
const roles: string[] = resp.roles; this.toast.showInfo('PROJECT.TOAST.MEMBERCHANGED', true);
}).catch(error => {
if (users && users.length && roles && roles.length) { this.toast.showError(error);
Promise.all(users.map(user => {
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
return this.mgmtService.addProjectMember((this.project as Project.AsObject).id, user.id, roles);
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
return this.mgmtService.addProjectGrantMember(
(this.project as GrantedProject.AsObject).projectId,
this.grantId,
user.id,
roles,
);
}
})).then(() => {
setTimeout(() => {
this.changePage.emit();
}, 1000);
this.toast.showInfo('PROJECT.TOAST.MEMBERSADDED', true);
}).catch(error => {
this.toast.showError(error);
});
}
}
}); });
} }
}
updateRoles(member: Member.AsObject, selectionChange: MatSelectChange): void {
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.updateProjectMember((this.project as Project.AsObject).id, member.userId, selectionChange.value)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERCHANGED', true);
}).catch(error => {
this.toast.showError(error);
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.updateProjectGrantMember((this.project as GrantedProject.AsObject).projectId,
this.grantId, member.userId, selectionChange.value)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERCHANGED', true);
}).catch(error => {
this.toast.showError(error);
});
}
}
} }

View File

@ -15,7 +15,7 @@
{{'ACTIONS.CLOSE' | translate}} {{'ACTIONS.CLOSE' | translate}}
</button> </button>
<button [disabled]="setting == undefined" cdkFocusInitial color="primary" mat-raised-button class="ok-button" <button [disabled]="setting === undefined" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="closeDialog(setting)"> (click)="closeDialog(setting)">
{{'ACTIONS.OK' | translate}} {{'ACTIONS.OK' | translate}}
</button> </button>

View File

@ -3,7 +3,7 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { PrivateLabelingSetting } from 'src/app/proto/generated/zitadel/project_pb'; import { PrivateLabelingSetting } from 'src/app/proto/generated/zitadel/project_pb';
@Component({ @Component({
selector: 'app-project-private-labeling-dialog', selector: 'cnsl-project-private-labeling-dialog',
templateUrl: './project-private-labeling-dialog.component.html', templateUrl: './project-private-labeling-dialog.component.html',
styleUrls: ['./project-private-labeling-dialog.component.scss'], styleUrls: ['./project-private-labeling-dialog.component.scss'],
}) })

View File

@ -5,51 +5,51 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@Component({ @Component({
selector: 'app-project-role-detail', selector: 'cnsl-project-role-detail',
templateUrl: './project-role-detail.component.html', templateUrl: './project-role-detail.component.html',
styleUrls: ['./project-role-detail.component.scss'], styleUrls: ['./project-role-detail.component.scss'],
}) })
export class ProjectRoleDetailComponent { export class ProjectRoleDetailComponent {
public projectId: string = ''; public projectId: string = '';
public formGroup!: FormGroup; public formGroup!: FormGroup;
constructor(private mgmtService: ManagementService, private toast: ToastService, constructor(private mgmtService: ManagementService, private toast: ToastService,
public dialogRef: MatDialogRef<ProjectRoleDetailComponent>, public dialogRef: MatDialogRef<ProjectRoleDetailComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) { @Inject(MAT_DIALOG_DATA) public data: any) {
this.projectId = data.projectId; this.projectId = data.projectId;
this.formGroup = new FormGroup({ this.formGroup = new FormGroup({
key: new FormControl({ value: '', disabled: true }, [Validators.required]), key: new FormControl({ value: '', disabled: true }, [Validators.required]),
displayName: new FormControl(''), displayName: new FormControl(''),
group: new FormControl(''), group: new FormControl(''),
});
this.formGroup.patchValue(data.role);
}
submitForm(): void {
if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) {
this.mgmtService.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group.value)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true);
this.dialogRef.close(true);
}).catch(error => {
this.toast.showError(error);
}); });
}
}
this.formGroup.patchValue(data.role); public closeDialog(): void {
} this.dialogRef.close(false);
}
submitForm(): void { public get key(): AbstractControl | null {
if (this.formGroup.valid && this.key?.value && this.group?.value && this.displayName?.value) { return this.formGroup.get('key');
this.mgmtService.updateProjectRole(this.projectId, this.key.value, this.displayName.value, this.group.value) }
.then(() => { public get displayName(): AbstractControl | null {
this.toast.showInfo('PROJECT.TOAST.ROLECHANGED', true); return this.formGroup.get('displayName');
this.dialogRef.close(true); }
}).catch(error => { public get group(): AbstractControl | null {
this.toast.showError(error); return this.formGroup.get('group');
}); }
}
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public get key(): AbstractControl | null {
return this.formGroup.get('key');
}
public get displayName(): AbstractControl | null {
return this.formGroup.get('displayName');
}
public get group(): AbstractControl | null {
return this.formGroup.get('group');
}
} }

View File

@ -1,8 +1,8 @@
<app-refresh-table *ngIf="projectId" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult" <cnsl-refresh-table *ngIf="projectId" (refreshed)="refreshPage()" [dataSize]="dataSource?.totalResult ?? 0"
[emitRefreshOnPreviousRoutes]="['/projects/'+projectId+'/roles/create']" [selection]="selection" [emitRefreshOnPreviousRoutes]="['/projects/'+projectId+'/roles/create']" [selection]="selection"
[loading]="dataSource?.loading$ | async" [timestamp]="dataSource?.viewTimestamp"> [loading]="dataSource?.loading$ | async" [timestamp]="dataSource?.viewTimestamp">
<ng-template appHasRole [appHasRole]="['project.role.write:' + projectId, 'project.role.write']" actions> <ng-template cnslHasRole [hasRole]="['project.role.write:' + projectId, 'project.role.write']" actions>
<a *ngIf="actionsVisible" [disabled]="disabled" [routerLink]="[ '/projects', projectId, 'roles', 'create']" <a *ngIf="actionsVisible" [disabled]="disabled" [routerLink]="[ '/projects', projectId, 'roles', 'create']"
color="primary" mat-raised-button> color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
@ -55,7 +55,7 @@
<th mat-header-cell *matHeaderCellDef></th> <th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let role"> <td mat-cell *matCellDef="let role">
<button <button
[disabled]="disabled || (['project.role.delete', 'project.role.delete:' + projectId] | hasRole | async) == false" [disabled]="disabled || (['project.role.delete', 'project.role.delete:' + projectId] | hasRole | async) === false"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}" mat-icon-button color="warn" matTooltip="{{'ACTIONS.DELETE' | translate}}"
(click)="deleteRole(role)"> (click)="deleteRole(role)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
@ -67,7 +67,7 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr> <tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table> </table>
<div *ngIf="(dataSource.loading$ | async) == false && !dataSource?.totalResult" class="no-content-row"> <div *ngIf="(dataSource.loading$ | async) === false && !dataSource?.totalResult" class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{'PROJECT.ROLE.EMPTY' | translate}}</span> <span>{{'PROJECT.ROLE.EMPTY' | translate}}</span>
</div> </div>
@ -76,4 +76,4 @@
[pageSizeOptions]="[25, 50, 100, 250]"> [pageSizeOptions]="[25, 50, 100, 250]">
</cnsl-paginator> </cnsl-paginator>
</div> </div>
</app-refresh-table> </cnsl-refresh-table>

View File

@ -13,111 +13,111 @@ import { ProjectRolesDataSource } from './project-roles-datasource';
@Component({ @Component({
selector: 'app-project-roles', selector: 'cnsl-project-roles',
templateUrl: './project-roles.component.html', templateUrl: './project-roles.component.html',
styleUrls: ['./project-roles.component.scss'], styleUrls: ['./project-roles.component.scss'],
}) })
export class ProjectRolesComponent implements AfterViewInit, OnInit { export class ProjectRolesComponent implements AfterViewInit, OnInit {
@Input() public projectId: string = ''; @Input() public projectId: string = '';
@Input() public disabled: boolean = false; @Input() public disabled: boolean = false;
@Input() public actionsVisible: boolean = false; @Input() public actionsVisible: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent; @ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatTable) public table!: MatTable<Role.AsObject>; @ViewChild(MatTable) public table!: MatTable<Role.AsObject>;
public dataSource!: ProjectRolesDataSource; public dataSource!: ProjectRolesDataSource;
public selection: SelectionModel<Role.AsObject> = new SelectionModel<Role.AsObject>(true, []); public selection: SelectionModel<Role.AsObject> = new SelectionModel<Role.AsObject>(true, []);
@Output() public changedSelection: EventEmitter<Array<Role.AsObject>> = new EventEmitter(); @Output() public changedSelection: EventEmitter<Array<Role.AsObject>> = new EventEmitter();
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
public displayedColumns: string[] = ['select', 'key', 'displayname', 'group', 'creationDate', 'actions']; public displayedColumns: string[] = ['select', 'key', 'displayname', 'group', 'creationDate', 'actions'];
constructor(private mgmtService: ManagementService, private toast: ToastService, private dialog: MatDialog) { constructor(private mgmtService: ManagementService, private toast: ToastService, private dialog: MatDialog) {
this.dataSource = new ProjectRolesDataSource(this.mgmtService); this.dataSource = new ProjectRolesDataSource(this.mgmtService);
} }
public ngOnInit(): void { public ngOnInit(): void {
this.dataSource.loadRoles(this.projectId, 0, 25, 'asc'); this.dataSource.loadRoles(this.projectId, 0, 25, 'asc');
this.selection.changed.subscribe(() => { this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected); this.changedSelection.emit(this.selection.selected);
}); });
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
this.paginator.page this.paginator.page
.pipe( .pipe(
tap(() => this.loadRolesPage()), tap(() => this.loadRolesPage()),
) )
.subscribe(); .subscribe();
} }
public selectAllOfGroup(group: string): void { public selectAllOfGroup(group: string): void {
const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue() const groupRoles: Role.AsObject[] = this.dataSource.rolesSubject.getValue()
.filter(role => role.group === group); .filter(role => role.group === group);
this.selection.select(...groupRoles); this.selection.select(...groupRoles);
} }
private loadRolesPage(): void { private loadRolesPage(): void {
this.dataSource.loadRoles( this.dataSource.loadRoles(
this.projectId, this.projectId,
this.paginator.pageIndex, this.paginator.pageIndex,
this.paginator.pageSize, this.paginator.pageSize,
); );
} }
public changePage(): void { public changePage(): void {
this.selection.clear(); this.selection.clear();
this.loadRolesPage(); this.loadRolesPage();
} }
public isAllSelected(): boolean { public isAllSelected(): boolean {
const numSelected = this.selection.selected.length; const numSelected = this.selection.selected.length;
const numRows = this.dataSource.rolesSubject.value.length; const numRows = this.dataSource.rolesSubject.value.length;
return numSelected === numRows; return numSelected === numRows;
} }
public masterToggle(): void { public masterToggle(): void {
this.isAllSelected() ? this.isAllSelected() ?
this.selection.clear() : this.selection.clear() :
this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row)); this.dataSource.rolesSubject.value.forEach((row: Role.AsObject) => this.selection.select(row));
} }
public deleteRole(role: Role.AsObject): Promise<any> { public deleteRole(role: Role.AsObject): Promise<any> {
const index = this.dataSource.rolesSubject.value.findIndex(iter => iter.key === role.key); const index = this.dataSource.rolesSubject.value.findIndex(iter => iter.key === role.key);
return this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => { return this.mgmtService.removeProjectRole(this.projectId, role.key).then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true); this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
if (index > -1) { if (index > -1) {
this.dataSource.rolesSubject.value.splice(index, 1); this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value); this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
} }
}); });
} }
public removeRole(role: Role.AsObject, index: number): void { public removeRole(role: Role.AsObject, index: number): void {
this.mgmtService this.mgmtService
.removeProjectRole(this.projectId, role.key) .removeProjectRole(this.projectId, role.key)
.then(() => { .then(() => {
this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true); this.toast.showInfo('PROJECT.TOAST.ROLEREMOVED', true);
this.dataSource.rolesSubject.value.splice(index, 1); this.dataSource.rolesSubject.value.splice(index, 1);
this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value); this.dataSource.rolesSubject.next(this.dataSource.rolesSubject.value);
}) })
.catch(error => { .catch(error => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
public openDetailDialog(role: Role.AsObject): void { public openDetailDialog(role: Role.AsObject): void {
this.dialog.open(ProjectRoleDetailComponent, { this.dialog.open(ProjectRoleDetailComponent, {
data: { data: {
role, role,
projectId: this.projectId, projectId: this.projectId,
}, },
width: '400px', width: '400px',
}); });
} }
public refreshPage(): void { public refreshPage(): void {
this.dataSource.loadRoles(this.projectId, this.paginator.pageIndex, this.paginator.pageSize); this.dataSource.loadRoles(this.projectId, this.paginator.pageIndex, this.paginator.pageSize);
} }
} }

View File

@ -18,7 +18,7 @@ const rotate = animation([
), ),
]); ]);
@Component({ @Component({
selector: 'app-refresh-table', selector: 'cnsl-refresh-table',
templateUrl: './refresh-table.component.html', templateUrl: './refresh-table.component.html',
styleUrls: ['./refresh-table.component.scss'], styleUrls: ['./refresh-table.component.scss'],
animations: [ animations: [
@ -29,10 +29,10 @@ const rotate = animation([
}) })
export class RefreshTableComponent implements OnInit { export class RefreshTableComponent implements OnInit {
@Input() public selection: SelectionModel<any> = new SelectionModel<any>(true, []); @Input() public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
@Input() public timestamp!: Timestamp.AsObject; @Input() public timestamp: Timestamp.AsObject | undefined = undefined;
@Input() public dataSize: number = 0; @Input() public dataSize: number = 0;
@Input() public emitRefreshAfterTimeoutInMs: number = 0; @Input() public emitRefreshAfterTimeoutInMs: number = 0;
@Input() public loading: boolean = false; @Input() public loading: boolean | null = false;
@Input() public emitRefreshOnPreviousRoutes: string[] = []; @Input() public emitRefreshOnPreviousRoutes: string[] = [];
@Output() public refreshed: EventEmitter<void> = new EventEmitter(); @Output() public refreshed: EventEmitter<void> = new EventEmitter();
@Input() public hideRefresh: boolean = false; @Input() public hideRefresh: boolean = false;

View File

@ -17,7 +17,7 @@ export enum ProjectAutocompleteType {
} }
@Component({ @Component({
selector: 'app-search-project-autocomplete', selector: 'cnsl-search-project-autocomplete',
templateUrl: './search-project-autocomplete.component.html', templateUrl: './search-project-autocomplete.component.html',
styleUrls: ['./search-project-autocomplete.component.scss'], styleUrls: ['./search-project-autocomplete.component.scss'],
}) })
@ -95,9 +95,9 @@ export class SearchProjectAutocompleteComponent implements OnDestroy {
this.unsubscribed$.next(); this.unsubscribed$.next();
} }
public displayFn(project?: any): string | undefined { public displayFn(project?: any): string {
return (project && project.projectName) ? `${project.projectName}` : return (project && project.projectName) ? `${project.projectName}` :
(project && project.name) ? `${project.name}` : undefined; (project && project.name) ? `${project.name}` : '';
} }
public add(event: MatChipInputEvent): void { public add(event: MatChipInputEvent): void {
@ -111,6 +111,8 @@ export class SearchProjectAutocompleteComponent implements OnDestroy {
return project.projectName === value; return project.projectName === value;
} else if (project?.name) { } else if (project?.name) {
return project.name === value; return project.name === value;
} else {
return false;
} }
}); });
if (index > -1) { if (index > -1) {

Some files were not shown because too many files have changed in this diff Show More