mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-27 01:32:15 +00:00
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:
co-authored by
Livio Amstutz
parent
8d0cf9f368
commit
8baf0fe08c
@@ -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();
|
||||
});
|
||||
});
|
||||
+63
-29
@@ -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;
|
||||
}
|
||||
+8
-8
@@ -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 }} <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 }} <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 }} <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 }}
|
||||
<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 }}
|
||||
<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,
|
||||
|
||||
+11
-15
@@ -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>
|
||||
|
||||
+4
@@ -18,6 +18,10 @@
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.set-password-btn {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.general-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
+52
-25
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+25
@@ -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>
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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();
|
||||
});
|
||||
});
|
||||
+16
@@ -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();
|
||||
});
|
||||
});
|
||||
+8
-8
@@ -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>
|
||||
|
||||
+2
-2
@@ -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
-2
@@ -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,
|
||||
|
||||
+22
-10
@@ -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 {
|
||||
|
||||
+11
-14
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-12
@@ -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>
|
||||
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user