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

@ -28,19 +28,19 @@
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
"src/styles.scss"
],
"scripts": [
"./node_modules/tinycolor2/dist/tinycolor-min.js"
],
"allowedCommonJsDependencies": [
"@angular/common/locales/de",
"src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/management_pb",
"src/app/proto/generated/**",
"file-saver",
"qrcode"
],
"@angular/common/locales/de",
"src/app/proto/generated/zitadel/admin_pb",
"src/app/proto/generated/zitadel/management_pb",
"src/app/proto/generated/**",
"file-saver",
"qrcode"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
@ -84,8 +84,8 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "4mb",
"maximumError": "5mb"
"maximumWarning": "5mb",
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
@ -102,8 +102,7 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
},
"options": {},
"configurations": {
"production": {
"browserTarget": "console:build:production"
@ -142,15 +141,11 @@
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-eslint/builder:lint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**",
"**/proto/generated/**"
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
},
@ -174,6 +169,7 @@
},
"defaultProject": "console",
"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() {
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"
},
"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/cli": "~12.2.8",
"@angular/compiler-cli": "~12.2.8",
@ -69,7 +77,6 @@
"stylelint-config-standard": "^22.0.0",
"stylelint-scss": "^3.21.0",
"ts-node": "~10.2.1",
"tslint": "~6.1.3",
"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$">
<mat-toolbar class="root-header">
<button *ngIf="authenticationService.authenticated" aria-label="Toggle sidenav" mat-icon-button
@ -7,7 +7,7 @@
</button>
<ng-container *ngIf="labelpolicy && !labelpolicy?.disableWatermark">
<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" />
<ng-template #lighttheme>
<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' |
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' ]">
<mat-icon class="avatar">add</mat-icon>
{{'MENU.NEWORG' | translate}}
@ -60,20 +60,20 @@
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION'
| translate}}</a>
<div (clickOutside)="closeAccountCard()" class="icon-container">
<app-avatar
<cnsl-avatar
*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"
[name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)"
[size]="38">
</app-avatar>
<app-accounts-card @accounts class="a_card mat-elevation-z1" *ngIf="showAccount"
(close)="showAccount = false" [user]="user" [iamuser]="iamuser$ | async">
</app-accounts-card>
</cnsl-avatar>
<cnsl-accounts-card @accounts class="a_card mat-elevation-z1" *ngIf="showAccount"
(closedCard)="showAccount = false" [user]="user" [iamuser]="iamuser$ | async">
</cnsl-accounts-card>
</div>
</mat-toolbar>
<mat-drawer-container class="main-container">
<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="list">
<a @navitem class="nav-item" [routerLinkActive]="['active']"
@ -92,8 +92,7 @@
</ng-container>
<div *ngIf="org" [@navAnimation]="org">
<ng-template appHasRole
[appHasRole]="['org.read']">
<ng-template cnslHasRole [hasRole]="['org.read']">
<div @navitem class="divider">
<div class="line"></div>
<span>{{org?.name ? org.name : ('MENU.ORGSECTION' | translate)}}</span>
@ -101,7 +100,7 @@
</div>
</ng-template>
<ng-template appHasRole [appHasRole]="['org.read']">
<ng-template cnslHasRole [hasRole]="['org.read']">
<a @navitem matTooltip="{{'MENU.TOOLTIP.ORG' | translate}}" class="nav-item"
[routerLinkActive]="['active']" [routerLink]="[ '/org']">
<i class="icon las la-cog"></i>
@ -109,7 +108,7 @@
</a>
</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"
[routerLinkActive]="['active']" [routerLink]="[ '/projects']">
<i class="icon las la-layer-group"></i>
@ -133,7 +132,7 @@
</a>
</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"
[routerLinkActive]="['active']" [routerLink]="[ '/users/list/humans']"
[routerLinkActiveOptions]="{ exact: true }">
@ -149,7 +148,7 @@
</a>
</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"
[routerLinkActive]="['active']" [routerLink]="[ '/grants']"
[routerLinkActiveOptions]="{ exact: true }">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,22 @@
<div class="card" appOutsideClick (clickOutside)="closeCard($event)">
<app-avatar
<div class="card" cnslOutsideClick (clickOutside)="closeCard($event)">
<cnsl-avatar
*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 || ''"
[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">
</app-avatar>
</cnsl-avatar>
<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>
<button color="primary" (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<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">
</app-avatar>
</cnsl-avatar>
<div class="col">
<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';
@Component({
selector: 'app-accounts-card',
selector: 'cnsl-accounts-card',
templateUrl: './accounts-card.component.html',
styleUrls: ['./accounts-card.component.scss'],
})
export class AccountsCardComponent implements OnInit {
@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 loadingUsers: boolean = false;
constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) {
@ -37,12 +37,12 @@ export class AccountsCardComponent implements OnInit {
public editUserProfile(): void {
this.router.navigate(['users/me']);
this.close.emit();
this.closedCard.emit();
}
public closeCard(element: HTMLElement): void {
if (!element.classList.contains('dontcloseonclick')) {
this.close.emit();
this.closedCard.emit();
}
}
@ -69,6 +69,6 @@ export class AccountsCardComponent implements OnInit {
public logout(): void {
this.authService.signout();
this.close.emit();
this.closedCard.emit();
}
}

View File

@ -27,7 +27,7 @@
{{'ACTIONS.CANCEL' | translate}}
</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()">
{{'ACTIONS.ADD' | translate}}
</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';
export enum AddKeyDialogType {
MACHINE = 'MACHINE',
AUTHNKEY = 'AUTHNKEY',
MACHINE = 'MACHINE',
AUTHNKEY = 'AUTHNKEY',
}
@Component({
selector: 'app-add-key-dialog',
templateUrl: './add-key-dialog.component.html',
styleUrls: ['./add-key-dialog.component.scss'],
selector: 'cnsl-add-key-dialog',
templateUrl: './add-key-dialog.component.html',
styleUrls: ['./add-key-dialog.component.scss'],
})
export class AddKeyDialogComponent {
public startDate: Date = new Date();
types: KeyType[] = [];
public type!: KeyType;
public dateControl: FormControl = new FormControl('', []);
public startDate: Date = new Date();
types: KeyType[] = [];
public type!: KeyType;
public dateControl: FormControl = new FormControl('', []);
constructor(
public dialogRef: MatDialogRef<AddKeyDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.types = [KeyType.KEY_TYPE_JSON];
this.type = KeyType.KEY_TYPE_JSON;
const today = new Date();
this.startDate.setDate(today.getDate() + 1);
}
constructor(
public dialogRef: MatDialogRef<AddKeyDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.types = [KeyType.KEY_TYPE_JSON];
this.type = KeyType.KEY_TYPE_JSON;
const today = new Date();
this.startDate.setDate(today.getDate() + 1);
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close({ type: this.type, date: this.dateControl.value });
}
public closeDialogWithSuccess(): void {
this.dialogRef.close({ type: this.type, date: this.dateControl.value });
}
}

View File

@ -10,7 +10,7 @@
<cnsl-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()">
<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}}
</mat-option>
</mat-select>
@ -19,16 +19,16 @@
<ng-container
*ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED">
<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)"
[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>
<!-- if no context end -->
<app-search-user-autocomplete [users]="preselectedUsers" (selectionChanged)="users = $event">
</app-search-user-autocomplete>
<cnsl-search-user-autocomplete [users]="preselectedUsers" (selectionChanged)="users = $any($event)">
</cnsl-search-user-autocomplete>
<cnsl-form-field class="full-width" appearance="outline"
*ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED || creationType === CreationType.IAM">
@ -41,8 +41,8 @@
</cnsl-form-field>
<ng-container *ngIf="creationType === CreationType.ORG">
<app-org-member-roles-autocomplete (selectionChanged)="setOrgMemberRoles($event)">
</app-org-member-roles-autocomplete>
<cnsl-org-member-roles-autocomplete (selectionChanged)="setOrgMemberRoles($event)">
</cnsl-org-member-roles-autocomplete>
</ng-container>
</div>
@ -51,7 +51,7 @@
{{'ACTIONS.CANCEL' | translate}}
</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()">
{{'ACTIONS.ADD' | translate}}
</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';
export enum CreationType {
PROJECT_OWNED = 0,
PROJECT_GRANTED = 1,
ORG = 2,
IAM = 3,
PROJECT_OWNED = 0,
PROJECT_GRANTED = 1,
ORG = 2,
IAM = 3,
}
@Component({
selector: 'app-member-create-dialog',
templateUrl: './member-create-dialog.component.html',
styleUrls: ['./member-create-dialog.component.scss'],
selector: 'cnsl-member-create-dialog',
templateUrl: './member-create-dialog.component.html',
styleUrls: ['./member-create-dialog.component.scss'],
})
export class MemberCreateDialogComponent {
private projectId: string = '';
private grantId: string = '';
public preselectedUsers: Array<User.AsObject> = [];
private projectId: string = '';
private grantId: string = '';
public preselectedUsers: Array<User.AsObject> = [];
public creationType!: CreationType;
public creationType!: CreationType;
/**
* Specifies options for creating members,
* without ending $, to enable write event permission even if user is allowed
* to create members for only one specific project.
*/
public creationTypes: Array<{ type: CreationType, disabled$: Observable<boolean>; }> = [
{ type: CreationType.IAM, disabled$: this.authService.isAllowed(['iam.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_GRANTED, disabled$: this.authService.isAllowed(['project.grant.member.write']) },
];
public users: Array<User.AsObject> = [];
public roles: Array<Role.AsObject> | string[] = [];
public CreationType: any = CreationType;
public ProjectAutocompleteType: any = ProjectAutocompleteType;
public memberRoleOptions: string[] = [];
/**
* Specifies options for creating members,
* without ending $, to enable write event permission even if user is allowed
* to create members for only one specific project.
*/
public creationTypes: Array<{ type: CreationType, disabled$: Observable<boolean>; }> = [
{ type: CreationType.IAM, disabled$: this.authService.isAllowed(['iam.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_GRANTED, disabled$: this.authService.isAllowed(['project.grant.member.write']) },
];
public users: Array<User.AsObject> = [];
public roles: Array<Role.AsObject> | string[] = [];
public CreationType: any = CreationType;
public ProjectAutocompleteType: any = ProjectAutocompleteType;
public memberRoleOptions: string[] = [];
public showCreationTypeSelector: boolean = false;
constructor(
private mgmtService: ManagementService,
private adminService: AdminService,
private authService: GrpcAuthService,
public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private toastService: ToastService,
) {
if (data?.projectId) {
this.projectId = data.projectId;
}
if (data?.user) {
this.preselectedUsers = [data.user];
this.users = [data.user];
}
if (data?.creationType !== undefined) {
this.creationType = data.creationType;
this.loadRoles();
} else {
this.showCreationTypeSelector = true;
}
public showCreationTypeSelector: boolean = false;
constructor(
private mgmtService: ManagementService,
private adminService: AdminService,
private authService: GrpcAuthService,
public dialogRef: MatDialogRef<MemberCreateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private toastService: ToastService,
) {
if (data?.projectId) {
this.projectId = data.projectId;
}
if (data?.user) {
this.preselectedUsers = [data.user];
this.users = [data.user];
}
public loadRoles(): void {
switch (this.creationType) {
case CreationType.PROJECT_GRANTED:
this.mgmtService.listProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).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;
}
if (data?.creationType !== undefined) {
this.creationType = data.creationType;
this.loadRoles();
} else {
this.showCreationTypeSelector = true;
}
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
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 loadRoles(): void {
switch (this.creationType) {
case CreationType.PROJECT_GRANTED:
this.mgmtService.listProjectGrantMemberRoles().then(resp => {
this.memberRoleOptions = resp.resultList;
}).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 setOrgMemberRoles(roles: string[]): void {
this.roles = roles;
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
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,
'useragent': type == OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
'native': type == OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}">
<div class="cnsl-app-card" [ngClass]="{'web': type === OIDCAppType.OIDC_APP_TYPE_WEB,
'useragent': type === OIDCAppType.OIDC_APP_TYPE_USER_AGENT,
'native': type === OIDCAppType.OIDC_APP_TYPE_NATIVE, 'api': isApiApp}">
<ng-content></ng-content>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-avatar',
selector: 'cnsl-avatar',
templateUrl: './avatar.component.html',
styleUrls: ['./avatar.component.scss'],
})
@ -77,7 +77,7 @@ export class AvatarComponent implements OnInit {
return colors[hash % colors.length];
}
// tslint:disable
/* eslint-disable */
private hashCode(str: string, seed: number = 0): number {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
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);
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';
@Component({
selector: 'app-card',
selector: 'cnsl-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss'],
animations: [

View File

@ -5,17 +5,18 @@
</button>
</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">
<span *ngIf="hist.values[0].dates[0]" class="date">{{
hist.values[0]?.dates[0]| timestampToDate | localizedDate: 'dd. MMMM YYYY' }}</span>
<span *ngIf="hist.values[0].dates[0]" class="date">
{{ 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="row">
<app-avatar matTooltip="{{ dayelement.editorDisplayName }}"
<cnsl-avatar matTooltip="{{ dayelement.editorDisplayName }}"
*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 || ''">
</app-avatar>
</cnsl-avatar>
<ng-template #spacer>
<div class="spacer"></div>
</ng-template>

View File

@ -29,6 +29,9 @@ export interface MappedChange {
dates: Timestamp.AsObject[];
editorId: string;
editorName: string;
editorDisplayName: string;
editorAvatarUrl: string;
editorPreferredLoginName: string;
eventTypes: Array<{ key: string; localizedMessage: string; }>;
sequences: number[];
}>;
@ -41,7 +44,7 @@ type ListChanges = ListMyUserChangesResponse.AsObject |
ListAppChangesResponse.AsObject;
@Component({
selector: 'app-changes',
selector: 'cnsl-changes',
templateUrl: './changes.component.html',
styleUrls: ['./changes.component.scss'],
})
@ -253,7 +256,7 @@ export class ChangesComponent implements OnInit, OnDestroy {
}
// Order by ascending property value
// tslint:disable
/* eslint-disable */
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
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 => {
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">
<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()">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
@ -51,7 +51,7 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let key">
<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}}"
(click)="deleteKey(key)">
<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"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</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';
@Component({
selector: 'app-client-keys',
selector: 'cnsl-client-keys',
templateUrl: './client-keys.component.html',
styleUrls: ['./client-keys.component.scss'],
})
@ -79,7 +79,7 @@ export class ClientKeysComponent implements OnInit {
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
const type: KeyType = resp.type;
@ -96,7 +96,7 @@ export class ClientKeysComponent implements OnInit {
}
if (type) {
return this.mgmtService.addAppKey(
this.mgmtService.addAppKey(
this.projectId,
this.appId,
type,

View File

@ -6,16 +6,15 @@
<div class="people">
<div class="img-list" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 10; else compact">
<ng-container *ngFor="let member of membersSubject | async; index as i">
<div @animate (click)="emitShowDetail()" class="avatar-circle"
matTooltip="{{ member.displayName }} | {{member.rolesList?.join(' ')}}" [ngStyle]="{'z-index': 100 - i}">
<app-avatar *ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''" [forColor]="member?.userName"
[forColor]="member?.preferredLoginName"
<cnsl-avatar *ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
class="avatar dontcloseonclick" [avatarUrl]="member.avatarUrl|| ''"
[forColor]="member.preferredLoginName ?? 'A'"
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)" [size]="32">
</app-avatar>
</cnsl-avatar>
<ng-template #cog>
<div class="sa-icon">
<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';
@Component({
selector: 'app-contributors',
templateUrl: './contributors.component.html',
styleUrls: ['./contributors.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
selector: 'cnsl-contributors',
templateUrl: './contributors.component.html',
styleUrls: ['./contributors.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
})
export class ContributorsComponent {
@Input() title: string = '';
@Input() description: string = '';
@Input() disabled: boolean = false;
@Input() totalResult: number = 0;
@Input() loading: boolean = false;
@Input() membersSubject!: BehaviorSubject<Member.AsObject[]>;
@Output() addClicked: EventEmitter<void> = new EventEmitter();
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter();
@Output() refreshClicked: EventEmitter<void> = new EventEmitter();
@Input() title: string = '';
@Input() description: string = '';
@Input() disabled: boolean = false;
@Input() totalResult: number = 0;
@Input() loading: boolean | null = false;
@Input() membersSubject!: BehaviorSubject<Member.AsObject[]>;
@Output() addClicked: EventEmitter<void> = new EventEmitter();
@Output() showDetailClicked: EventEmitter<void> = new EventEmitter();
@Output() refreshClicked: EventEmitter<void> = new EventEmitter();
public emitAddMember(): void {
this.addClicked.emit();
}
public emitAddMember(): void {
this.addClicked.emit();
}
public emitShowDetail(): void {
this.showDetailClicked.emit();
}
public emitShowDetail(): void {
this.showDetailClicked.emit();
}
public emitRefresh(): void {
this.refreshClicked.emit();
}
public emitRefresh(): void {
this.refreshClicked.emit();
}
}

View File

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

View File

@ -1,4 +1,4 @@
<div *ngIf="currentMap">
<div *ngIf="currentMap">
<form [formGroup]="form" >
@ -8,24 +8,24 @@
<cnsl-form-field class="formfield" >
<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>
<div class="chips" *ngIf="warnText[key.key] == undefined">
<div class="chips" *ngIf="warnText[key.key] === undefined">
<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="value">{{chip.value}}</span>
<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"></i>
<i *ngIf="copied === chip.value" class="las la-clipboard-check"></i>
</div>
</ng-container>
</div>
</cnsl-form-field>
<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.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.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>
</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>
</form>
</div>

View File

@ -3,6 +3,8 @@ import { FormControl, FormGroup } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { InfoSectionType } from '../info-section/info-section.component';
@Component({
selector: 'cnsl-edit-text',
templateUrl: './edit-text.component.html',
@ -23,6 +25,7 @@ export class EditTextComponent implements OnInit, OnDestroy {
@Input() public disabled: boolean = true;
public copied: string = '';
public InfoSectionType: any = InfoSectionType;
public ngOnInit(): void {
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">
<h2>{{'FEATURES.TIER.TITLE' | translate}}</h2>
@ -17,7 +17,7 @@
<ng-container *ngIf="serviceType === FeatureServiceType.MGMT">
<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}}
<a (click)="setCustomer()">{{'ACTIONS.EDIT' | translate}}</a>
</p>
@ -32,7 +32,7 @@
</p>
</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">
<a color="primary" [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL" target="_blank"
@ -40,7 +40,7 @@
</div>
</ng-container>
<ng-template appHasRole [appHasRole]="['iam.features.delete']">
<ng-template cnslHasRole [hasRole]="['iam.features.delete']">
<button *ngIf="serviceType === FeatureServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="resetFeatures()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
@ -268,12 +268,12 @@
</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"
type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
</div>
</app-detail-layout>
</cnsl-detail-layout>
<ng-template #templateRef let-active="active">
<span class="state" [ngClass]="{'active': active, 'inactive': !active}">

View File

@ -26,7 +26,7 @@ export enum FeatureServiceType {
}
@Component({
selector: 'app-features',
selector: 'cnsl-features',
templateUrl: './features.component.html',
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) {
case FeatureServiceType.MGMT:
return this.managementService.getFeatures();
case FeatureServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getDefaultFeatures();
} else {
return Promise.reject();
}
break;
}
}

View File

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

View File

@ -5,16 +5,16 @@ let nextUniqueId = 0;
export const CNSL_ERROR = new InjectionToken<CnslErrorDirective>('CnslError');
@Directive({
selector: '[cnsl-error]',
host: {
'class': 'cnsl-error',
'role': 'alert',
'[attr.id]': 'id',
},
providers: [{ provide: CNSL_ERROR, useExisting: CnslErrorDirective }],
selector: '[cnslError]',
host: {
'class': 'cnsl-error',
'role': 'alert',
'[attr.id]': 'id',
},
providers: [{ provide: CNSL_ERROR, useExisting: 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 {
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChildren,
ElementRef,
HostListener,
Inject,
InjectionToken,
OnDestroy,
QueryList,
ViewChild,
ViewEncapsulation,
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ContentChildren,
ElementRef,
HostListener,
Inject,
InjectionToken,
OnDestroy,
QueryList,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { NgControl } from '@angular/forms';
import { MatFormFieldControl } from '@angular/material/form-field';
@ -22,142 +22,140 @@ import { startWith, takeUntil } from 'rxjs/operators';
import { cnslFormFieldAnimations } from './animations';
import { CNSL_ERROR, CnslErrorDirective } from './error.directive';
import { _CNSL_HINT, CnslHintDirective } from './hint.directive';
export const CNSL_FORM_FIELD = new InjectionToken<CnslFormFieldComponent>('CnslFormFieldComponent');
class CnslFormFieldBase {
constructor(public _elementRef: ElementRef) { }
constructor(public _elementRef: ElementRef) { }
}
@Component({
selector: 'cnsl-form-field',
templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'],
providers: [
{ provide: CNSL_FORM_FIELD, useExisting: CnslFormFieldComponent },
],
host: {
'[class.ng-untouched]': '_shouldForward("untouched")',
'[class.ng-touched]': '_shouldForward("touched")',
'[class.ng-pristine]': '_shouldForward("pristine")',
'[class.ng-dirty]': '_shouldForward("dirty")',
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
'[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused',
'[class.cnsl-form-field-invalid]': '_control.errorState',
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [cnslFormFieldAnimations.transitionMessages],
selector: 'cnsl-form-field',
templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'],
providers: [
{ provide: CNSL_FORM_FIELD, useExisting: CnslFormFieldComponent },
],
host: {
'[class.ng-untouched]': '_shouldForward("untouched")',
'[class.ng-touched]': '_shouldForward("touched")',
'[class.ng-pristine]': '_shouldForward("pristine")',
'[class.ng-dirty]': '_shouldForward("dirty")',
'[class.ng-valid]': '_shouldForward("valid")',
'[class.ng-invalid]': '_shouldForward("invalid")',
'[class.ng-pending]': '_shouldForward("pending")',
'[class.cnsl-form-field-disabled]': '_control.disabled',
'[class.cnsl-form-field-autofilled]': '_control.autofilled',
'[class.cnsl-focused]': '_control.focused',
'[class.cnsl-form-field-invalid]': '_control.errorState',
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [cnslFormFieldAnimations.transitionMessages],
})
export class CnslFormFieldComponent extends CnslFormFieldBase implements OnDestroy, AfterContentInit, AfterViewInit {
focused: boolean = false;
private _destroyed: Subject<void> = new Subject<void>();
focused: boolean = false;
private _destroyed: Subject<void> = new Subject<void>();
@ViewChild('connectionContainer', { static: true }) _connectionContainerRef!: ElementRef;
@ViewChild('inputContainer') _inputContainerRef!: ElementRef;
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
get _control(): MatFormFieldControl<any> {
return this._explicitFormFieldControl || this._controlNonStatic || this._controlStatic;
@ViewChild('connectionContainer', { static: true }) _connectionContainerRef!: ElementRef;
@ViewChild('inputContainer') _inputContainerRef!: ElementRef;
@ContentChild(MatFormFieldControl) _controlNonStatic!: MatFormFieldControl<any>;
@ContentChild(MatFormFieldControl, { static: true }) _controlStatic!: MatFormFieldControl<any>;
get _control(): MatFormFieldControl<any> {
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'])
_focusChanged(isFocused: boolean): void {
if (isFocused !== this.focused && (!isFocused)) {
this.focused = isFocused;
this.stateChanges.next();
}
public ngAfterViewInit(): void {
// Avoid animations on load.
this._subscriptAnimationState = 'enter';
this._changeDetectorRef.detectChanges();
}
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,
@Inject(ElementRef)
// Use `ElementRef` here so Angular has something to inject.
_labelOptions: any) {
super(_elementRef);
// 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.');
}
}
public ngAfterViewInit(): void {
// Avoid animations on load.
this._subscriptAnimationState = 'enter';
this._changeDetectorRef.detectChanges();
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);
}
}
public ngOnDestroy(): void {
this._destroyed.next();
this._destroyed.complete();
}
/** 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];
}
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());
}
// 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';
}
/** 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 { CnslErrorDirective } from './error.directive';
import { CnslFormFieldComponent } from './form-field.component';
import { CnslHintDirective } from './hint.directive';
@NgModule({
declarations: [
CnslFormFieldComponent,
CnslErrorDirective,
CnslHintDirective,
],
imports: [
CommonModule,
MatRippleModule,
LabelModule,
],
exports: [
CnslFormFieldComponent,
LabelComponent,
CnslErrorDirective,
CnslHintDirective,
],
declarations: [
CnslFormFieldComponent,
CnslErrorDirective,
],
imports: [
CommonModule,
MatRippleModule,
LabelModule,
],
exports: [
CnslFormFieldComponent,
LabelComponent,
CnslErrorDirective,
],
})
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';
@Component({
selector: 'app-idp-create',
selector: 'cnsl-idp-create',
templateUrl: './idp-create.component.html',
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"
[selection]="selection">
<div actions>
@ -23,15 +23,15 @@
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[disabled]="serviceType==PolicyComponentServiceType.MGMT">
[disabled]="serviceType === PolicyComponentServiceType.MGMT">
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let idp">
<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)">
<img src="../../../assets/images/google.png"
*ngIf="idp.stylingType == IDPSTYLINGTYPE.IDPSTYLINGTYPE_GOOGLE" alt="google" />
*ngIf="idp.stylingType === IDPSTYLINGTYPE.IDPSTYLINGTYPE_GOOGLE" alt="google" />
</mat-checkbox>
</td>
</ng-container>
@ -91,7 +91,7 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let idp">
<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}}"
(click)="removeIdp(idp)">
<i class="las la-trash"></i>
@ -107,4 +107,4 @@
</div>
<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>
</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';
@Component({
selector: 'app-idp-table',
selector: 'cnsl-idp-table',
templateUrl: './idp-table.component.html',
styleUrls: ['./idp-table.component.scss'],
})

View File

@ -1,176 +1,173 @@
<app-detail-layout [backRouterLink]="backroutes" [title]="idp?.name"
[description]="'IDP.DETAIL.DESCRIPTION' | translate">
<div *ngIf="canWrite | async" actions>
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="idpactions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
<cnsl-detail-layout [backRouterLink]="backroutes" [title]="'IDP.DETAIL.TITLE' | translate"
[description]="'IDP.DETAIL.DESCRIPTION' | translate">
<div *ngIf="canWrite | async" actions>
<button class="actions-trigger" mat-raised-button color="primary" [matMenuTriggerFor]="idpactions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<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>
<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-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 *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">
<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-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</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-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>
<cnsl-info-row *ngIf="idp" [idp]="idp"></cnsl-info-row>
<form class="idp-form" (ngSubmit)="updateIdp()">
<ng-container [formGroup]="idpForm">
<div class="idp-content">
<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>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="name" />
</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-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>
</form>
</cnsl-info-section>
</div>
</ng-container>
<ng-container *ngIf="idp?.oidcConfig && oidcConfigForm">
<h2>{{'IDP.OIDC.TITLE' | translate}}</h2>
<p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<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>
</form>
<form (ngSubmit)="updateOidcConfig()">
<ng-container [formGroup]="oidcConfigForm">
<div class="idp-content">
<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>
<ng-container *ngIf="idp?.oidcConfig && oidcConfigForm">
<h2>{{'IDP.OIDC.TITLE' | translate}}</h2>
<p>{{'IDP.OIDC.DESCRIPTION' | translate}}</p>
<input cnslInput [matChipInputFor]="chipScopesList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true"
(matChipInputTokenEnd)="addScope($event)">
</cnsl-form-field>
<button (click)="addScope($event)" mat-icon-button>
<mat-icon>add</mat-icon>
</button>
</div>
<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>
<form (ngSubmit)="updateOidcConfig()">
<ng-container [formGroup]="oidcConfigForm">
<div class="idp-content">
<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>
<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>
<div class="btn-wrapper">
<button color="primary" mat-raised-button class="continue-button"
[disabled]="oidcConfigForm.invalid || (canWrite | async) === false" type="submit">
{{ 'ACTIONS.SAVE' | translate }}
<input cnslInput [matChipInputFor]="chipScopesList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="true" (matChipInputTokenEnd)="addScope($event)">
</cnsl-form-field>
<button (click)="addScope($any($event))" mat-icon-button>
<mat-icon>add</mat-icon>
</button>
</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 *ngIf="idp?.jwtConfig && jwtConfigForm">
<h2>{{'IDP.JWT.TITLE' | translate}}</h2>
<p>{{'IDP.JWT.DESCRIPTION' | translate}}</p>
<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>
</div>
</form>
</ng-container>
<form (ngSubmit)="updateJwtConfig()">
<ng-container [formGroup]="jwtConfigForm">
<div class="idp-content">
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.ISSUER' | translate }}</cnsl-label>
<input cnslInput formControlName="issuer" />
</cnsl-form-field>
<ng-container *ngIf="idp?.jwtConfig && jwtConfigForm">
<h2>{{'IDP.JWT.TITLE' | translate}}</h2>
<p>{{'IDP.JWT.DESCRIPTION' | translate}}</p>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="headerName" />
</cnsl-form-field>
<form (ngSubmit)="updateJwtConfig()">
<ng-container [formGroup]="jwtConfigForm">
<div class="idp-content">
<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.JWT.JWTENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtEndpoint" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.HEADERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="headerName" />
</cnsl-form-field>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="keysEndpoint" />
</cnsl-form-field>
</div>
</ng-container>
<cnsl-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="jwtEndpoint" />
</cnsl-form-field>
<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-form-field appearance="outline" class="formfield">
<cnsl-label>{{ 'IDP.JWT.JWTKEYSENDPOINT' | translate }}</cnsl-label>
<input cnslInput formControlName="keysEndpoint" />
</cnsl-form-field>
</div>
</ng-container>
</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';
@Component({
selector: 'app-idp',
selector: 'cnsl-idp',
templateUrl: './idp.component.html',
styleUrls: ['./idp.component.scss'],
})

View File

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

View File

@ -1,6 +1,6 @@
<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 == 'WARN'" class="icon las la-exclamation"></i>
<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 === 'WARN'" class="icon las la-exclamation"></i>
<div class="info-section-content">
<ng-content></ng-content>

View File

@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core';
enum InfoSectionType {
export enum InfoSectionType {
INFO = 'INFO',
SUCCESS = 'SUCCESS',
WARN = 'WARN',
@ -14,5 +14,5 @@ enum InfoSectionType {
export class InfoSectionComponent {
@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.
* @docs-private
*/
// tslint:disable-next-line:no-input-rename
// eslint-disable-next-line @angular-eslint/no-input-rename
@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
// ViewEngine they're overwritten.
/** 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('blur', ['false'])
// tslint:enable:no-host-decorator-in-concrete
/* eslint-enable */
_focusChanged(isFocused: boolean): void {
if (isFocused !== this.focused && (!this.readonly || !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.
// In Ivy the `host` bindings will be merged when this class is extended, whereas in
// ViewEngine they're overwritten.
// tslint:disable-next-line:no-host-decorator-in-concrete
// eslint-disable-next-line
@HostListener('input')
_onInput(): void {
// 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();
}
}
// tslint:disable
/* eslint-disable */
static ngAcceptInputType_disabled: BooleanInput;
static ngAcceptInputType_readonly: 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 different types.
static ngAcceptInputType_value: any;
// tslint:enable
/* eslint-enable */
}

View File

@ -5,7 +5,7 @@
</div>
<div class="row">
<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">
<ng-content select="[icon]"></ng-content>
<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 {
@ -7,7 +7,7 @@ export interface CnslLinks {
routerLink?: any;
href?: string;
iconClasses?: string;
withRole?: Array<string | RegExp>;
withRole?: string[] | RegExp[];
}
@Component({
@ -15,11 +15,6 @@ export interface CnslLinks {
templateUrl: './links.component.html',
styleUrls: ['./links.component.scss'],
})
export class LinksComponent implements OnInit {
export class LinksComponent {
@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">
<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-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
@ -50,7 +50,7 @@
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<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}}"
(click)="deleteKey(key)">
<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"
[pageSizeOptions]="[5, 10, 20]" (page)="changePage($event)"></cnsl-paginator>
</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';
@Component({
selector: 'app-machine-keys',
templateUrl: './machine-keys.component.html',
styleUrls: ['./machine-keys.component.scss'],
selector: 'cnsl-machine-keys',
templateUrl: './machine-keys.component.html',
styleUrls: ['./machine-keys.component.scss'],
})
export class MachineKeysComponent implements OnInit {
@Input() userId!: string;
@Input() userId!: string;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<Key.AsObject> = new MatTableDataSource<Key.AsObject>();
public selection: SelectionModel<Key.AsObject> = new SelectionModel<Key.AsObject>(true, []);
public keyResult!: ListMachineKeysResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@Input() public displayedColumns: string[] = ['select', 'id', 'type', 'creationDate', 'expirationDate', 'actions'];
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
public dataSource: MatTableDataSource<Key.AsObject> = new MatTableDataSource<Key.AsObject>();
public selection: SelectionModel<Key.AsObject> = new SelectionModel<Key.AsObject>(true, []);
public keyResult!: ListMachineKeysResponse.AsObject;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@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,
private toast: ToastService) {
this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected);
});
}
constructor(public translate: TranslateService, private mgmtService: ManagementService, private dialog: MatDialog,
private toast: ToastService) {
this.selection.changed.subscribe(() => {
this.changedSelection.emit(this.selection.selected);
});
}
public ngOnInit(): void {
this.getData(10, 0);
}
public ngOnInit(): void {
this.getData(10, 0);
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize);
}
public changePage(event: PageEvent): void {
this.getData(event.pageSize, event.pageIndex * event.pageSize);
}
public deleteKey(key: Key.AsObject): void {
this.mgmtService.removeMachineKey(key.id, this.userId).then(() => {
this.selection.clear();
this.toast.showInfo('USER.TOAST.SELECTEDKEYSDELETED', true);
this.getData(10, 0);
}).catch(error => {
this.toast.showError(error);
});
}
public deleteKey(key: Key.AsObject): void {
this.mgmtService.removeMachineKey(key.id, this.userId).then(() => {
this.selection.clear();
this.toast.showInfo('USER.TOAST.SELECTEDKEYSDELETED', true);
this.getData(10, 0);
}).catch(error => {
this.toast.showError(error);
});
}
public openAddKey(): void {
const dialogRef = this.dialog.open(AddKeyDialogComponent, {
data: {},
width: '400px',
});
public openAddKey(): void {
const dialogRef = this.dialog.open(AddKeyDialogComponent, {
data: {},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
const type: KeyType = resp.type;
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
const type: KeyType = resp.type;
let date: Timestamp | undefined;
let date: Timestamp | undefined;
if (resp.date as Moment) {
const ts = new Timestamp();
const milliseconds = resp.date.toDate().getTime();
const seconds = Math.abs(milliseconds / 1000);
const nanos = (milliseconds - seconds * 1000) * 1000 * 1000;
ts.setSeconds(seconds);
ts.setNanos(nanos);
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);
});
if (resp.date as Moment) {
const ts = new Timestamp();
const milliseconds = resp.date.toDate().getTime();
const seconds = Math.abs(milliseconds / 1000);
const nanos = (milliseconds - seconds * 1000) * 1000 * 1000;
ts.setSeconds(seconds);
ts.setNanos(nanos);
date = ts;
}
}
public refreshPage(): void {
this.getData(this.paginator.pageSize, this.paginator.pageIndex * this.paginator.pageSize);
if (type) {
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">
<ng-container actions *ngIf="selection.hasValue()">
@ -21,9 +21,9 @@
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox [disabled]="!canWrite" color="primary" (click)="$event.stopPropagation()"
(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">
</app-avatar>
</cnsl-avatar>
<ng-template #cog>
<div class="sa-icon">
<i class="las la-user-cog"></i>
@ -94,4 +94,4 @@
<cnsl-paginator *ngIf="dataSource" class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [pageSize]="INITIALPAGESIZE"
[length]="dataSource.totalResult" [pageSizeOptions]="[25, 50, 100, 250]" (page)="changePage($event)">
</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 { IamMembersDataSource } from 'src/app/pages/iam/iam-members/iam-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 { PageEvent, PaginatorComponent } from '../paginator/paginator.component';
import { ProjectMembersDataSource } from '../project-members/project-members-datasource';
type MemberDatasource = OrgMembersDataSource | ProjectMembersDataSource | IamMembersDataSource;
type MemberDatasource = OrgMembersDataSource | ProjectMembersDataSource | ProjectGrantMembersDataSource | IamMembersDataSource;
@Component({
selector: 'app-members-table',
templateUrl: './members-table.component.html',
styleUrls: ['./members-table.component.scss'],
selector: 'cnsl-members-table',
templateUrl: './members-table.component.html',
styleUrls: ['./members-table.component.scss'],
})
export class MembersTableComponent implements OnInit, OnDestroy {
public INITIALPAGESIZE: number = 25;
@Input() public canDelete: boolean = false;
@Input() public canWrite: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatTable) public table!: MatTable<Member.AsObject>;
@Input() public dataSource!: MemberDatasource;
public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
@Input() public memberRoleOptions: string[] = [];
@Input() public factoryLoadFunc!: Function;
@Input() public refreshTrigger!: Observable<void>;
@Output() public updateRoles: EventEmitter<{ member: Member.AsObject, change: MatSelectChange; }> = new EventEmitter();
@Output() public changedSelection: EventEmitter<any[]> = new EventEmitter();
@Output() public deleteMember: EventEmitter<Member.AsObject> = new EventEmitter();
public INITIALPAGESIZE: number = 25;
@Input() public canDelete: boolean | null = false;
@Input() public canWrite: boolean | null = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatTable) public table!: MatTable<Member.AsObject>;
@Input() public dataSource!: MemberDatasource;
public selection: SelectionModel<any> = new SelectionModel<any>(true, []);
@Input() public memberRoleOptions: string[] = [];
@Input() public factoryLoadFunc!: Function;
@Input() public refreshTrigger!: Observable<void>;
@Output() public updateRoles: EventEmitter<{ member: Member.AsObject, change: MatSelectChange; }> = new EventEmitter();
@Output() public changedSelection: EventEmitter<any[]> = 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. */
public displayedColumns: string[] = ['select', 'userId', 'firstname', 'lastname', 'loginname', 'email', 'roles'];
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
public displayedColumns: string[] = ['select', 'userId', 'firstname', 'lastname', 'loginname', 'email', 'roles'];
constructor() {
this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe(_ => {
this.changedSelection.emit(this.selection.selected);
});
constructor() {
this.selection.changed.pipe(takeUntil(this.destroyed)).subscribe(_ => {
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 {
this.refreshTrigger.pipe(takeUntil(this.destroyed)).subscribe(() => {
this.changePage(this.paginator);
});
public ngOnDestroy(): void {
this.destroyed.next();
}
if (this.canDelete) {
this.displayedColumns.push('actions');
}
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.membersSubject.value.length;
return numSelected === numRows;
}
public ngOnDestroy(): void {
this.destroyed.next();
}
public masterToggle(): void {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.membersSubject.value.forEach(row => this.selection.select(row));
}
public isAllSelected(): boolean {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.membersSubject.value.length;
return numSelected === numRows;
}
public changePage(event?: PageEvent): any {
this.selection.clear();
return this.factoryLoadFunc(event ?? this.paginator);
}
public masterToggle(): void {
this.isAllSelected() ?
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);
}
public triggerDeleteMember(member: any): void {
this.deleteMember.emit(member);
}
}

View File

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

View File

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

View File

@ -1,10 +1,10 @@
<div class="validation-col" *ngIf="this.policy">
<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>
<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">
<mat-progress-spinner class="spinner" diameter="20" [color]="currentError ? 'warn': 'valid'"
mode="determinate" [value]="(password?.value?.length / policy.minLength) * 100">

View File

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

View File

@ -4,7 +4,7 @@
<p class="desc"> {{'LOGINPOLICY.ADDIDP.DESCRIPTION' | translate}}</p>
<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>
<mat-select [(ngModel)]="idpType" (selectionChange)="loadIdps()">
<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';
@Component({
selector: 'app-add-idp-dialog',
selector: 'cnsl-add-idp-dialog',
templateUrl: './add-idp-dialog.component.html',
styleUrls: ['./add-idp-dialog.component.scss'],
})
@ -23,7 +23,7 @@ export class AddIdpDialogComponent {
];
public idp: IDP.AsObject | undefined = undefined;
public availableIdps: Array<IDP.AsObject[] | IDP.AsObject> | string[] = [];
public availableIdps: IDP.AsObject[] = [];
constructor(
private mgmtService: ManagementService,

View File

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

View File

@ -1,177 +1,208 @@
<app-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.LOGIN_POLICY.TITLE' | 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-detail-layout [backRouterLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']"
[title]="'POLICY.LOGIN_POLICY.TITLE' | 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>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="!isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()"
mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn"
(click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-template appHasRole [appHasRole]="['policy.write']">
<button *ngIf="isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['login_policy'] | hasFeature | async) == false" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}" (click)="savePolicy()"
mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
</ng-template>
<ng-template cnslHasRole [hasRole]="['policy.write']">
<button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}"
(click)="savePolicy()" mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
</ng-template>
</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 }}"
[expanded]="true">
<cnsl-login-policy-idps [serviceType]="serviceType" [service]="service"
[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>
</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>
</app-card>
<app-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="WARN">
<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]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<app-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)">
</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>
<ng-template #factorsInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.FORCEMFA_DESC' | translate}}
</cnsl-info-section>
<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>
</ng-template>
</div>
</app-card>
<div class="row">
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<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>
<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']">
<app-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}"
[expanded]="false">
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<app-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))">
</app-idp-table>
</app-card>
</ng-template>
<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>
<app-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid>
</app-detail-layout>
<cnsl-info-section
*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 { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { GridPolicy, LOGIN_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './mfa-table/mfa-table.component';
@Component({
selector: 'app-login-policy',
selector: 'cnsl-login-policy',
templateUrl: './login-policy.component.html',
styleUrls: ['./login-policy.component.scss'],
})
@ -39,6 +40,7 @@ export class LoginPolicyComponent implements OnDestroy {
public disabled: boolean = true;
public currentPolicy: GridPolicy = LOGIN_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor(
private route: ActivatedRoute,
private toast: ToastService,

View File

@ -6,7 +6,7 @@
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
<mat-select [(ngModel)]="newMfaType">
<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}}
</mat-option>
</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';
enum LoginMethodComponentType {
MultiFactor = 1,
SecondFactor = 2,
MultiFactor = 1,
SecondFactor = 2,
}
@Component({
selector: 'app-dialog-add-type',
templateUrl: './dialog-add-type.component.html',
styleUrls: ['./dialog-add-type.component.scss'],
selector: 'cnsl-dialog-add-type',
templateUrl: './dialog-add-type.component.html',
styleUrls: ['./dialog-add-type.component.scss'],
})
export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType;
public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types;
}
public LoginMethodComponentType: any = LoginMethodComponentType;
public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types;
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialog(): void {
this.dialogRef.close();
}
public closeDialogWithCode(): void {
this.dialogRef.close(this.newMfaType);
}
public closeDialogWithCode(): void {
this.dialogRef.close(this.newMfaType);
}
}

View File

@ -1,19 +1,19 @@
<div class="sp_wrapper">
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
</div>
<div class="mfa-list">
<div class="mfa mat-elevation-z1" *ngFor="let mfa of mfas">
<span>
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</span>
<div class="mfa mat-elevation-z1" *ngFor="let mfa of mfas">
<span>
{{(componentType === LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</span>
<button color="warn" *ngIf="!disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<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 color="warn" *ngIf="!disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
<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>
</div>

View File

@ -30,7 +30,7 @@ export enum LoginMethodComponentType {
}
@Component({
selector: 'app-mfa-table',
selector: 'cnsl-mfa-table',
templateUrl: './mfa-table.component.html',
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"
[description]="'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate">
@ -35,23 +35,23 @@
</cnsl-form-field>
</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>
</cnsl-info-section>
<div class="divider"></div>
<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>
</div>
<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>
<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>
</div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid>
</app-detail-layout>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</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 { 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 { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { mapRequestValues } from './helper';
// tslint:disable
/* eslint-disable */
const KeyNamesArray = [
'emailVerificationDoneText',
'emailVerificationText',
@ -61,7 +62,7 @@ const KeyNamesArray = [
'passwordlessText',
'externalRegistrationUserOverviewText'
];
// tslint:enable
/* eslint-enable */
const REQUESTMAP = {
[PolicyComponentServiceType.MGMT]: {
@ -88,7 +89,7 @@ const REQUESTMAP = {
},
};
@Component({
selector: 'app-login-texts',
selector: 'cnsl-login-texts',
templateUrl: './login-texts.component.html',
styleUrls: ['./login-texts.component.scss'],
})
@ -114,6 +115,7 @@ export class LoginTextsComponent implements OnDestroy {
public currentPolicy: GridPolicy = LOGIN_TEXTS_POLICY;
public destroy$: Subject<void> = new Subject();
public InfoSectionType: any = InfoSectionType;
public form: FormGroup = new FormGroup({
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"
[description]="'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate">
<div class="top-actions">
<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>
<cnsl-form-field class="language">
@ -19,23 +19,23 @@
</cnsl-form-field>
</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>
</cnsl-info-section>
<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>
</div>
<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>
<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>
</div>
<div class="divider"></div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid>
</app-detail-layout>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</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 { 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 { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@ -267,7 +268,7 @@ const REQUESTMAP = {
},
};
@Component({
selector: 'app-message-texts',
selector: 'cnsl-message-texts',
templateUrl: './message-texts.component.html',
styleUrls: ['./message-texts.component.scss'],
})
@ -285,6 +286,7 @@ export class MessageTextsComponent implements OnDestroy {
public updateRequest!: SetCustomInitMessageTextRequest | SetDefaultInitMessageTextRequest;
public InfoSectionType: any = InfoSectionType;
public chips: {
[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));
case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails((this.service as ManagementService).getCustomPasswordlessRegistrationMessageText(req));
default:
return undefined;
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
switch (type) {
@ -461,7 +465,11 @@ export class MessageTextsComponent implements OnDestroy {
return this.stripDetails((this.service as AdminService).getCustomDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS:
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 => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
const handler = (prom: Promise<any>): Promise<any> => {
return prom.then(() => {
setTimeout(() => {
this.loadData(this.currentType);
}, 1000);
}).catch(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));
}
if (resp && this.serviceType === PolicyComponentServiceType.MGMT) {
const handler = (prom: Promise<any>): Promise<any> => {
return prom.then(() => {
setTimeout(() => {
this.loadData(this.currentType);
}, 1000);
}).catch(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));
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">
<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"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
@ -24,5 +24,5 @@
}}</button>
</div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="login"></app-policy-grid>
</app-detail-layout>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="login"></cnsl-policy-grid>
</cnsl-detail-layout>

View File

@ -15,7 +15,7 @@ import { GridPolicy, IAM_POLICY } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-org-iam-policy',
selector: 'cnsl-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
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) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
@ -74,6 +74,8 @@ export class OrgIamPolicyComponent implements OnDestroy {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
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">
<ng-template appHasRole [appHasRole]="['policy.delete']">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
@ -41,4 +41,4 @@
<button (click)="savePolicy()" color="primary" type="submit"
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
</div>
</app-detail-layout>
</cnsl-detail-layout>

View File

@ -4,7 +4,7 @@ import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { GetPasswordAgePolicyResponse as AdminGetPasswordAgePolicyResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import {
GetPasswordAgePolicyResponse as MgmtGetPasswordAgePolicyResponse,
GetPasswordAgePolicyResponse as MgmtGetPasswordAgePolicyResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { PasswordAgePolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -15,138 +15,138 @@ import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-password-age-policy',
templateUrl: './password-age-policy.component.html',
styleUrls: ['./password-age-policy.component.scss'],
selector: 'cnsl-password-age-policy',
templateUrl: './password-age-policy.component.html',
styleUrls: ['./password-age-policy.component.scss'],
})
export class PasswordAgePolicyComponent implements OnDestroy {
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: AdminService | ManagementService;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
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;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
return this.route.params;
})).subscribe(() => {
this.getData().then(resp => {
if (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();
return this.route.params;
})).subscribe(() => {
this.getData().then(resp => {
if (resp.policy) {
this.ageData = resp.policy;
}
}
});
});
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
(this.service as ManagementService).resetPasswordAgePolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.getData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public incrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays !== undefined) {
this.ageData.expireWarnDays++;
}
}
private async getData():
Promise<MgmtGetPasswordAgePolicyResponse.AsObject | AdminGetPasswordAgePolicyResponse.AsObject> {
public decrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays && this.ageData?.expireWarnDays > 0) {
this.ageData.expireWarnDays--;
}
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordAgePolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordAgePolicy();
}
}
public incrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays !== undefined) {
this.ageData.maxAgeDays++;
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
(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 {
if (this.ageData?.maxAgeDays && this.ageData?.maxAgeDays > 0) {
this.ageData.maxAgeDays--;
}
public incrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays !== undefined) {
this.ageData.expireWarnDays++;
}
}
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 {
(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 decrementExpireWarnDays(): void {
if (this.ageData?.expireWarnDays && this.ageData?.expireWarnDays > 0) {
this.ageData.expireWarnDays--;
}
}
public get isDefault(): boolean {
if (this.ageData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.ageData as PasswordAgePolicy.AsObject).isDefault;
public incrementMaxAgeDays(): void {
if (this.ageData?.maxAgeDays !== undefined) {
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 {
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">
<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>
</cnsl-info-section>
@ -11,8 +11,8 @@
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) == false"
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<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>
{{'POLICY.RESET' | translate}}
</button>
@ -24,11 +24,11 @@
<span class="left-desc">{{'POLICY.DATA.MINLENGTH' | translate}}</span>
<span class="fill-space"></span>
<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>
</button>
<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>
</button>
</div>
@ -37,14 +37,14 @@
<mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASNUMBER' | translate}}</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>
</div>
<div class="row">
<mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASSYMBOL' | translate}}</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>
</div>
<div class="row">
@ -52,7 +52,7 @@
<span class="left-desc">{{'POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="fill-space"></span>
<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>
</div>
<div class="row">
@ -60,15 +60,15 @@
<span class="left-desc">{{'POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="fill-space"></span>
<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>
</div>
</div>
<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>
</div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></app-policy-grid>
</app-detail-layout>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></cnsl-policy-grid>
</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 { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { COMPLEXITY_POLICY, GridPolicy } from '../../policy-grid/policies';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-password-policy',
selector: 'cnsl-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
})
@ -32,6 +33,7 @@ export class PasswordComplexityPolicyComponent implements OnDestroy {
public loading: boolean = false;
public currentPolicy: GridPolicy = COMPLEXITY_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor(
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">
<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>
</cnsl-info-section>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) == false" *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<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>
{{'POLICY.RESET' | translate}}
</button>
@ -19,11 +19,11 @@
<span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span>
<span class="fill-space"></span>
<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>
</button>
<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>
</button>
</div>
@ -31,7 +31,7 @@
</div>
<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>
</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 { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-password-lockout-policy',
selector: 'cnsl-password-lockout-policy',
templateUrl: './password-lockout-policy.component.html',
styleUrls: ['./password-lockout-policy.component.scss'],
})
@ -27,6 +28,7 @@ export class PasswordLockoutPolicyComponent implements OnDestroy {
public lockoutData!: LockoutPolicy.AsObject;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public InfoSectionType: any = InfoSectionType;
constructor(
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"
[description]="'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate">
<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>
</cnsl-info-section>
@ -25,12 +25,11 @@
</div>
<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>
<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>
</div>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid>
</app-detail-layout>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</cnsl-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 { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { CnslLinks } from '../../links/links.component';
import { GridPolicy, PRIVACY_POLICY } from '../../policy-grid/policies';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-privacy-policy',
selector: 'cnsl-privacy-policy',
templateUrl: './privacy-policy.component.html',
styleUrls: ['./privacy-policy.component.scss'],
})
@ -39,6 +40,7 @@ export class PrivacyPolicyComponent implements OnDestroy {
public privacyPolicy!: PrivacyPolicy.AsObject;
public form!: FormGroup;
public currentPolicy: GridPolicy = PRIVACY_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor(
private route: ActivatedRoute,

View File

@ -1,33 +1,33 @@
<div class="preview" *ngIf="policy">
<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="login-wrapper" [style.color]="theme == Theme.DARK ? policy.fontColorDark : policy.fontColor">
<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">
<ng-container *ngIf="preview === Preview.PREVIEW; else currentimgs">
<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['previewLogo'] && theme === Theme.LIGHT" [src]="images['previewLogo']" alt="logo-mock" />
<img *ngIf="images['previewDarkLogo'] && theme === Theme.DARK" [src]="images['previewDarkLogo']" alt="logo-mock" />
</ng-container>
<ng-template #currentimgs>
<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['logo'] && theme === Theme.LIGHT" [src]="images['logo']" alt="logo-mock" />
<img *ngIf="images['darkLogo'] && theme === Theme.DARK" [src]="images['darkLogo']" alt="logo-mock" />
</ng-template>
<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>
<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>
<cnsl-form-field class="formfield">
<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>
<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>
<span [style.color]="theme == Theme.DARK ? policy.warnColorDark : policy.warnColor">{{'POLICY.PRIVATELABELING.PREVIEW.ERROR' | translate}}</span>
<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>
<span [style.color]="theme === Theme.DARK ? policy.warnColorDark : policy.warnColor">{{'POLICY.PRIVATELABELING.PREVIEW.ERROR' | translate}}</span>
</div>
<div class="btn-wrapper">
<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.LIGHT" mat-raised-button [style.background]="policy.primaryColor" [style.color]="policy.primaryColor == '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | 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.LIGHT" mat-raised-button [style.background]="policy.primaryColor" [style.color]="policy.primaryColor === '#ffffff' ? '#000000' : '#ffffff'">{{'POLICY.PRIVATELABELING.PREVIEW.PRIMARYBUTTON' | translate}}</button>
</div>
</div>
</div>

View File

@ -11,7 +11,7 @@
<p class="desc">{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}</p>
<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>
</cnsl-info-section>
@ -34,14 +34,14 @@
<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"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</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}}
</button>
</div>
@ -99,8 +99,8 @@
<label class="file-label">
<i class="icon las la-image"></i>
<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)">
<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>
<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>
</label>
</div>
</div>
@ -138,8 +138,8 @@
<label class="file-label">
<i class="icon las la-image"></i>
<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)">
<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>
<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>
</label>
</div>
</div>
@ -155,10 +155,8 @@
{{'POLICY.PRIVATELABELING.COLORS' | translate}}</div>
</mat-panel-title>
</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="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>
</ng-container>
<ng-container *ngIf="theme==Theme.LIGHT">
<ng-container *ngIf="theme === Theme.LIGHT">
<div class="colors" *ngIf="data && previewData">
<div class="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>
<div class="fonts">
<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>
<span>ABC • abc • 123</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 class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
@ -227,8 +225,8 @@
<label class="file-label">
<i class="icon las la-file"></i>
<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)">
<a class="btn" *ngIf="serviceType == PolicyComponentServiceType.ADMIN || (serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async))" (click)="selectedFontFile.click()">{{'POLICY.PRIVATELABELING.BTN' | translate}}</a>
<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>
</label>
</div>
</div>
@ -246,25 +244,25 @@
<div class="adv-container" *ngIf="previewData">
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type="WARN"
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN"
>
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span>
</cnsl-info-section>
</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()">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle>
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type="WARN">
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.watermark'})"></span>
</cnsl-info-section>
</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()">
{{'POLICY.DATA.DISABLEWATERMARK' | translate}}
</mat-slide-toggle>
@ -275,8 +273,8 @@
<div class="preview-wrapper">
<!-- <cnsl-preview class="prev" label="CURRENT CONFIG" [policy]="data"></cnsl-preview> -->
<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}" >
<span><span [ngClass]="{'strong': preview == Preview.PREVIEW}">P</span> / <span [ngClass]="{'strong': preview == Preview.CURRENT}">C</span></span>
<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>
</button>
<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>
<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>

View File

@ -25,6 +25,7 @@ import { StorageKey, StorageLocation, StorageService } from 'src/app/services/st
import { ThemeService } from 'src/app/services/theme.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 { PolicyComponentServiceType } from '../policy-component-types.enum';
@ -51,7 +52,7 @@ export enum ColorType {
const MAX_ALLOWED_SIZE = 0.5 * 1024 * 1024;
@Component({
selector: 'app-private-labeling-policy',
selector: 'cnsl-private-labeling-policy',
templateUrl: './private-labeling-policy.component.html',
styleUrls: ['./private-labeling-policy.component.scss'],
})
@ -88,6 +89,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
public loadingImages: boolean = false;
private org!: Org.AsObject;
public currentPolicy: GridPolicy = PRIVATELABEL_POLICY;
public InfoSectionType: any = InfoSectionType;
constructor(
private authService: GrpcAuthService,
private route: ActivatedRoute,
@ -164,16 +166,18 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
}
}
public onDropFont(filelist: FileList): Promise<any> | void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData, this.org.id));
case PolicyComponentServiceType.ADMIN:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData, this.org.id));
public onDropFont(filelist: FileList | null): Promise<any> | void {
if (filelist) {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, 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">
<ng-container *ngFor="let policy of filteredPolicies">
<ng-template appHasRole
[appHasRole]="type == PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type == PolicyComponentServiceType.MGMT ? policy.orgWithRole : []">
<ng-template cnslHasRole
[hasRole]="type === PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type === PolicyComponentServiceType.MGMT ? policy.orgWithRole : []">
<div class="p-item card">
<div class="avatar {{policy.color}}">
<mat-icon *ngIf="policy.svgIcon" class="icon" [svgIcon]="policy.svgIcon"></mat-icon>
@ -28,7 +28,7 @@
</div>
<div class="btn-wrapper">
<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>
</div>
</div>

View File

@ -4,7 +4,7 @@ import { PolicyComponentServiceType, PolicyComponentType } from 'src/app/modules
import { GridPolicy, POLICIES } from './policies';
@Component({
selector: 'app-policy-grid',
selector: 'cnsl-policy-grid',
templateUrl: './policy-grid.component.html',
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);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
constructor(private mgmtService: ManagementService) {
constructor(private service: ManagementService) {
super();
}
public loadMembers(projectId: string,
projectType: ProjectType,
pageIndex: number, pageSize: number, grantId?: string): void {
public loadMembers(projectId: string, projectType: ProjectType, pageIndex: number, pageSize: number, grantId?: string): void {
const offset = pageIndex * pageSize;
this.loadingSubject.next(true);
@ -40,9 +38,9 @@ export class ProjectMembersDataSource extends DataSource<Member.AsObject> {
Promise<ListProjectGrantMembersResponse.AsObject>
| undefined =
projectType === ProjectType.PROJECTTYPE_OWNED ?
this.mgmtService.listProjectMembers(projectId, pageSize, offset) :
this.service.listProjectMembers(projectId, pageSize, offset) :
projectType === ProjectType.PROJECTTYPE_GRANTED && grantId ?
this.mgmtService.listProjectGrantMembers(projectId,
this.service.listProjectGrantMembers(projectId,
grantId, pageSize, offset) : undefined;
if (promise) {
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: '']"
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}">
<p class="desc">{{'MEMBER.DOCSINFO' | translate}} <a href="https://docs.zitadel.ch/docs/manuals/admin-managers"
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"
(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"
[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)">
<ng-template appHasRole selectactions
[appHasRole]="['project.member.delete:' + (projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '', 'project.member.delete']">
<ng-template cnslHasRole selectactions
[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"
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="del-button" mat-raised-button>
<i class="las la-trash"></i>
{{'ACTIONS.SELECTIONDELETE' | translate}}
</button>
</ng-template>
<ng-template appHasRole writeactions
[appHasRole]="['project.member.write:'+(projectType === ProjectType.PROJECTTYPE_OWNED) ? $any(project)?.id : (projectType === ProjectType.PROJECTTYPE_GRANTED) ? $any(project)?.projectId: '','project.member.write']">
<ng-template cnslHasRole writeactions
[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>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</ng-template>
</app-members-table>
</app-detail-layout>
<!-- TODO: check for both project.member and project.grant.member permissions -->
</cnsl-members-table>
</cnsl-detail-layout>

View File

@ -14,205 +14,209 @@ import { CreationType, MemberCreateDialogComponent } from '../add-member-dialog/
import { ProjectMembersDataSource, ProjectType } from './project-members-datasource';
@Component({
selector: 'app-project-members',
templateUrl: './project-members.component.html',
styleUrls: ['./project-members.component.scss'],
selector: 'cnsl-project-members',
templateUrl: './project-members.component.html',
styleUrls: ['./project-members.component.scss'],
})
export class ProjectMembersComponent {
public INITIALPAGESIZE: number = 25;
public project!: Project.AsObject | GrantedProject.AsObject;
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public grantId: string = '';
public projectName: string = '';
public dataSource!: ProjectMembersDataSource;
public memberRoleOptions: string[] = [];
public INITIALPAGESIZE: number = 25;
public project!: Project.AsObject | GrantedProject.AsObject;
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public grantId: string = '';
public projectName: string = '';
public dataSource!: ProjectMembersDataSource;
public memberRoleOptions: string[] = [];
public changePageFactory!: Function;
public changePage: EventEmitter<void> = new EventEmitter();
public selection: Array<Member.AsObject> = [];
public changePageFactory!: Function;
public changePage: EventEmitter<void> = new EventEmitter();
public selection: Array<Member.AsObject> = [];
public ProjectType: any = ProjectType;
constructor(
private mgmtService: ManagementService,
private dialog: MatDialog,
private toast: ToastService,
private route: ActivatedRoute) {
this.route.data.pipe(take(1)).subscribe(data => {
this.projectType = data.type;
public ProjectType: any = ProjectType;
constructor(
private mgmtService: ManagementService,
private dialog: MatDialog,
private toast: ToastService,
private route: ActivatedRoute) {
this.route.data.pipe(take(1)).subscribe(data => {
this.projectType = data.type;
this.getRoleOptions();
this.getRoleOptions();
this.route.params.subscribe(params => {
this.grantId = params.grantid;
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.getProjectByID(params.projectid).then(resp => {
if (resp.project) {
this.project = resp.project;
this.projectName = this.project.name;
this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.id, this.projectType, 0, this.INITIALPAGESIZE);
this.route.params.subscribe(params => {
this.grantId = params.grantid;
if (this.projectType === ProjectType.PROJECTTYPE_OWNED) {
this.mgmtService.getProjectByID(params.projectid).then(resp => {
if (resp.project) {
this.project = resp.project;
this.projectName = this.project.name;
this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.id, this.projectType, 0, this.INITIALPAGESIZE);
this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers(
(this.project as Project.AsObject).id,
this.projectType,
event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE,
this.grantId,
);
};
}
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.getGrantedProjectByID(params.projectid, params.grantid).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
this.projectName = this.project.projectName;
this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.projectId,
this.projectType,
0,
this.INITIALPAGESIZE,
this.grantId,
);
this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers(
(this.project as Project.AsObject).id,
this.projectType,
event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE,
this.grantId,
);
};
}
});
} else if (this.projectType === ProjectType.PROJECTTYPE_GRANTED) {
this.mgmtService.getGrantedProjectByID(params.projectid, params.grantid).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
this.projectName = this.project.projectName;
this.dataSource = new ProjectMembersDataSource(this.mgmtService);
this.dataSource.loadMembers(this.project.projectId,
this.projectType,
0,
this.INITIALPAGESIZE,
this.grantId,
);
this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers(
(this.project as GrantedProject.AsObject).projectId,
this.projectType,
event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE,
this.grantId,
);
};
}
});
}
});
this.changePageFactory = (event?: PageEvent) => {
return this.dataSource.loadMembers(
(this.project as GrantedProject.AsObject).projectId,
this.projectType,
event?.pageIndex ?? 0,
event?.pageSize ?? this.INITIALPAGESIZE,
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 {
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 openAddMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
data: {
creationType: CreationType.PROJECT_OWNED,
},
width: '400px',
});
public removeProjectMemberSelection(): void {
Promise.all(this.selection.map(member => {
dialogRef.afterClosed().subscribe(resp => {
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) {
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);
});
return this.mgmtService.addProjectMember((this.project as Project.AsObject).id, user.id, roles);
} 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);
});
return this.mgmtService.addProjectGrantMember(
(this.project as GrantedProject.AsObject).projectId,
this.grantId,
user.id,
roles,
);
} else {
return Promise.reject();
}
})).then(() => {
})).then(() => {
setTimeout(() => {
this.changePage.emit();
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);
});
this.toast.showInfo('PROJECT.TOAST.MEMBERSADDED', true);
}).catch(error => {
this.toast.showError(error);
});
}
}
}
});
}
public openAddMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
data: {
creationType: CreationType.PROJECT_OWNED,
},
width: '400px',
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);
});
dialogRef.afterClosed().subscribe(resp => {
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) {
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);
});
}
}
} 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);
});
}
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}}
</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)">
{{'ACTIONS.OK' | translate}}
</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';
@Component({
selector: 'app-project-private-labeling-dialog',
selector: 'cnsl-project-private-labeling-dialog',
templateUrl: './project-private-labeling-dialog.component.html',
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';
@Component({
selector: 'app-project-role-detail',
templateUrl: './project-role-detail.component.html',
styleUrls: ['./project-role-detail.component.scss'],
selector: 'cnsl-project-role-detail',
templateUrl: './project-role-detail.component.html',
styleUrls: ['./project-role-detail.component.scss'],
})
export class ProjectRoleDetailComponent {
public projectId: string = '';
public projectId: string = '';
public formGroup!: FormGroup;
constructor(private mgmtService: ManagementService, private toast: ToastService,
public dialogRef: MatDialogRef<ProjectRoleDetailComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
public formGroup!: FormGroup;
constructor(private mgmtService: ManagementService, private toast: ToastService,
public dialogRef: MatDialogRef<ProjectRoleDetailComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.projectId = data.projectId;
this.formGroup = new FormGroup({
key: new FormControl({ value: '', disabled: true }, [Validators.required]),
displayName: new FormControl(''),
group: new FormControl(''),
this.projectId = data.projectId;
this.formGroup = new FormGroup({
key: new FormControl({ value: '', disabled: true }, [Validators.required]),
displayName: 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 {
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);
});
}
}
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');
}
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"
[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']"
color="primary" mat-raised-button>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
@ -55,7 +55,7 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let role">
<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}}"
(click)="deleteRole(role)">
<i class="las la-trash"></i>
@ -67,7 +67,7 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</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>
<span>{{'PROJECT.ROLE.EMPTY' | translate}}</span>
</div>
@ -76,4 +76,4 @@
[pageSizeOptions]="[25, 50, 100, 250]">
</cnsl-paginator>
</div>
</app-refresh-table>
</cnsl-refresh-table>

View File

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

View File

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

View File

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

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