feat(console-v2): login policy extension, domain policy, filter and UI fixes (#3644)

* show filter count when set

* toast contrast color

* fix notification settings, password dialog

* app-create, user-create layout

* domain policy

* login-policy, project grid loader, i18n

* login policy

* login policy save lifetimes

* private labeling optim

* granted project grantId

* smtp address matching

* i18n

* i18n

* i18n

* replace url strategy

* fix privatelabeling color picker saving

* stylelint

Co-authored-by: Livio Amstutz <[email protected]>
This commit is contained in:
Max Peintner
2022-05-17 14:18:37 +00:00
committed by GitHub
co-authored by Livio Amstutz
parent 8d0cf9f368
commit 8baf0fe08c
83 changed files with 1967 additions and 1212 deletions
@@ -47,9 +47,9 @@
width: 32px;
line-height: 1rem;
border-radius: 50%;
-webkit-box-shadow: 2px 0 7px -1px rgba(33, 34, 36, 0.5);
-moz-box-shadow: 2px 0 7px -1px rgba(33, 34, 36, 0.5);
box-shadow: 2px 0 7px -1px rgba(33, 34, 36, 0.5);
-webkit-box-shadow: 1px 0 3px -1px rgba(33, 34, 36, 0.5);
-moz-box-shadow: 1px 0 3px -1px rgba(33, 34, 36, 0.5);
box-shadow: 1px 0 3px -1px rgba(33, 34, 36, 0.5);
cursor: pointer;
display: flex;
align-items: center;
@@ -1,14 +1,18 @@
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()">
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()" [queryCount]="searchQueries.length">
<div class="filter-row" id="filtercomp">
<div class="email-query">
<mat-checkbox id="name" class="cb" [checked]="getSubFilter(SubQuery.NAME)"
(change)="changeCheckbox(SubQuery.NAME, $event )">{{'FILTER.ORGNAME' | translate}}
<mat-checkbox
id="name"
class="cb"
[checked]="getSubFilter(SubQuery.NAME)"
(change)="changeCheckbox(SubQuery.NAME, $event)"
>{{ 'FILTER.ORGNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.NAME) as eq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.'+method | translate}}
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -19,4 +23,4 @@
</div>
</div>
</div>
</cnsl-filter>
</cnsl-filter>
@@ -50,7 +50,7 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
});
this.searchQueries = orgQueries.filter((q) => q !== undefined) as OrgQuery[];
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : undefined);
// this.showFilter = true;
// this.filterOpen.emit(true);
}
@@ -88,11 +88,9 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
switch (subquery) {
case SubQuery.NAME:
(query as OrgNameQuery).setName(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
}
this.filterCount$.next(this.filterCount);
}
public getSubFilter(subquery: SubQuery): any {
@@ -109,23 +107,17 @@ export class FilterOrgComponent extends FilterComponent implements OnInit {
public setMethod(query: any, event: any) {
(query as UserNameQuery).setMethod(event.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);
this.filterCount$.next(this.filterCount);
}
public resetFilter(): void {
this.searchQueries = [];
this.emitFilter();
}
public get filterCount(): number {
return this.searchQueries.length;
}
}
@@ -1,31 +1,18 @@
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()">
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()" [queryCount]="searchQueries.length">
<div class="filter-row" id="filtercomp">
<!-- <div class="name-query">
<mat-checkbox id="resourceowner" class="cb" [checked]="getSubFilter(SubQuery.RESOURCEOWNER)"
(change)="changeCheckbox(SubQuery.RESOURCEOWNER, $event )">{{'FILTER.RESOURCEOWNER' | translate}}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.RESOURCEOWNER) as ronq">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="ronq.getResourceOwner()"
(change)="setValue(SubQuery.RESOURCEOWNER, ronq, $event)" />
</cnsl-form-field>
</div>
</div> -->
<div class="email-query">
<mat-checkbox id="name" class="cb" [checked]="getSubFilter(SubQuery.NAME)"
(change)="changeCheckbox(SubQuery.NAME, $event )">{{'FILTER.PROJECTNAME' | translate}}
<mat-checkbox
id="name"
class="cb"
[checked]="getSubFilter(SubQuery.NAME)"
(change)="changeCheckbox(SubQuery.NAME, $event)"
>{{ 'FILTER.PROJECTNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.NAME) as eq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.'+method | translate}}
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -36,4 +23,4 @@
</div>
</div>
</div>
</cnsl-filter>
</cnsl-filter>
@@ -50,7 +50,7 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
});
this.searchQueries = projectQueries.filter((q) => q !== undefined) as ProjectQuery[];
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
// this.showFilter = true;
// this.filterOpen.emit(true);
}
@@ -60,15 +60,6 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
public changeCheckbox(subquery: SubQuery, event: MatCheckboxChange) {
if (event.checked) {
switch (subquery) {
// case SubQuery.RESOURCEOWNER:
// const ronq = new ProjectResourceOwnerQuery();
// ronq.setResourceOwner('');
// const ro_sq = new ProjectQuery();
// ro_sq.setProjectResourceOwnerQuery(ronq);
// this.searchQueries.push(ro_sq);
// break;
case SubQuery.NAME:
const nq = new ProjectNameQuery();
nq.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
@@ -82,14 +73,6 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
}
} else {
switch (subquery) {
// case SubQuery.RESOURCEOWNER:
// const index_s = this.searchQueries.findIndex(
// (q) => (q as ProjectQuery).toObject().projectResourceOwnerQuery !== undefined,
// );
// if (index_s > -1) {
// this.searchQueries.splice(index_s, 1);
// }
// break;
case SubQuery.NAME:
const index_dn = this.searchQueries.findIndex((q) => (q as ProjectQuery).toObject().nameQuery !== undefined);
if (index_dn > -1) {
@@ -103,28 +86,15 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
public setValue(subquery: SubQuery, query: any, event: any) {
const value = event?.target?.value ?? event.value;
switch (subquery) {
// case SubQuery.RESOURCEOWNER:
// (query as ProjectResourceOwnerQuery).setResourceOwner(value);
// this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
// break;
case SubQuery.NAME:
(query as ProjectNameQuery).setName(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
}
this.filterCount$.next(this.filterCount);
}
public getSubFilter(subquery: SubQuery): any {
switch (subquery) {
// case SubQuery.RESOURCEOWNER:
// const s = this.searchQueries.find((q) => (q as ProjectQuery).toObject().projectResourceOwnerQuery !== undefined);
// if (s) {
// return (s as ProjectQuery).getProjectResourceOwnerQuery();
// } else {
// return undefined;
// }
case SubQuery.NAME:
const dn = this.searchQueries.find((q) => (q as ProjectQuery).toObject().nameQuery !== undefined);
if (dn) {
@@ -137,15 +107,13 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
public setMethod(query: any, event: any) {
(query as UserNameQuery).setMethod(event.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);
this.filterCount$.next(this.filterCount);
}
public resetFilter(): void {
@@ -153,7 +121,7 @@ export class FilterProjectComponent extends FilterComponent implements OnInit {
this.emitFilter();
}
public get filterCount(): number {
public get filterCounter(): number {
return this.searchQueries.length;
}
}
@@ -1,4 +1,4 @@
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()">
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()" [queryCount]="searchQueries.length">
<div class="filter-row" id="filtercomp">
<div class="name-query">
<mat-checkbox
@@ -82,7 +82,7 @@ export class FilterUserGrantsComponent extends FilterComponent implements OnInit
});
this.searchQueries = userQueries.filter((q) => q !== undefined) as UserGrantQuery[];
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
// this.showFilter = true;
// this.filterOpen.emit(true);
}
@@ -170,23 +170,21 @@ export class FilterUserGrantsComponent extends FilterComponent implements OnInit
switch (subquery) {
case SubQuery.DISPLAYNAME:
(query as DisplayNameQuery).setDisplayName(event?.target?.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.USERNAME:
(query as UserNameQuery).setUserName(event?.target?.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.ORGNAME:
(query as UserGrantOrgNameQuery).setOrgName(event?.target?.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.PROJECTNAME:
(query as UserGrantProjectNameQuery).setProjectName(event?.target?.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
}
this.filterCount$.next(this.filterCount);
}
public getSubFilter(subquery: SubQuery): any {
@@ -225,23 +223,17 @@ export class FilterUserGrantsComponent extends FilterComponent implements OnInit
public setMethod(query: any, event: any) {
(query as UserNameQuery).setMethod(event.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);
this.filterCount$.next(this.filterCount);
}
public resetFilter(): void {
this.searchQueries = [];
this.emitFilter();
}
public get filterCount(): number {
return this.searchQueries.length;
}
}
@@ -1,8 +1,12 @@
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()">
<cnsl-filter (resetted)="resetFilter()" (trigger)="emitFilter()" [queryCount]="searchQueries.length">
<div class="filter-row" id="filtercomp">
<div class="name-query">
<mat-checkbox id="state" class="cb" [checked]="getSubFilter(SubQuery.STATE)"
(change)="changeCheckbox(SubQuery.STATE, $event )">{{'FILTER.STATE' | translate}}
<mat-checkbox
id="state"
class="cb"
[checked]="getSubFilter(SubQuery.STATE)"
(change)="changeCheckbox(SubQuery.STATE, $event)"
>{{ 'FILTER.STATE' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.STATE) as sq">
<span class="nomethod cnsl-secondary-text">
@@ -10,9 +14,9 @@
</span>
<cnsl-form-field class="filter-select-value" appearance="outline">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE,sq, $event)">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
<mat-option *ngFor="let state of states" [value]="state">
{{ 'USER.STATE.'+state | translate }}
{{ 'USER.STATE.' + state | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -20,63 +24,77 @@
</div>
<div class="name-query">
<mat-checkbox id="displayname" class="cb" [checked]="getSubFilter(SubQuery.DISPLAYNAME)"
(change)="changeCheckbox(SubQuery.DISPLAYNAME, $event )">{{'FILTER.DISPLAYNAME' | translate}}
<mat-checkbox
id="displayname"
class="cb"
[checked]="getSubFilter(SubQuery.DISPLAYNAME)"
(change)="changeCheckbox(SubQuery.DISPLAYNAME, $event)"
>{{ 'FILTER.DISPLAYNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.DISPLAYNAME) as dnq">
<cnsl-form-field class="filter-select-method" appearance="outline">
<mat-select [value]="dnq.getMethod()" (selectionChange)="setMethod(dnq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.'+method | translate }}
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value" appearance="outline">
<input cnslInput name="value" [value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)" />
<input
cnslInput
name="value"
[value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)"
/>
</cnsl-form-field>
</div>
</div>
<div class="email-query">
<mat-checkbox id="email" class="cb" [checked]="getSubFilter(SubQuery.EMAIL)"
(change)="changeCheckbox(SubQuery.EMAIL, $event )">{{'FILTER.EMAIL' | translate}}
<mat-checkbox
id="email"
class="cb"
[checked]="getSubFilter(SubQuery.EMAIL)"
(change)="changeCheckbox(SubQuery.EMAIL, $event)"
>{{ 'FILTER.EMAIL' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.EMAIL) as eq">
<cnsl-form-field class="filter-select-method" appearance="outline">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.'+method | translate}}
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value" appearance="outline">
<input cnslInput name="value" [value]="eq.getEmailAddress()"
(change)="setValue(SubQuery.EMAIL, eq, $event)" />
<input cnslInput name="value" [value]="eq.getEmailAddress()" (change)="setValue(SubQuery.EMAIL, eq, $event)" />
</cnsl-form-field>
</div>
</div>
<div class="usernane-query">
<mat-checkbox id="username" class="cb" [checked]="getSubFilter(SubQuery.USERNAME)"
(change)="changeCheckbox(SubQuery.USERNAME, $event )">{{'FILTER.USERNAME' | translate}}
<mat-checkbox
id="username"
class="cb"
[checked]="getSubFilter(SubQuery.USERNAME)"
(change)="changeCheckbox(SubQuery.USERNAME, $event)"
>{{ 'FILTER.USERNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.USERNAME) as unq">
<cnsl-form-field class="filter-select-method" appearance="outline">
<mat-select [value]="unq.getMethod()" (selectionChange)="setMethod(unq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.'+method | translate}}
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value" appearance="outline">
<input cnslInput name="value" [value]="unq.getUserName()"
(change)="setValue(SubQuery.USERNAME, unq, $event)" />
<input cnslInput name="value" [value]="unq.getUserName()" (change)="setValue(SubQuery.USERNAME, unq, $event)" />
</cnsl-form-field>
</div>
</div>
</div>
</cnsl-filter>
</cnsl-filter>
@@ -91,7 +91,7 @@ export class FilterUserComponent extends FilterComponent implements OnInit {
});
this.searchQueries = userQueries.filter((q) => q !== undefined) as UserSearchQuery[];
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
// this.showFilter = true;
// this.filterOpen.emit(true);
}
@@ -181,23 +181,21 @@ export class FilterUserComponent extends FilterComponent implements OnInit {
switch (subquery) {
case SubQuery.STATE:
(query as StateQuery).setState(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.DISPLAYNAME:
(query as DisplayNameQuery).setDisplayName(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.EMAIL:
(query as EmailQuery).setEmailAddress(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
case SubQuery.USERNAME:
(query as UserNameQuery).setUserName(value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
break;
}
this.filterCount$.next(this.filterCount);
}
public getSubFilter(subquery: SubQuery): any {
@@ -235,23 +233,17 @@ export class FilterUserComponent extends FilterComponent implements OnInit {
public setMethod(query: any, event: any) {
(query as UserNameQuery).setMethod(event.value);
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
}
public emitFilter(): void {
this.filterChanged.emit(this.filterCount ? this.searchQueries : undefined);
this.filterChanged.emit(this.searchQueries ? this.searchQueries : []);
this.showFilter = false;
this.filterOpen.emit(false);
this.filterCount$.next(this.filterCount);
}
public resetFilter(): void {
this.searchQueries = [];
this.emitFilter();
}
public get filterCount(): number {
return this.searchQueries.length;
}
}
@@ -1,25 +1,37 @@
<div class="cnsl-filter-button-wrapper">
<button mat-stroked-button class="filter-toggle cnsl-action-button" (click)="toggleFilter()" cdkOverlayOrigin
#trigger="cdkOverlayOrigin">
<button
mat-stroked-button
class="filter-toggle cnsl-action-button"
(click)="toggleFilter()"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
>
<i class="las la-filter no-margin"></i>
<span>{{ 'ACTIONS.FILTER' | translate }}</span>
<span *ngIf="(filterCount$ | async)" class="filter-count">{{filterCount$ | async}}</span>
<span *ngIf="queryCount" class="filter-count">{{ queryCount }}</span>
<cnsl-action-keys [doNotUseContrast]="true" [type]="ActionKeysType.FILTER" (actionTriggered)="toggleFilter()">
</cnsl-action-keys>
</button>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayHasBackdrop]="true" [flexibleDimensions]="true"
[lockPosition]="true" [cdkConnectedOverlayOffsetY]="10" [cdkConnectedOverlayPositions]="positions"
[cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="showFilter"
cdkConnectedOverlayBackdropClass="transparent-backdrop" (backdropClick)="showFilter= false"
(detach)="showFilter = false">
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayHasBackdrop]="true"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayPositions]="positions"
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="showFilter"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
(backdropClick)="showFilter = false"
(detach)="showFilter = false"
>
<div class="cnsl-filter-wrapper" cdkTrapFocus>
<div class="filter-top">
<button mat-stroked-button (click)="resetted.emit()">{{'ACTIONS.RESET' |
translate}}</button>
<span class="filter-middle">{{'FILTER.TITLE' | translate}}</span>
<button mat-raised-button color="primary" (click)="emitFilter()">{{'ACTIONS.FINISH' | translate}}</button>
<button mat-stroked-button (click)="resetted.emit()">{{ 'ACTIONS.RESET' | translate }}</button>
<span class="filter-middle">{{ 'FILTER.TITLE' | translate }}</span>
<button mat-raised-button color="primary" (click)="emitFilter()">{{ 'ACTIONS.FINISH' | translate }}</button>
</div>
<ng-content></ng-content>
</div>
</ng-template>
</div>
</div>
@@ -1,7 +1,7 @@
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
import { Component, EventEmitter, OnDestroy, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
import { Observable, Subject, takeUntil } from 'rxjs';
import { SearchQuery as MemberSearchQuery } from 'src/app/proto/generated/zitadel/member_pb';
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
import { OrgQuery } from 'src/app/proto/generated/zitadel/org_pb';
@@ -24,15 +24,16 @@ type FilterSearchQueryAsObject =
styleUrls: ['./filter.component.scss'],
})
export class FilterComponent implements OnDestroy {
@Output() public filterChanged: EventEmitter<FilterSearchQuery[] | undefined> = new EventEmitter();
@Output() public filterChanged: EventEmitter<FilterSearchQuery[]> = new EventEmitter();
@Output() public filterOpen: EventEmitter<boolean> = new EventEmitter<boolean>(false);
@Output() public resetted: EventEmitter<void> = new EventEmitter();
@Output() public trigger: EventEmitter<void> = new EventEmitter();
private destroy$: Subject<void> = new Subject();
@Input() public queryCount: number = 0;
public filterCount$: BehaviorSubject<number> = new BehaviorSubject(0);
private destroy$: Subject<void> = new Subject();
public filterChanged$: Observable<FilterSearchQuery[]> = this.filterChanged.asObservable();
public showFilter: boolean = false;
public methods: TextQueryMethod[] = [
@@ -59,7 +60,6 @@ export class FilterComponent implements OnDestroy {
}
public ngOnDestroy(): void {
this.filterCount$.complete();
this.destroy$.next();
this.destroy$.complete();
}
@@ -83,6 +83,7 @@ export class FilterComponent implements OnDestroy {
queryParams: {
['filter']: JSON.stringify(filters),
},
replaceUrl: true,
queryParamsHandling: 'merge',
skipLocationChange: false,
});
@@ -68,9 +68,16 @@ export class HeaderComponent implements OnDestroy {
}
public get isOnInstance(): boolean {
return (
['/instance', '/views', '/orgs', '/settings', '/failed-events', '/instance/members'].includes(this.router.url) ||
this.router.url.includes('/settings')
);
const pages: string[] = [
'/instance',
'/settings',
'/views',
'/orgs',
'/settings',
'/failed-events',
'/instance/members',
];
return pages.findIndex((p) => this.router.url.includes(p)) > -1;
}
}
@@ -1,6 +1,11 @@
<cnsl-refresh-table *ngIf="dataSource" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
[timestamp]="dataSource.viewTimestamp" [selection]="selection" [loading]="dataSource?.loading$ | async">
<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()">
<ng-content select="[selectactions]"></ng-content>
</ng-container>
@@ -13,17 +18,32 @@
<table mat-table class="table" aria-label="Elements" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox [disabled]="!canWrite" color="primary" (change)="$event ? masterToggle() : null"
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
[indeterminate]="selection.hasValue() && !isAllSelected()"
>
</mat-checkbox>
</th>
<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)">
<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">
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
>
<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"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="row.preferredLoginName" [isMachine]="true">
@@ -35,27 +55,30 @@
</ng-container>
<ng-container matColumnDef="userId">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERID' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.MEMBER.USERID' | translate }}</th>
<td mat-cell *matCellDef="let member">
{{member.userId}} </td>
{{ member.userId }}
</td>
</ng-container>
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.DISPLAYNAME' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.MEMBER.DISPLAYNAME' | translate }}</th>
<td mat-cell *matCellDef="let member">
{{member.displayName}} </td>
{{ member.displayName }}
</td>
</ng-container>
<ng-container matColumnDef="loginname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LOGINNAME' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.MEMBER.LOGINNAME' | translate }}</th>
<td mat-cell *matCellDef="let member">
{{member.preferredLoginName}} </td>
{{ member.preferredLoginName }}
</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.MEMBER.EMAIL' | translate }}</th>
<td mat-cell *matCellDef="let member">
{{member.email}}
{{ member.email }}
</td>
</ng-container>
@@ -63,24 +86,36 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let member" class="member-action-tr">
<cnsl-table-actions [hasActions]="true">
<button actions matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn"
(click)="triggerDeleteMember(member)" mat-icon-button><i class="las la-trash"></i></button>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="$event.stopPropagation(); triggerDeleteMember(member)"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
<button menuActions mat-menu-item [routerLink]="['/users', member.userId]">
{{'ACTIONS.TABLE.SHOWUSER' | translate : ({value: member.displayName})}}
{{ 'ACTIONS.TABLE.SHOWUSER' | translate: { value: member.displayName } }}
</button>
</cnsl-table-actions>
</td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef class="role-row"> {{ 'ROLESLABEL' | translate }} </th>
<th mat-header-cell *matHeaderCellDef class="role-row">{{ 'ROLESLABEL' | translate }}</th>
<td mat-cell *matCellDef="let member" class="role-row">
<mat-chip-list class="cnsl-chip-list" aria-label="role selection">
<mat-chip class="cnsl-chip" *ngFor="let role of member.rolesList" [removable]="true" [selectable]="false"
(removed)="removeRole(member, role)">
<mat-chip
class="cnsl-chip"
*ngFor="let role of member.rolesList"
[removable]="true"
[selectable]="false"
(removed)="removeRole(member, role)"
>
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span>{{role | roletransform}}</span>
<span>{{ role | roletransform }}</span>
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
@@ -90,13 +125,23 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight pointer" (click)="addRole(member)" mat-row
*matRowDef="let member; columns: displayedColumns;">
</tr>
<tr
class="highlight pointer"
(click)="addRole(member)"
mat-row
*matRowDef="let member; columns: displayedColumns"
></tr>
</table>
</div>
<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
*ngIf="dataSource"
class="paginator"
#paginator
[timestamp]="dataSource?.viewTimestamp"
[pageSize]="INITIALPAGESIZE"
[length]="dataSource.totalResult"
[pageSizeOptions]="[25, 50, 100, 250]"
(page)="changePage($event)"
>
</cnsl-paginator>
</cnsl-refresh-table>
</cnsl-refresh-table>
@@ -15,7 +15,14 @@
</a>
</ng-template>
<table [dataSource]="dataSource" mat-table class="table" aria-label="Elements">
<table
[dataSource]="dataSource"
mat-table
class="table"
aria-label="Organizations"
matSort
(matSortChange)="sortChange($event)"
>
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.ACTIVE' | translate }}
@@ -51,6 +58,7 @@
</ng-container>
<ng-container matColumnDef="name">
<!-- mat-sort-header -->
<th mat-header-cell *matHeaderCellDef>
{{ 'ORG.PAGES.NAME' | translate }}
</th>
@@ -1,9 +1,11 @@
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { Component, Input, ViewChild } from '@angular/core';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, catchError, finalize, from, map, Observable, of, Subject, switchMap, takeUntil } from 'rxjs';
import { Org, OrgQuery, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { Org, OrgFieldName, OrgQuery, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
@@ -38,6 +40,7 @@ export class OrgTableComponent {
public filterOpen: boolean = false;
public OrgState: any = OrgState;
public copied: string = '';
@ViewChild(MatSort) public sort!: MatSort;
private searchQueries: OrgQuery[] = [];
private destroy$: Subject<void> = new Subject();
@@ -48,7 +51,12 @@ export class OrgTableComponent {
});
private requestOrgsObservable$ = this.requestOrgs$.pipe(takeUntil(this.destroy$));
constructor(private authService: GrpcAuthService, private router: Router, private toast: ToastService) {
constructor(
private authService: GrpcAuthService,
private router: Router,
private toast: ToastService,
private _liveAnnouncer: LiveAnnouncer,
) {
this.requestOrgs$.next({ limit: this.initialLimit, offset: 0, queries: this.searchQueries });
this.authService.getActiveOrg().then((org) => (this.activeOrg = org));
@@ -60,7 +68,17 @@ export class OrgTableComponent {
public loadOrgs(request: Request): Observable<Org.AsObject[]> {
this.loadingSubject.next(true);
return from(this.authService.listMyProjectOrgs(request.limit, request.offset, request.queries)).pipe(
let sortingField: OrgFieldName | undefined = undefined;
if (this.sort?.active && this.sort?.direction)
switch (this.sort.active) {
case 'name':
sortingField = OrgFieldName.ORG_FIELD_NAME_NAME;
break;
}
return from(
this.authService.listMyProjectOrgs(request.limit, request.offset, request.queries, sortingField, this.sort?.direction),
).pipe(
map((resp) => {
this.timestamp = resp.details?.viewTimestamp;
this.totalResult = resp.details?.totalResult ?? 0;
@@ -86,6 +104,15 @@ export class OrgTableComponent {
});
}
public sortChange(sortState: Sort) {
if (sortState.direction && sortState.active) {
this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`);
this.refresh();
} else {
this._liveAnnouncer.announce('Sorting cleared');
}
}
public applySearchQuery(searchQueries: OrgQuery[]): void {
this.searchQueries = searchQueries;
this.requestOrgs$.next({
@@ -0,0 +1,52 @@
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<h2>{{ 'POLICY.DOMAIN_POLICY.TITLE' | translate }}</h2>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<!-- <ng-template cnslHasRole [hasRole]="['domain.policy.delete']"> -->
<button
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
<!-- </ng-template> -->
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="content" *ngIf="domainData">
<div class="row">
<mat-checkbox color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="domainData.userLoginMustBeDomain">
{{ 'POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate }}
</mat-checkbox>
</div>
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="row">
<mat-checkbox color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="domainData.validateOrgDomains">
{{ 'POLICY.DATA.VALIDATEORGDOMAINS' | translate }}
</mat-checkbox>
</div>
<div class="row">
<mat-checkbox
color="primary"
name="hasNumber"
ngDefaultControl
[(ngModel)]="domainData.smtpSenderAddressMatchesInstanceDomain"
>
{{ 'POLICY.DATA.SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN' | translate }}
</mat-checkbox>
</div>
</div>
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DomainPolicyComponent } from './domain-policy.component';
describe('DomainPolicyComponent', () => {
let component: DomainPolicyComponent;
let fixture: ComponentFixture<DomainPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [DomainPolicyComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DomainPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,32 +1,43 @@
import { Component, Injector, Input, OnDestroy, OnInit, Type } from '@angular/core';
import { Subscription } from 'rxjs';
import { GetCustomOrgIAMPolicyResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddCustomDomainPolicyRequest,
GetCustomOrgIAMPolicyResponse,
UpdateDomainPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { GetOrgIAMPolicyResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { OrgIAMPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { DomainPolicy, OrgIAMPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageLocation, StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'cnsl-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'],
selector: 'cnsl-domain-policy',
templateUrl: './domain-policy.component.html',
styleUrls: ['./domain-policy.component.scss'],
})
export class OrgIamPolicyComponent implements OnInit, OnDestroy {
export class DomainPolicyComponent implements OnInit, OnDestroy {
private managementService!: ManagementService;
@Input() public serviceType!: PolicyComponentServiceType;
public iamData!: OrgIAMPolicy.AsObject;
public domainData!: DomainPolicy.AsObject;
public loading: boolean = false;
private sub: Subscription = new Subscription();
private org!: Org.AsObject;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(private toast: ToastService, private injector: Injector, private adminService: AdminService) {}
constructor(
private toast: ToastService,
private injector: Injector,
private adminService: AdminService,
private storageService: StorageService,
) {}
ngOnInit(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
@@ -40,22 +51,32 @@ export class OrgIamPolicyComponent implements OnInit, OnDestroy {
}
public fetchData(): void {
this.getData().then((resp) => {
if (resp?.policy) {
this.iamData = resp.policy;
}
});
this.loading = true;
this.getData()
.then((resp) => {
this.loading = false;
if (resp?.policy) {
this.domainData = resp.policy;
}
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | any> {
const org: Org.AsObject | null = this.storageService.getItem('organization', StorageLocation.session);
if (org?.id) {
this.org = org;
}
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
return this.managementService.getDomainPolicy();
case PolicyComponentServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
break;
return this.adminService.getCustomDomainPolicy(this.org.id);
default:
return Promise.reject();
}
@@ -64,23 +85,33 @@ export class OrgIamPolicyComponent implements OnInit, OnDestroy {
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.iamData as OrgIAMPolicy.AsObject).isDefault) {
if ((this.domainData as OrgIAMPolicy.AsObject).isDefault) {
const req = new AddCustomDomainPolicyRequest();
req.setOrgId(this.org.id);
req.setUserLoginMustBeDomain(this.domainData.userLoginMustBeDomain);
req.setValidateOrgDomains(this.domainData.validateOrgDomains);
req.setSmtpSenderAddressMatchesInstanceDomain(this.domainData.smtpSenderAddressMatchesInstanceDomain);
this.adminService
.addCustomOrgIAMPolicy(this.org.id, this.iamData.userLoginMustBeDomain)
.addCustomDomainPolicy(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
this.fetchData();
})
.catch((error) => {
this.toast.showError(error);
});
break;
} else {
const req = new AddCustomDomainPolicyRequest();
req.setOrgId(this.org.id);
req.setUserLoginMustBeDomain(this.domainData.userLoginMustBeDomain);
req.setValidateOrgDomains(this.domainData.validateOrgDomains);
req.setSmtpSenderAddressMatchesInstanceDomain(this.domainData.smtpSenderAddressMatchesInstanceDomain);
this.adminService
.updateCustomOrgIAMPolicy(this.org.id, this.iamData.userLoginMustBeDomain)
.updateCustomDomainPolicy(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
this.fetchData();
})
.catch((error) => {
this.toast.showError(error);
@@ -88,12 +119,15 @@ export class OrgIamPolicyComponent implements OnInit, OnDestroy {
break;
}
case PolicyComponentServiceType.ADMIN:
// update Default org iam policy?
const req = new UpdateDomainPolicyRequest();
req.setUserLoginMustBeDomain(this.domainData.userLoginMustBeDomain);
req.setValidateOrgDomains(this.domainData.validateOrgDomains);
req.setSmtpSenderAddressMatchesInstanceDomain(this.domainData.smtpSenderAddressMatchesInstanceDomain);
this.adminService
.updateOrgIAMPolicy(this.iamData.userLoginMustBeDomain)
.updateDomainPolicy(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
this.fetchData();
})
.catch((error) => {
this.toast.showError(error);
@@ -105,7 +139,7 @@ export class OrgIamPolicyComponent implements OnInit, OnDestroy {
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.adminService
.resetCustomOrgIAMPolicyToDefault(this.org.id)
.resetCustomDomainPolicyToDefault(this.org.id)
.then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
@@ -119,8 +153,8 @@ export class OrgIamPolicyComponent implements OnInit, OnDestroy {
}
public get isDefault(): boolean {
if (this.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.iamData as OrgIAMPolicy.AsObject).isDefault;
if (this.domainData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.domainData as OrgIAMPolicy.AsObject).isDefault;
} else {
return false;
}
@@ -2,8 +2,9 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
@@ -13,27 +14,26 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { CardModule } from '../../card/card.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { OrgIamPolicyRoutingModule } from './org-iam-policy-routing.module';
import { OrgIamPolicyComponent } from './org-iam-policy.component';
import { DomainPolicyComponent } from './domain-policy.component';
@NgModule({
declarations: [OrgIamPolicyComponent],
declarations: [DomainPolicyComponent],
imports: [
OrgIamPolicyRoutingModule,
CommonModule,
FormsModule,
CardModule,
InputModule,
MatButtonModule,
HasRolePipeModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatProgressSpinnerModule,
MatTooltipModule,
InfoSectionModule,
MatCheckboxModule,
TranslateModule,
DetailLayoutModule,
],
exports: [OrgIamPolicyComponent],
exports: [DomainPolicyComponent],
})
export class OrgIamPolicyModule {}
export class DomainPolicyModule {}
@@ -45,15 +45,9 @@
<p class="cnsl-secondary-text">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
<div *ngIf="loginData" class="login-policy-row">
<mat-slide-toggle
card-actions
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.forceMfa"
>
<mat-checkbox card-actions class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.forceMfa">
{{ 'POLICY.DATA.FORCEMFA' | translate }}
</mat-slide-toggle>
</mat-checkbox>
</div>
<cnsl-card class="max-card-width">
<cnsl-mfa-table
@@ -77,11 +71,60 @@
<br />
<h2>{{ 'POLICY.LOGIN_POLICY.LIFETIMEDURATIONS' | translate }}</h2>
<form class="lifetime-form" (ngSubmit)="savePolicy()" [formGroup]="lifetimeForm" autocomplete="off">
<cnsl-form-field class="lifetime-form-field" label="Password Check Lifetime" required="true">
<cnsl-label
>{{ 'POLICY.DATA.PASSWORDCHECKLIFETIME' | translate }}&nbsp;<strong
>({{ 'POLICY.DATA.INHOURS' | translate }})</strong
></cnsl-label
>
<input cnslInput type="number" name="passwordCheckLifetime" formControlName="passwordCheckLifetime" />
</cnsl-form-field>
<cnsl-form-field class="lifetime-form-field" label="external Login Check Lifetime" required="true">
<cnsl-label
>{{ 'POLICY.DATA.EXTERNALLOGINCHECKLIFETIME' | translate }}&nbsp;<strong
>({{ 'POLICY.DATA.INHOURS' | translate }})</strong
></cnsl-label
>
<input cnslInput type="number" name="externalLoginCheckLifetime" formControlName="externalLoginCheckLifetime" />
</cnsl-form-field>
<cnsl-form-field class="lifetime-form-field" label="MFA Init Skip Lifetime" required="true">
<cnsl-label
>{{ 'POLICY.DATA.MFAINITSKIPLIFETIME' | translate }}&nbsp;<strong
>({{ 'POLICY.DATA.INHOURS' | translate }})</strong
></cnsl-label
>
<input cnslInput type="number" name="mfaInitSkipLifetime" formControlName="mfaInitSkipLifetime" />
</cnsl-form-field>
<cnsl-form-field class="lifetime-form-field" label="Second Factor Check Lifetime" required="true">
<cnsl-label
>{{ 'POLICY.DATA.SECONDFACTORCHECKLIFETIME' | translate }}&nbsp;
<strong>({{ 'POLICY.DATA.INHOURS' | translate }})</strong></cnsl-label
>
<input cnslInput type="number" name="secondFactorCheckLifetime" formControlName="secondFactorCheckLifetime" />
</cnsl-form-field>
<cnsl-form-field class="lifetime-form-field" label="Multi Factor Check Lifetime" required="true">
<cnsl-label
>{{ 'POLICY.DATA.MULTIFACTORCHECKLIFETIME' | translate }}&nbsp;
<strong>({{ 'POLICY.DATA.INHOURS' | translate }})</strong></cnsl-label
>
<input cnslInput type="number" name="multiFactorCheckLifetime" formControlName="multiFactorCheckLifetime" />
</cnsl-form-field>
</form>
<br />
<h2>{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}</h2>
<cnsl-card class="max-card-width login-policy-content" *ngIf="loginData">
<div class="max-card-width login-policy-content" *ngIf="loginData">
<div class="login-policy-row">
<mat-slide-toggle
<mat-checkbox
class="login-policy-toggle"
color="primary"
matTooltip="{{ 'POLICY.DATA.FORCEMFA_DESC' | translate }}"
@@ -89,16 +132,16 @@
[(ngModel)]="loginData.allowUsernamePassword"
>
{{ 'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate }}
</mat-slide-toggle>
</mat-checkbox>
<!-- <cnsl-info-section class="info">
{{ 'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate }}
</cnsl-info-section> -->
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowRegister">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{ 'POLICY.DATA.ALLOWREGISTER' | translate }}
</mat-slide-toggle>
</mat-checkbox>
<!-- <ng-template #regInfo>
<cnsl-info-section class="info">
@@ -107,9 +150,9 @@
</ng-template> -->
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowExternalIdp">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowExternalIdp">
{{ 'POLICY.DATA.ALLOWEXTERNALIDP' | translate }}
</mat-slide-toggle>
</mat-checkbox>
<!-- <ng-template #idpInfo>
<cnsl-info-section class="info">
@@ -119,9 +162,9 @@
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.hidePasswordReset">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.hidePasswordReset">
{{ 'POLICY.DATA.HIDEPASSWORDRESET' | translate }}
</mat-slide-toggle>
</mat-checkbox>
<!-- <ng-template #passwordResetInfo>
<cnsl-info-section class="info">
@@ -131,9 +174,14 @@
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.ignoreUnknownUsernames">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.ignoreUnknownUsernames"
>
{{ 'POLICY.DATA.IGNOREUNKNOWNUSERNAMES' | translate }}
</mat-slide-toggle>
</mat-checkbox>
</div>
<div class="login-policy-row">
@@ -142,19 +190,13 @@
<input cnslInput placeholder="https://" [(ngModel)]="loginData.defaultRedirectUri" />
</cnsl-form-field>
</div>
</cnsl-card>
<div class="login-policy-btn-container">
<button class="login-policy-save-button" (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
*ngIf="!isDefault"
color="primary"
class="loginpolicy-reset-button"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
@@ -164,3 +206,11 @@
</button>
</ng-template>
</ng-container>
<br />
<div class="login-policy-btn-container">
<button class="login-policy-save-button" (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
@@ -12,6 +12,20 @@
display: block;
}
.lifetime-form {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
column-gap: 1rem;
@media only screen and (max-width: 950px) {
grid-template-columns: 1fr 1fr;
}
@media only screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
}
.login-policy-content {
.login-policy-row {
padding-bottom: 0.5rem;
@@ -26,6 +40,11 @@
}
}
.loginpolicy-reset-button {
margin: 1rem 0;
display: block;
}
.login-policy-btn-container {
display: flex;
justify-content: flex-start;
@@ -1,4 +1,6 @@
import { Component, Injector, Input, OnInit, Type } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest,
@@ -37,16 +39,54 @@ export class LoginPolicyComponent implements OnInit {
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
public PasswordlessType: any = PasswordlessType;
constructor(private toast: ToastService, private injector: Injector) {}
public lifetimeForm!: FormGroup;
constructor(private toast: ToastService, private injector: Injector, private fb: FormBuilder) {
this.lifetimeForm = this.fb.group({
passwordCheckLifetime: [240, [Validators.required]],
externalLoginCheckLifetime: [12, [Validators.required]],
mfaInitSkipLifetime: [720, [Validators.required]],
secondFactorCheckLifetime: [12, [Validators.required]],
multiFactorCheckLifetime: [12, [Validators.required]],
});
}
private fetchData(): void {
this.getData().then((resp) => {
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
}
});
this.getData()
.then((resp) => {
console.log(resp);
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
this.passwordCheckLifetime?.setValue(
this.loginData.passwordCheckLifetime?.seconds ? this.loginData.passwordCheckLifetime?.seconds / 60 / 60 : 240,
);
this.externalLoginCheckLifetime?.setValue(
this.loginData.externalLoginCheckLifetime?.seconds
? this.loginData.externalLoginCheckLifetime?.seconds / 60 / 60
: 12,
);
this.mfaInitSkipLifetime?.setValue(
this.loginData.mfaInitSkipLifetime?.seconds ? this.loginData.mfaInitSkipLifetime?.seconds / 60 / 60 : 720,
);
this.secondFactorCheckLifetime?.setValue(
this.loginData.secondFactorCheckLifetime?.seconds
? this.loginData.secondFactorCheckLifetime?.seconds / 60 / 60
: 12,
);
this.multiFactorCheckLifetime?.setValue(
this.loginData.multiFactorCheckLifetime?.seconds
? this.loginData.multiFactorCheckLifetime?.seconds / 60 / 60
: 12,
);
}
})
.catch(this.toast.showError);
}
public ngOnInit(): void {
@@ -88,6 +128,22 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setForceMfa(this.loginData.forceMfa);
mgmtreq.setPasswordlessType(this.loginData.passwordlessType);
mgmtreq.setHidePasswordReset(this.loginData.hidePasswordReset);
const pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
mgmtreq.setPasswordCheckLifetime(pcl);
const elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setExternalLoginCheckLifetime(elcl);
const misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
mgmtreq.setMfaInitSkipLifetime(misl);
const sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setSecondFactorCheckLifetime(sfcl);
const mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
mgmtreq.setMultiFactorCheckLifetime(mficl);
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
@@ -107,6 +163,21 @@ export class LoginPolicyComponent implements OnInit {
adminreq.setForceMfa(this.loginData.forceMfa);
adminreq.setPasswordlessType(this.loginData.passwordlessType);
adminreq.setHidePasswordReset(this.loginData.hidePasswordReset);
const admin_pcl = new Duration().setSeconds((this.passwordCheckLifetime?.value ?? 240) * 60 * 60);
adminreq.setPasswordCheckLifetime(admin_pcl);
const admin_elcl = new Duration().setSeconds((this.externalLoginCheckLifetime?.value ?? 12) * 60 * 60);
adminreq.setExternalLoginCheckLifetime(admin_elcl);
const admin_misl = new Duration().setSeconds((this.mfaInitSkipLifetime?.value ?? 720) * 60 * 60);
adminreq.setMfaInitSkipLifetime(admin_misl);
const admin_sfcl = new Duration().setSeconds((this.secondFactorCheckLifetime?.value ?? 12) * 60 * 60);
adminreq.setSecondFactorCheckLifetime(admin_sfcl);
const admin_mficl = new Duration().setSeconds((this.multiFactorCheckLifetime?.value ?? 12) * 60 * 60);
adminreq.setMultiFactorCheckLifetime(admin_mficl);
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
// adminreq.setPasswordCheckLifetime(this.loginData.passwordCheckLifetime);
@@ -153,4 +224,24 @@ export class LoginPolicyComponent implements OnInit {
return false;
}
}
public get passwordCheckLifetime(): AbstractControl | null {
return this.lifetimeForm.get('passwordCheckLifetime');
}
public get externalLoginCheckLifetime(): AbstractControl | null {
return this.lifetimeForm.get('externalLoginCheckLifetime');
}
public get mfaInitSkipLifetime(): AbstractControl | null {
return this.lifetimeForm.get('mfaInitSkipLifetime');
}
public get secondFactorCheckLifetime(): AbstractControl | null {
return this.lifetimeForm.get('secondFactorCheckLifetime');
}
public get multiFactorCheckLifetime(): AbstractControl | null {
return this.lifetimeForm.get('multiFactorCheckLifetime');
}
}
@@ -1,7 +1,8 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
@@ -30,6 +31,7 @@ import { MfaTableComponent } from './mfa-table/mfa-table.component';
InfoSectionModule,
FormsModule,
CardModule,
ReactiveFormsModule,
InputModule,
MatIconModule,
MatButtonModule,
@@ -37,6 +39,7 @@ import { MfaTableComponent } from './mfa-table/mfa-table.component';
HasRoleModule,
MatDialogModule,
HasRolePipeModule,
MatCheckboxModule,
MatTooltipModule,
DetailLayoutModule,
MatProgressSpinnerModule,
@@ -1,12 +1,16 @@
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
<mat-spinner diameter="30" *ngIf="smtpLoading || smsProvidersLoading" color="primary"></mat-spinner>
</div>
<h2>{{ 'SETTING.SMTP.TITLE' | translate }}</h2>
<cnsl-info-section *ngIf="!form.valid" class="info-section-warn" [fitWidth]="true" [type]="InfoSectionType.ALERT">{{
'SETTING.SMTP.REQUIREDWARN' | translate
}}</cnsl-info-section>
<cnsl-info-section
*ngIf="!smtpLoading && !form.valid"
class="info-section-warn"
[fitWidth]="true"
[type]="InfoSectionType.ALERT"
>{{ 'SETTING.SMTP.REQUIREDWARN' | translate }}</cnsl-info-section
>
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
@@ -33,17 +37,9 @@
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" />
</cnsl-form-field>
<cnsl-form-field class="smtp-form-field" label="Password" required="true">
<cnsl-label>{{ 'SETTING.SMTP.PASSWORD' | translate }}</cnsl-label>
<input
id="smtp-password"
cnslInput
name="smtp-password"
autocomplete="smtp-password"
type="password"
formControlName="password"
/>
</cnsl-form-field>
<button class="set-password-btn" (click)="setSMTPPassword()" mat-stroked-button>
{{ 'SETTING.SMTP.SETPASSWORD' | translate }}
</button>
<div class="general-btn-container">
<button class="save-button" (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
@@ -18,6 +18,10 @@
margin: 1rem 0;
}
.set-password-btn {
margin-bottom: 1rem;
}
.general-btn-container {
display: flex;
justify-content: flex-start;
@@ -14,6 +14,7 @@ import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
import { SMTPPasswordDialogComponent } from './smtp-password-dialog/smtp-password-dialog.component';
@Component({
selector: 'cnsl-notification-settings',
@@ -25,7 +26,12 @@ export class NotificationSettingsComponent implements OnInit {
public smsProviders: SMSProvider.AsObject[] = [];
public logNotificationProvider!: DebugNotificationProvider.AsObject;
public fileNotificationProvider!: DebugNotificationProvider.AsObject;
public loading: boolean = false;
public smtpLoading: boolean = false;
public smsProvidersLoading: boolean = false;
public logProviderLoading: boolean = false;
public fileProviderLoading: boolean = false;
public form!: FormGroup;
public SMSProviderConfigState: any = SMSProviderConfigState;
@@ -45,7 +51,6 @@ export class NotificationSettingsComponent implements OnInit {
tls: [true, [Validators.required]],
host: ['', [Validators.required]],
user: ['', [Validators.required]],
password: ['', [Validators.required]],
});
}
@@ -54,47 +59,61 @@ export class NotificationSettingsComponent implements OnInit {
}
private fetchData(): void {
this.smtpLoading = true;
this.service
.getSMTPConfig()
.then((smtpConfig) => {
this.smtpLoading = false;
if (smtpConfig.smtpConfig) {
this.form.patchValue(smtpConfig.smtpConfig);
}
})
.catch((error) => {
this.smtpLoading = false;
if (error && error.code === 5) {
console.log(error);
}
});
this.service.listSMSProviders().then((smsProviders) => {
if (smsProviders.resultList) {
this.smsProviders = smsProviders.resultList;
console.log(this.smsProviders);
}
});
this.smsProvidersLoading = true;
this.service
.listSMSProviders()
.then((smsProviders) => {
this.smsProvidersLoading = false;
if (smsProviders.resultList) {
this.smsProviders = smsProviders.resultList;
}
})
.catch((error) => {
this.smsProvidersLoading = false;
this.toast.showError(error);
});
this.logProviderLoading = true;
this.service
.getLogNotificationProvider()
.then((logNotificationProvider) => {
this.logProviderLoading = false;
if (logNotificationProvider.provider) {
this.logNotificationProvider = logNotificationProvider.provider;
}
})
.catch((error) => {
this.logProviderLoading = false;
this.toast.showError(error);
});
this.fileProviderLoading = true;
this.service
.getFileSystemNotificationProvider()
.then((fileNotificationProvider) => {
this.fileProviderLoading = false;
if (fileNotificationProvider.provider) {
console.log(fileNotificationProvider);
this.fileNotificationProvider = fileNotificationProvider.provider;
}
})
.catch((error) => {
console.log('hehe');
this.fileProviderLoading = false;
this.toast.showError(error);
});
}
@@ -107,16 +126,7 @@ export class NotificationSettingsComponent implements OnInit {
req.setTls(this.tls?.value ?? false);
req.setUser(this.user?.value ?? '');
return this.service.updateSMTPConfig(req).then(() => {
let passwordReq: UpdateSMTPConfigPasswordRequest;
if (this.password) {
passwordReq = new UpdateSMTPConfigPasswordRequest();
passwordReq.setPassword(this.password.value);
return this.service.updateSMTPConfigPassword(passwordReq);
} else {
return;
}
});
return this.service.updateSMTPConfig(req).catch(this.toast.showError);
}
public savePolicy(): void {
@@ -125,7 +135,6 @@ export class NotificationSettingsComponent implements OnInit {
prom
.then(() => {
this.toast.showInfo('SETTING.SMTP.SAVED', true);
this.loading = true;
setTimeout(() => {
this.fetchData();
}, 2000);
@@ -155,6 +164,28 @@ export class NotificationSettingsComponent implements OnInit {
});
}
public setSMTPPassword(): void {
const dialogRef = this.dialog.open(SMTPPasswordDialogComponent, {
width: '400px',
});
dialogRef.afterClosed().subscribe((password: string) => {
if (password) {
const passwordReq = new UpdateSMTPConfigPasswordRequest();
passwordReq.setPassword(password);
this.service
.updateSMTPConfigPassword(passwordReq)
.then(() => {
this.toast.showInfo('SETTING.SMTP.PASSWORDSET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
public get twilio(): SMSProvider.AsObject | undefined {
return this.smsProviders.find((p) => p.twilio);
}
@@ -178,8 +209,4 @@ export class NotificationSettingsComponent implements OnInit {
public get host(): AbstractControl | null {
return this.form.get('host');
}
public get password(): AbstractControl | null {
return this.form.get('password');
}
}
@@ -14,9 +14,10 @@ import { InfoSectionModule } from '../../info-section/info-section.module';
import { InputModule } from '../../input/input.module';
import { DialogAddSMSProviderComponent } from './dialog-add-sms-provider/dialog-add-sms-provider.component';
import { NotificationSettingsComponent } from './notification-settings.component';
import { SMTPPasswordDialogComponent } from './smtp-password-dialog/smtp-password-dialog.component';
@NgModule({
declarations: [NotificationSettingsComponent, DialogAddSMSProviderComponent],
declarations: [NotificationSettingsComponent, DialogAddSMSProviderComponent, SMTPPasswordDialogComponent],
imports: [
CommonModule,
CardModule,
@@ -0,0 +1,25 @@
<h1 mat-dialog-title>
<span>{{ 'SETTING.SMTP.SETPASSWORD' | translate }} {{ data?.number }}</span>
</h1>
<div mat-dialog-content>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'SETTING.SMTP.PASSWORD' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="password" />
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
<button
[disabled]="!password"
cdkFocusInitial
color="primary"
mat-raised-button
class="ok-button"
(click)="closeDialog(password)"
>
{{ 'ACTIONS.OK' | translate }}
</button>
</div>
@@ -0,0 +1,25 @@
h1 {
font-size: 1.5rem;
margin: 0;
}
.desc {
font-size: 14px;
}
.formfield {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { SMTPPasswordDialogComponent } from './smtp-password-dialog.component';
describe('CodeDialogComponent', () => {
let component: SMTPPasswordDialogComponent;
let fixture: ComponentFixture<SMTPPasswordDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [SMTPPasswordDialogComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SMTPPasswordDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,16 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'cnsl-smtp-password-dialog',
templateUrl: './smtp-password-dialog.component.html',
styleUrls: ['./smtp-password-dialog.component.scss'],
})
export class SMTPPasswordDialogComponent {
public password: string = '';
constructor(public dialogRef: MatDialogRef<SMTPPasswordDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {}
closeDialog(password: string = ''): void {
this.dialogRef.close(password);
}
}
@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { OrgIamPolicyComponent } from './org-iam-policy.component';
const routes: Routes = [
{
path: '',
component: OrgIamPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrgIamPolicyRoutingModule { }
@@ -1,41 +0,0 @@
<h2>{{ 'POLICY.IAM_POLICY.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.IAM_POLICY.DESCRIPTION' | translate }}</p>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<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 }}
</button>
</ng-template>
<div class="content" *ngIf="iamData">
<div class="row">
<mat-slide-toggle
color="primary"
name="hasNumber"
ngDefaultControl
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
[(ngModel)]="iamData.userLoginMustBeDomain"
>
{{ 'POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate }}
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">
<button
(click)="savePolicy()"
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { OrgIamPolicyComponent } from './org-iam-policy.component';
describe('OrgIamPolicyComponent', () => {
let component: OrgIamPolicyComponent;
let fixture: ComponentFixture<OrgIamPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OrgIamPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OrgIamPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -40,7 +40,7 @@
</div>
</div>
<div class="row">
<mat-slide-toggle
<mat-checkbox
class="slide-toggle"
color="primary"
name="hasNumber"
@@ -52,10 +52,10 @@
<mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon>
<span class="left-desc">{{ 'POLICY.DATA.HASNUMBER' | translate }}</span>
</div>
</mat-slide-toggle>
</mat-checkbox>
</div>
<div class="row">
<mat-slide-toggle
<mat-checkbox
class="slide-toggle"
color="primary"
name="hasSymbol"
@@ -67,10 +67,10 @@
<mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon>
<span class="left-desc">{{ 'POLICY.DATA.HASSYMBOL' | translate }}</span>
</div>
</mat-slide-toggle>
</mat-checkbox>
</div>
<div class="row">
<mat-slide-toggle
<mat-checkbox
class="slide-toggle"
color="primary"
name="hasLowercase"
@@ -82,10 +82,10 @@
<mat-icon class="icon" svgIcon="mdi_format-letter-case-lower"></mat-icon>
<span class="left-desc">{{ 'POLICY.DATA.HASLOWERCASE' | translate }}</span>
</div>
</mat-slide-toggle>
</mat-checkbox>
</div>
<div class="row">
<mat-slide-toggle
<mat-checkbox
class="slide-toggle"
color="primary"
name="hasUppercase"
@@ -97,7 +97,7 @@
<mat-icon class="icon" svgIcon="mdi_format-letter-case-upper"></mat-icon>
<span class="left-desc">{{ 'POLICY.DATA.HASUPPERCASE' | translate }}</span>
</div>
</mat-slide-toggle>
</mat-checkbox>
</div>
</div>
</cnsl-card>
@@ -32,12 +32,12 @@
}
.slide-toggle {
margin-left: 1.75rem;
margin-left: 2.25rem;
.slide-toggle-row {
display: flex;
align-items: center;
margin-left: 1.5rem;
margin-left: 2.25rem;
.icon {
margin-right: 1rem;
@@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
@@ -25,10 +25,10 @@ import { PasswordComplexityPolicyComponent } from './password-complexity-policy.
FormsModule,
InputModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
MatCheckboxModule,
HasRolePipeModule,
TranslateModule,
DetailLayoutModule,
@@ -1,19 +1,31 @@
<div [attr.data-e2e]="'color'">
<p class="name cnsl-secondary-text">{{name}}</p>
<p class="name cnsl-secondary-text">{{ name }}</p>
<div class="wrapper">
<button [disabled]="disabled" class="color-shadow" [style.background-color]="previewColor"
(click)="isOpen = !isOpen" cdkOverlayOrigin #trigger="cdkOverlayOrigin"
matTooltip="{{'ACTIONS.SET' | translate}}"> </button>
<button
[disabled]="disabled"
class="color-shadow"
[style.background-color]="previewColor"
(click)="isOpen = !isOpen"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
matTooltip="{{ 'ACTIONS.SET' | translate }}"
></button>
<div class="hex-wrapper" [ngClass]="{'pointer': !disabled}" (click)="disabled ? null : (isOpen = !isOpen)">
<div class="hex-wrapper" [ngClass]="{ pointer: !disabled }" (click)="disabled ? null : (isOpen = !isOpen)">
<i class="las la-hashtag"></i>
<span class="hex">{{previewColorCropped}}</span>
<span class="hex">{{ previewColorCropped }}</span>
</div>
</div>
</div>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen"
(overlayOutsideClick)="isOpen = false">
<color-chrome class="picker" [color]="previewColor" (onChangeComplete)="changeComplete($event)"></color-chrome>
</ng-template>
<ng-template
cdkConnectedOverlay
(detach)="submitColor()"
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
(overlayOutsideClick)="isOpen = false"
>
<color-chrome class="picker" [color]="previewColor" (onChangeComplete)="changeComplete($event)"> </color-chrome>
<button class="close-icon" mat-mini-fab color="primary" (click)="submitColor()"><mat-icon>check</mat-icon></button>
</ng-template>
@@ -42,12 +42,20 @@
}
.picker {
margin: 1rem 0;
position: relative;
}
.close-icon {
position: absolute;
top: 0;
right: 0;
transform: translateX(50%) translateY(-50%);
}
// stylelint-disable
::ng-deep .chrome-picker {
border-radius: 0.5rem !important;
box-shadow: 0 0 1px #00000020, 0 1px 3px #00000020 !important;
}
::ng-deep .saturation {
@@ -123,7 +123,12 @@ export class ColorComponent implements OnInit {
}
public changeComplete(event: ColorEvent): void {
this.emitPreview(event.color.hex);
this.color = event.color.hex;
}
public submitColor(): void {
this.emitPreview(this.color);
this.isOpen = false;
}
public get previewColorCropped(): string {
@@ -1,17 +1,26 @@
@use '@angular/material' as mat;
@mixin preview-theme($theme) {
@import '../../../../../styles/input.scss';
@import '../../../label/label.component.scss';
@mixin preview-theme($show-dark) {
$theme: if($show-dark, $caos-dark-app-theme, $caos-light-app-theme);
@include input-theme($theme);
@include cnsl-label-theme($theme);
@include mat.all-component-themes($theme);
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$p-border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
.preview {
pointer-events: none;
border-radius: 0.5rem;
transform: scale(0.9);
color: map-get($foreground, base);
* {
pointer-events: none;
@@ -77,18 +86,6 @@
}
}
}
&.light {
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #000;
}
}
&.dark {
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #fff;
}
}
}
}
}
@@ -34,6 +34,10 @@
<mat-button-toggle [value]="View.PREVIEW">
<div class="toggle-row">
<span>{{ 'POLICY.PRIVATELABELING.VIEWS.PREVIEW' | translate }}</span>
<i
class="info-i las la-question-circle"
matTooltip="{{ 'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate }}"
></i>
<div *ngIf="view === View.PREVIEW" class="current-dot"></div>
</div>
</mat-button-toggle>
@@ -71,10 +75,6 @@
</button>
</div>
<cnsl-info-section *ngIf="view === View.PREVIEW" class="desc cnsl-secondary-text">
{{ 'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate }}
</cnsl-info-section>
<div *ngIf="previewData && data" class="lab-policy-content">
<mat-accordion class="settings">
<mat-expansion-panel class="expansion">
@@ -223,7 +223,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.BACKGROUNDDARK"
(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()"
(previewChanged)="previewData.backgroundColorDark !== $event ? setDarkBackgroundColorAndSave($event) : null"
name="Background Color"
[color]="data.backgroundColorDark"
[previewColor]="previewData.backgroundColorDark"
@@ -234,7 +234,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColorDark = $event; savePolicy()"
(previewChanged)="previewData.primaryColorDark !== $event ? setDarkPrimaryColorAndSave($event) : null"
name="Primary Color"
[color]="data.primaryColorDark"
[previewColor]="previewData.primaryColorDark"
@@ -246,7 +246,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.WARN"
(previewChanged)="previewData.warnColorDark = $event; savePolicy()"
(previewChanged)="previewData.warnColorDark !== $event ? setDarkWarnColorAndSave($event) : null"
name="Warn Color"
[color]="data.warnColorDark"
[previewColor]="previewData.warnColorDark"
@@ -258,7 +258,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.FONTDARK"
(previewChanged)="previewData.fontColorDark = $event; savePolicy()"
(previewChanged)="previewData.fontColorDark !== $event ? setDarkFontColorAndSave($event) : null"
name="Font Color"
[color]="data.fontColorDark"
[previewColor]="previewData.fontColorDark"
@@ -274,7 +274,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.BACKGROUNDLIGHT"
(previewChanged)="previewData.backgroundColor = $event; savePolicy()"
(previewChanged)="previewData.backgroundColor !== $event ? setBackgroundColorAndSave($event) : null"
name="Background Color"
[color]="data.backgroundColor"
[previewColor]="previewData.backgroundColor"
@@ -285,7 +285,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColor = $event; savePolicy()"
(previewChanged)="previewData.primaryColor !== $event ? setPrimaryColorAndSave($event) : null"
name="Primary Color"
[color]="data.primaryColor"
[previewColor]="previewData.primaryColor"
@@ -298,7 +298,7 @@
[disabled]="view === View.CURRENT"
[colorType]="ColorType.WARN"
name="Warn Color"
(previewChanged)="previewData.warnColor = $event; savePolicy()"
(previewChanged)="previewData.warnColor !== $event ? setWarnColorAndSave($event) : null"
[color]="data.warnColor"
[previewColor]="previewData.warnColor"
></cnsl-color>
@@ -308,7 +308,7 @@
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.FONTLIGHT"
(previewChanged)="previewData.fontColor = $event; savePolicy()"
(previewChanged)="previewData.fontColor !== $event ? setFontColorAndSave($event) : null"
name="Font Color"
[color]="data.fontColor"
[previewColor]="previewData.fontColor"
@@ -420,6 +420,7 @@
[refresh]="refreshPreview"
[theme]="theme"
class="preview"
[ngClass]="{ darkmode: theme === Theme.DARK, lightmode: theme === Theme.LIGHT }"
[policy]="view === View.PREVIEW ? previewData : data"
>
</cnsl-preview>
@@ -1,5 +1,7 @@
@use '@angular/material' as mat;
@import './preview/preview.component.scss';
@mixin private-label-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
@@ -68,6 +70,12 @@
margin-right: 0.5rem;
}
.info-i {
font-size: 1.2rem;
margin-left: 0.5rem;
margin-right: 0;
}
.current-dot {
height: 8px;
width: 8px;
@@ -314,6 +322,16 @@
min-height: 600px;
width: fit-content;
margin: auto;
.preview {
&.lightmode {
@include preview-theme(false);
}
&.darkmode {
@include preview-theme(true);
}
}
}
}
}
@@ -473,6 +473,46 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
}
}
public setDarkBackgroundColorAndSave($event: string): void {
this.previewData.backgroundColorDark = $event;
this.savePolicy();
}
public setDarkPrimaryColorAndSave($event: string): void {
this.previewData.primaryColorDark = $event;
this.savePolicy();
}
public setDarkWarnColorAndSave($event: string): void {
this.previewData.warnColorDark = $event;
this.savePolicy();
}
public setDarkFontColorAndSave($event: string): void {
this.previewData.fontColorDark = $event;
this.savePolicy();
}
public setBackgroundColorAndSave($event: string): void {
this.previewData.backgroundColor = $event;
this.savePolicy();
}
public setPrimaryColorAndSave($event: string): void {
this.previewData.primaryColor = $event;
this.savePolicy();
}
public setWarnColorAndSave($event: string): void {
this.previewData.warnColor = $event;
this.savePolicy();
}
public setFontColorAndSave($event: string): void {
this.previewData.fontColor = $event;
this.savePolicy();
}
public overwriteValues(req: AddCustomLabelPolicyRequest | UpdateCustomLabelPolicyRequest): void {
req.setBackgroundColorDark(this.previewData.backgroundColorDark);
req.setBackgroundColor(this.previewData.backgroundColor);
@@ -1,35 +1,89 @@
<cnsl-detail-layout *ngIf="project" [hasBackButton]="true"
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}">
<cnsl-detail-layout
*ngIf="project"
[hasBackButton]="true"
title="{{ projectName }} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
>
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}</span>
<a mat-icon-button href="https://docs.zitadel.ch/docs/manuals/admin-managers" target="_blank">
<i class="las la-info-circle"></i>
</a>
</p>
<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 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}}" mat-raised-button>
<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
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)="($event.stopPropagation); removeProjectMemberSelection()"
color="warn"
matTooltip="{{ 'ORG_DETAIL.TABLE.DELETE' | translate }}"
mat-raised-button
>
<i class="las la-trash"></i>
<span>{{'ACTIONS.SELECTIONDELETE' | translate}}</span>
<span>{{ 'ACTIONS.SELECTIONDELETE' | translate }}</span>
<cnsl-action-keys [type]="ActionKeysType.DELETE" (actionTriggered)="removeProjectMemberSelection()">
</cnsl-action-keys>
</button>
</ng-template>
<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']">
<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'
]"
>
<button color="primary" (click)="openAddMember()" class="cnsl-action-button" mat-raised-button>
<mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>
<cnsl-action-keys (actionTriggered)="openAddMember()">
</cnsl-action-keys>
<cnsl-action-keys (actionTriggered)="openAddMember()"> </cnsl-action-keys>
</button>
</ng-template>
</cnsl-members-table>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -103,6 +103,19 @@ export class ProjectMembersComponent {
this.grantId,
);
};
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.GRANTEDPROJECT,
param: { key: 'projectid', value: (this.project as GrantedProject.AsObject).projectId },
routerLink: ['/projects', (this.project as GrantedProject.AsObject).projectId],
}),
];
breadcrumbService.setBreadcrumb(breadcrumbs);
}
});
}
@@ -16,9 +16,11 @@
<cnsl-password-lockout-policy [serviceType]="serviceType"></cnsl-password-lockout-policy>
</ng-container>
<ng-container *ngIf="currentSetting === 'login'">
<!-- <cnsl-org-iam-policy [serviceType]="serviceType"></cnsl-org-iam-policy> -->
<cnsl-login-policy [serviceType]="serviceType"></cnsl-login-policy>
</ng-container>
<ng-container *ngIf="currentSetting === 'domain'">
<cnsl-domain-policy [serviceType]="serviceType"></cnsl-domain-policy>
</ng-container>
<ng-container *ngIf="currentSetting === 'idp'">
<cnsl-idp-settings [serviceType]="serviceType"></cnsl-idp-settings>
</ng-container>
@@ -5,6 +5,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { CardModule } from '../card/card.module';
import { DomainPolicyModule } from '../policies/domain-policy/domain-policy.module';
import { GeneralSettingsModule } from '../policies/general-settings/general-settings.module';
import { IdpSettingsModule } from '../policies/idp-settings/idp-settings.module';
import { LoginPolicyModule } from '../policies/login-policy/login-policy.module';
@@ -12,7 +13,6 @@ import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.modu
import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module';
import { NotificationSettingsModule } from '../policies/notification-settings/notification-settings.module';
import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module';
import { OrgIamPolicyModule } from '../policies/org-iam-policy/org-iam-policy.module';
import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module';
import { PasswordLockoutPolicyModule } from '../policies/password-lockout-policy/password-lockout-policy.module';
import { PrivacyPolicyModule } from '../policies/privacy-policy/privacy-policy.module';
@@ -37,7 +37,7 @@ import { SettingsListComponent } from './settings-list.component';
PrivacyPolicyModule,
MessageTextsPolicyModule,
LoginTextsPolicyModule,
OrgIamPolicyModule,
DomainPolicyModule,
TranslateModule,
HasRolePipeModule,
NotificationSettingsModule,
@@ -19,10 +19,12 @@ export const LOGIN: SidenavSetting = {
id: 'login',
i18nKey: 'SETTINGS.LIST.LOGIN',
groupI18nKey: 'SETTINGS.GROUPS.LOGIN',
// requiredRoles: {
// [PolicyComponentServiceType.ADMIN]: true,
// [PolicyComponentServiceType.MGMT]: true,
// }
};
export const DOMAIN: SidenavSetting = {
id: 'domain',
i18nKey: 'SETTINGS.LIST.DOMAIN',
groupI18nKey: 'SETTINGS.GROUPS.DOMAIN',
};
export const LOCKOUT: SidenavSetting = {
@@ -55,6 +55,7 @@ export class SidenavComponent implements ControlValueAccessor, OnInit {
queryParams: {
[this.queryParam]: setting,
},
replaceUrl: true,
queryParamsHandling: 'merge',
skipLocationChange: false,
});
@@ -1,6 +1,12 @@
<cnsl-refresh-table [loading]="dataSource?.loading$ | async" (refreshed)="changePage()" [hideRefresh]="true"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [timestamp]="dataSource?.viewTimestamp"
[dataSize]="dataSource?.totalResult ?? 0" [selection]="selection">
<cnsl-refresh-table
[loading]="dataSource?.loading$ | async"
(refreshed)="changePage()"
[hideRefresh]="true"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes"
[timestamp]="dataSource?.viewTimestamp"
[dataSize]="dataSource?.totalResult ?? 0"
[selection]="selection"
>
<!-- <div leftActions class="user-grants-table-left-actions">
<button class="user-grant-type-button" [ngClass]="{'active': type === undefined}"
(click)="setType(undefined)">{{'PROJECT.GRANT.ALL' | translate}}</button>
@@ -10,19 +16,36 @@
(click)="setType(Type.TYPE_MACHINE)">{{'USER.TABLE.TYPES.MACHINE' | translate}}</button>
</div> -->
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="cnsl-action-button" mat-raised-button actions
(click)="deleteGrantSelection()" *ngIf="selection.hasValue() && disableDelete === false">
<button
color="warn"
matTooltip="{{ 'GRANTS.DELETE' | translate }}"
class="cnsl-action-button"
mat-raised-button
actions
(click)="deleteGrantSelection()"
*ngIf="selection.hasValue() && disableDelete === false"
>
<i class="las la-trash"></i>
<span>{{'ACTIONS.DELETE' | translate}}</span>
<span>{{ 'ACTIONS.DELETE' | translate }}</span>
<cnsl-action-keys (actionTriggered)="deleteGrantSelection()" [type]="ActionKeysType.DELETE"></cnsl-action-keys>
</button>
<cnsl-filter-user-grants actions *ngIf="!selection.hasValue()" (filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"></cnsl-filter-user-grants>
<cnsl-filter-user-grants
actions
*ngIf="!selection.hasValue()"
(filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"
></cnsl-filter-user-grants>
<a actions *ngIf="disableWrite === false && (selection.hasValue() === false)"
matTooltip="{{'GRANTS.ADD' | translate}}" color="primary" class="cnsl-action-button" mat-raised-button
[routerLink]="routerLink">
<a
actions
*ngIf="disableWrite === false && selection.hasValue() === false"
matTooltip="{{ 'GRANTS.ADD' | translate }}"
color="primary"
class="cnsl-action-button"
mat-raised-button
[routerLink]="routerLink"
>
<mat-icon class="icon">add</mat-icon>
<span>{{ 'GRANTS.ADD_BTN' | translate }}</span>
<cnsl-action-keys (actionTriggered)="gotoCreateLink(routerLink)" [type]="ActionKeysType.ADD"></cnsl-action-keys>
@@ -32,68 +55,91 @@
<table mat-table multiTemplateDataRows class="table" aria-label="Elements" [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th class="selection" mat-header-cell *matHeaderCellDef>
<mat-checkbox [disabled]="disableWrite" color="primary" (change)="$event ? masterToggle() : null"
<mat-checkbox
[disabled]="disableWrite"
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
[indeterminate]="selection.hasValue() && !isAllSelected()"
>
</mat-checkbox>
</th>
<td class="selection" mat-cell *matCellDef="let row">
<mat-checkbox
[disabled]="disableWrite || !((['user.grant.write$'] | hasRole | async) || ((context === UserGrantContext.OWNED_PROJECT ? ['user.grant.write:' + row?.projectId] : context === UserGrantContext.GRANTED_PROJECT ? ['user.grant.write:' + row?.id] : []) | hasRole | async))"
color="primary" (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
<cnsl-avatar *ngIf="row && row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
[name]="row.displayName" [avatarUrl]="row.avatarUrl || ''" [forColor]="row?.preferredLoginName"
[size]="32">
[disabled]="
disableWrite ||
!(
(['user.grant.write$'] | hasRole | async) ||
((context === UserGrantContext.OWNED_PROJECT
? ['user.grant.write:' + row?.projectId]
: context === UserGrantContext.GRANTED_PROJECT
? ['user.grant.write:' + row?.id]
: []
)
| hasRole
| async)
)
"
color="primary"
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
>
<cnsl-avatar
*ngIf="row && row?.displayName && row.firstName && row.lastName; else cog"
class="avatar"
[name]="row.displayName"
[avatarUrl]="row.avatarUrl || ''"
[forColor]="row?.preferredLoginName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar class="avatar" [isMachine]="true" [forColor]="row.preferredLoginName" [size]="32">
</cnsl-avatar>
<cnsl-avatar class="avatar" [isMachine]="true" [forColor]="row.preferredLoginName" [size]="32"> </cnsl-avatar>
</ng-template>
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="user">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.USER' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.USER' | translate }}</th>
<td mat-cell *matCellDef="let grant">
<a class="user">{{grant.displayName ? grant.displayName :
grant.userName ? grant.userName : ''}}</a>
<a class="user">{{ grant.displayName ? grant.displayName : grant.userName ? grant.userName : '' }}</a>
</td>
</ng-container>
<ng-container matColumnDef="org">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.ORG' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.ORG' | translate }}</th>
<td mat-cell *matCellDef="let grant">
{{grant.orgName}} </td>
{{ grant.orgName }}
</td>
</ng-container>
<ng-container matColumnDef="projectId">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.PROJECTNAME' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.PROJECTNAME' | translate }}</th>
<td mat-cell *matCellDef="let grant">
{{grant.projectName}} </td>
{{ grant.projectName }}
</td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.TYPE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.TYPE' | translate }}</th>
<td mat-cell *matCellDef="let grant">
<span class="state">{{getType(grant)}}</span>
<span class="state">{{ getType(grant) }}</span>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{'PROJECT.GRANT.CREATIONDATE' | translate}} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.CREATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let grant">
<span class="no-break">{{grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm'
}}</span>
<span class="no-break">{{ grant.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'PROJECT.GRANT.CHANGEDATE' | translate }}</th>
<td mat-cell *matCellDef="let grant">
<span class="no-break">{{grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm'
}}</span>
<span class="no-break">{{ grant.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
@@ -103,42 +149,56 @@
</th>
<td mat-cell *matCellDef="let grant; let i = index" class="role-data">
<div class="flex-row">
<cnsl-project-role-chip [roleName]="role" *ngFor="let role of grant.roleKeysList">{{ role }}
<cnsl-project-role-chip [roleName]="role" *ngFor="let role of grant.roleKeysList"
>{{ role }}
</cnsl-project-role-chip>
</div>
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"> </th>
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
<td mat-cell class="user-tr-actions" *matCellDef="let grant; let i = index">
<cnsl-table-actions [hasActions]="true">
<button actions matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" (click)="deleteGrant(grant)"
mat-icon-button>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="deleteGrant($event, grant)"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
<button menuActions mat-menu-item [routerLink]="['/users', grant.userId]">
{{'ACTIONS.TABLE.SHOWUSER' | translate : ({value: grant.displayName})}}
{{ 'ACTIONS.TABLE.SHOWUSER' | translate: { value: grant.displayName } }}
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr (click)="openEditDialog(grant)" class="highlight pointer" mat-row
*matRowDef="let grant; columns: displayedColumns;">
</tr>
<tr
(click)="openEditDialog(grant)"
class="highlight pointer"
mat-row
*matRowDef="let grant; columns: displayedColumns"
></tr>
</table>
</div>
<div *ngIf="(dataSource.loading$ | async) === false && !dataSource?.totalResult" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{'GRANTS.EMPTY' | translate}}</span>
<span>{{ 'GRANTS.EMPTY' | translate }}</span>
</div>
<cnsl-paginator class="paginator" #paginator [timestamp]="dataSource?.viewTimestamp" [length]="dataSource.totalResult"
[pageSize]="INITIAL_PAGE_SIZE" [length]="dataSource.totalResult" [pageSizeOptions]="[2, 3, 25, 50, 100, 250]"
(page)="changePage($event)">
<cnsl-paginator
class="paginator"
#paginator
[timestamp]="dataSource?.viewTimestamp"
[length]="dataSource.totalResult"
[pageSize]="INITIAL_PAGE_SIZE"
[length]="dataSource.totalResult"
[pageSizeOptions]="[2, 3, 25, 50, 100, 250]"
(page)="changePage($event)"
>
</cnsl-paginator>
</cnsl-refresh-table>
</cnsl-refresh-table>
@@ -200,7 +200,9 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
});
}
public deleteGrant(grant: UserGrant.AsObject): void {
public deleteGrant(event: any, grant: UserGrant.AsObject): void {
event.stopPropagation();
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',