feat(console): v2 feature cleanup, fix styling issues, instance naming (#3530)

* new console

* move npm ci to angular build

* rel path for assets

* local grpc copy

* login policy, rm clear views, features rel path

* login_hint param

* edit default color values

* remove features, zitadel-tier, contact styles

* html formatter

* rm feature restrictions, fix theming issues

* instance naming

* statehandler

* rm class

* rm class

* update footer links

* statehandler

* stroked color

Co-authored-by: Livio Amstutz <[email protected]>
This commit is contained in:
Max Peintner
2022-04-29 10:25:12 +02:00
committed by GitHub
co-authored by Livio Amstutz
parent f9e79ef8fe
commit 41e60b3d8a
93 changed files with 5256 additions and 13100 deletions
@@ -1,39 +1,54 @@
<div class="accounts-card" cnslOutsideClick (clickOutside)="closeCard($event)">
<cnsl-avatar (click)="editUserProfile()" *ngIf="user.human?.profile && user.human?.profile?.displayName"
class="avatar" [ngClass]="{'iam-user': iamuser}" [forColor]="user.preferredLoginName"
<cnsl-avatar
(click)="editUserProfile()"
*ngIf="user.human?.profile && user.human?.profile?.displayName"
class="avatar"
[ngClass]="{ 'iam-user': iamuser }"
[forColor]="user.preferredLoginName"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[name]="(user.human && user.human.profile && user.human.profile?.displayName) ? user.human.profile.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="80">
[name]="
user.human && user.human.profile && user.human.profile?.displayName
? user.human.profile.displayName
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
"
[size]="80"
>
</cnsl-avatar>
<span class="u-name">{{user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A'}}</span>
<span class="u-email" *ngIf="user.preferredLoginName">{{user.preferredLoginName}}</span>
<a [routerLink]="[ '/system']" class="iamuser" *ngIf="iamuser" (click)="close()">
<span class="label">{{'MENU.SYSTEM' | translate}}</span>
<span class="u-name">{{ user.human?.profile?.displayName ? user.human?.profile?.displayName : 'A' }}</span>
<span class="u-email" *ngIf="user.preferredLoginName">{{ user.preferredLoginName }}</span>
<a [routerLink]="['/system']" class="iamuser" *ngIf="iamuser" (click)="close()">
<span class="label">{{ 'MENU.INSTANCE' | translate }}</span>
<a class="iambtn">
<i class="las la-cog"></i>
</a>
</a>
<a [routerLink]="[ '/org']" class="iamuser" *ngIf="isOnSystem" (click)="close()">
<span class="label">{{'MENU.ORGANIZATION' | translate}}</span>
<a [routerLink]="['/org']" class="iamuser" *ngIf="isOnSystem" (click)="close()">
<span class="label">{{ 'MENU.ORGANIZATION' | translate }}</span>
<a class="iambtn">
<i class="las la-cog"></i>
</a>
</a>
<button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
<button (click)="editUserProfile()" mat-stroked-button>{{ 'USER.EDITACCOUNT' | translate }}</button>
<div class="l-accounts">
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)">
<cnsl-avatar *ngIf="session && session.displayName" class="small-avatar" [avatarUrl]="session.avatarUrl || ''"
[name]="session.displayName" [forColor]="session.loginName" [size]="32">
<cnsl-avatar
*ngIf="session && session.displayName"
class="small-avatar"
[avatarUrl]="session.avatarUrl || ''"
[name]="session.displayName"
[forColor]="session.loginName"
[size]="32"
>
</cnsl-avatar>
<div class="col">
<span class="user-title">{{session.displayName ? session.displayName : session.userName}} </span>
<span class="loginname">{{session.loginName}}</span>
<span class="email">{{'USER.STATE.'+session.authState | translate}}</span>
<span class="user-title">{{ session.displayName ? session.displayName : session.userName }} </span>
<span class="loginname">{{ session.loginName }}</span>
<span class="email">{{ 'USER.STATE.' + session.authState | translate }}</span>
</div>
<span class="fill-space"></span>
<mat-icon>keyboard_arrow_right</mat-icon>
@@ -43,12 +58,12 @@
<i class="las la-user-plus"></i>
</div>
<span class="col">
<span class="user-title">{{'USER.ADDACCOUNT' | translate}}</span>
<span class="user-title">{{ 'USER.ADDACCOUNT' | translate }}</span>
</span>
<span class="fill-space"></span>
<mat-icon>keyboard_arrow_right</mat-icon>
</a>
</div>
<button (click)="logout()" color="warn" mat-stroked-button>{{'MENU.LOGOUT' | translate}}</button>
</div>
<button (click)="logout()" color="warn" mat-stroked-button>{{ 'MENU.LOGOUT' | translate }}</button>
</div>
@@ -1,21 +1,26 @@
<span class="title" mat-dialog-title>{{'MEMBER.EDITROLE' | translate}}</span>
<span class="title" mat-dialog-title>{{ 'MEMBER.EDITROLE' | translate }}</span>
<div mat-dialog-content>
<p class="desc"> {{'MEMBER.EDITFOR' | translate: ({value: data.user})}}</p>
<p class="desc">{{ 'MEMBER.EDITFOR' | translate: { value: data.user } }}</p>
<div class="roles-selection">
<mat-checkbox class="role-cb" *ngFor="let role of allRoles" color="primary" (change)="toggleRole(role)"
[checked]="selectedRoles.includes(role)">
<mat-checkbox
class="role-cb"
*ngFor="let role of allRoles"
color="primary"
(change)="toggleRole(role)"
[checked]="selectedRoles.includes(role)"
>
<div class="role-cb-content">
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span>{{role | roletransform}}</span>
<span>{{ role | roletransform }}</span>
</div>
</mat-checkbox>
</div>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-stroked-button class="close-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
<button mat-stroked-button class="close-button" (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
<button color="primary" mat-raised-button class="ok-button" (click)="closeDialogWithRoles()">
{{'ACTIONS.CHANGE' | translate}}
{{ 'ACTIONS.CHANGE' | translate }}
</button>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FeaturesComponent } from './features.component';
const routes: Routes = [
{
path: '',
component: FeaturesComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class FeaturesRoutingModule { }
@@ -1,312 +0,0 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="('FEATURES.TITLE' | translate)">
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{'FEATURES.DESCRIPTION' | translate}}</span>
<a mat-icon-button href="https://docs.zitadel.ch/docs/legal/introduction" target="_blank">
<i class="las la-info-circle"></i>
</a>
</p>
<h2>{{'FEATURES.TIER.TITLE' | translate}}</h2>
<p *ngIf="serviceType === FeatureServiceType.MGMT" class="tier-desc cnsl-secondary-text">{{'FEATURES.TIER.DESCRIPTION'
| translate}}
{{'FEATURES.TIER.QUESTIONS' | translate}} <a href="mailto:[email protected]">[email protected]</a>.</p>
<div class="detail">
<p class="title cnsl-secondary-text">{{'FEATURES.TIER.NAME' | translate}}</p>
<p class="center">{{features?.tier?.name}}
<a class="ext" href="https://zitadel.ch/pricing" target="_blank">
<i class="las la-external-link-alt"></i>
</a>
</p>
</div>
<ng-container *ngIf="serviceType === FeatureServiceType.MGMT">
<mat-spinner class="spinner" diameter="20" *ngIf="customerLoading || stripeLoading"></mat-spinner>
<div class="detail" *ngIf="stripeCustomer || stripeCustomer === null">
<p class="title cnsl-secondary-text">{{'FEATURES.TIER.DETAILS' | translate}}
<a (click)="setCustomer()">{{'ACTIONS.EDIT' | translate}}</a>
</p>
<p>{{stripeCustomer?.contact}}</p>
<p *ngIf="stripeCustomer?.company">{{stripeCustomer?.company}}</p>
<p>{{stripeCustomer?.address}}</p>
<p *ngIf="stripeCustomer?.postal_code || stripeCustomer?.city || stripeCustomer?.country">
{{stripeCustomer?.postal_code}} {{stripeCustomer?.city}} {{stripeCustomer?.country}}
<img *ngIf="customerCountry" height="20px" width="30px"
style="margin-right: 1rem; border-radius: 2px; vertical-align: middle;"
src="../../../assets/flags/{{customerCountry.isoCode.toLowerCase()}}.png" />
</p>
</div>
<p class="error-tier-message" *ngIf="(stripeCustomer || stripeCustomer === null) && !customerValid">
{{'FEATURES.TIER.CUSTOMERINVALID' | translate}}</p>
<div class="current-tier">
<a color="primary" [disabled]="!org.id || !customerValid || !stripeURL" mat-raised-button [href]="stripeURL"
target="_blank" alt="change tier">{{'FEATURES.TIER.BTN' | translate}}</a>
</div>
</ng-container>
<ng-template cnslHasRole [hasRole]="['iam.features.delete']">
<button *ngIf="serviceType === FeatureServiceType.MGMT && !isDefault" matTooltip="{{'POLICY.RESET' | translate}}"
color="warn" (click)="resetFeatures()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<div class="content" *ngIf="features">
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.AUDITLOGRETENTION' | translate}}</span>
<span class="fill-space"></span>
<span>{{features.auditLogRetention | timestampToRetention }} {{'FEATURES.RETENTIONDAYS' |
translate}}</span>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.LOGINPOLICY' | translate}}</p>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYUSERNAMELOGIN' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyUsernameLogin}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyUsernameLogin" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYPASSWORDRESET' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyPasswordReset}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyPasswordReset" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYREGISTRATION' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyRegistration}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyRegistration" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYIDP' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyIdp}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyIdp" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYFACTORS' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyFactors}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyFactors" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar green">
<i class="icon las la-sign-in-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYPASSWORDLESS' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.loginPolicyPasswordless}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.loginPolicyPasswordless" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.PASSWORD' | translate}}</p>
<div class="row">
<div class="featureavatar yellow">
<mat-icon class="icon smaller" svgIcon="mdi_textbox_password"></mat-icon>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOGINPOLICYCOMPLEXITYPOLICY' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.passwordComplexityPolicy}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.passwordComplexityPolicy" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar yellow">
<mat-icon class="icon smaller" svgIcon="mdi_textbox_password"></mat-icon>
</div>
<span class="left-desc">{{'FEATURES.DATA.LOCKOUTPOLICY' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.lockoutPolicy}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.lockoutPolicy" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.LABELPOLICY' | translate}}</p>
<div class="row">
<div class="featureavatar blue">
<i class="icon las la-swatchbook"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYPRIVATELABEL' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.labelPolicyPrivateLabel}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.labelPolicyPrivateLabel" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar blue">
<i class="icon las la-swatchbook"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYWATERMARK' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.labelPolicyWatermark}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.labelPolicyWatermark" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.DOMAIN' | translate}}</p>
<div class="row">
<div class="featureavatar purple">
<i class="icon las la-gem"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.CUSTOMDOMAIN' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{active: features.customDomain}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.customDomain" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.TEXTSANDLINKS' | translate}}</p>
<div class="row">
<div class="featureavatar red">
<i class="icon las la-paragraph"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.CUSTOMTEXTMESSAGE' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.customTextMessage}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.customTextMessage" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar red">
<i class="icon las la-paragraph"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.CUSTOMTEXTLOGIN' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.customTextLogin}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.customTextLogin" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<div class="row">
<div class="featureavatar black">
<i class="icon las la-file-contract"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.PRIVACYPOLICY' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{active: features.privacyPolicy}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.privacyPolicy" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section cnsl-secondary-text">{{'FEATURES.HEADERS.METADATA' | translate}}</p>
<div class="row">
<div class="featureavatar blue">
<i class="icon las la-tags"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.METADATAUSER' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{active: features.metadataUser}"></template>
<mat-slide-toggle class="toggle" color="primary" name="hasNumber" ngDefaultControl
[(ngModel)]="features.metadataUser" *ngIf="(['iam.features.write'] | hasRole | async)">
</mat-slide-toggle>
</div>
<p class="feature-section">{{'FEATURES.HEADERS.FLOWS' | translate}}</p>
<div class="row">
<div class="featureavatar pink">
<i class="icon las la-exchange-alt"></i>
</div>
<span class="left-desc">{{'FEATURES.DATA.FLOWS' | translate}}</span>
<span class="fill-space"></span>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{active: features.actions}"></template>
</div>
<div class="row">
<span class="fill-space"></span>
<cnsl-form-field class="flow-select">
<cnsl-label>{{ 'FEATURES.DATA.FLOW.TYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="features.actionsAllowed"
[disabled]="(['iam.features.write'] | hasRole | async) === false">
<mat-option *ngFor="let allowedType of actionsSelection" [value]="allowedType">
{{ 'FEATURES.DATA.FLOW.ACTIONSALLOWED.'+allowedType | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field *ngIf="features.actionsAllowed === ActionsAllowed.ACTIONS_ALLOWED_MAX" class="flow-count">
<cnsl-label>{{ 'FEATURES.DATA.FLOW.COUNT' | translate }}</cnsl-label>
<input cnslInput type="number" [(ngModel)]="features.maxActions"
[disabled]="(['iam.features.write'] | hasRole | async) === false" ngDefaultControl />
</cnsl-form-field>
</div>
</div>
<div class="btn-container" *ngIf="(['iam.features.write'] | hasRole | async) === true">
<button (click)="savePolicy()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
</div>
</cnsl-detail-layout>
<ng-template #templateRef let-active="active">
<span class="state" [ngClass]="{'active': active, 'inactive': !active}">
{{active ? ('FEATURES.AVAILABLE' | translate) : ('FEATURES.UNAVAILABLE' | translate)}}
</span>
</ng-template>
@@ -1,181 +0,0 @@
.subinfo {
display: flex;
align-items: center;
font-size: 14px;
margin: -1.5rem 0 0 0;
i {
font-size: 1.2rem;
height: 1.2rem;
line-height: 1.2rem;
}
}
.tier-desc {
font-size: 14px;
margin-top: 0;
}
.detail {
margin-bottom: 1rem;
p {
margin: 0;
font-size: 14px;
display: flex;
align-items: center;
}
.title {
font-size: 14px;
margin-bottom: 0.5rem;
a {
margin-left: 0.5rem;
cursor: pointer;
}
}
.center {
display: flex;
align-items: center;
a {
margin-left: 0.5rem;
}
}
img {
height: 15px;
width: auto;
margin-left: 0.5rem;
}
}
.spinner {
margin: 0.5rem;
}
.error-tier-message {
color: var(--warn);
font-size: 14px;
}
.current-tier {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
.content {
padding-top: 1rem;
display: flex;
flex-direction: column;
width: 100%;
.feature-section {
font-size: 14px;
margin-top: 1.5rem;
}
.row {
display: flex;
align-items: center;
padding: 0.3rem 0;
.featureavatar {
margin-right: 1rem;
height: 30px;
width: 30px;
min-width: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(40deg, rgb(129, 85, 185) 30%, #7b8ada);
&.purple {
background: linear-gradient(40deg, #7c3aed 30%, #6d28d9);
}
&.red {
background: linear-gradient(40deg, #dc2626 30%, #db2777);
}
&.green {
background: linear-gradient(40deg, #059669 30%, #047857);
}
&.blue {
background: linear-gradient(40deg, #3b82f6 30%, #4f46e5);
}
&.pink {
background: linear-gradient(40deg, #db2777 30%, #be185d);
}
&.yellow {
background: linear-gradient(40deg, #f59e0b 30%, #b45309);
}
&.black {
background: linear-gradient(40deg, #1f2937, #111827);
}
i,
.icon {
font-size: 1.5rem;
height: 1.5rem;
line-height: 1.5rem;
color: white;
&.smaller {
font-size: 1.2rem;
height: 1.2rem;
line-height: 1.2rem;
}
}
}
.left-desc {
font-size: 0.9rem;
margin-right: 1rem;
}
.fill-space {
flex: 1;
}
.length-wrapper {
display: flex;
align-items: center;
}
.flow-select {
flex-shrink: 1;
min-width: 150px;
margin-left: 1rem;
}
.flow-count {
flex-shrink: 1;
width: 70px;
margin-left: 1rem;
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
}
}
.toggle {
margin-left: 1rem;
}
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FeaturesComponent } from './features.component';
describe('FeaturesComponent', () => {
let component: FeaturesComponent;
let fixture: ComponentFixture<FeaturesComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FeaturesComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FeaturesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,274 +0,0 @@
import { Component, Injector, OnDestroy, Type } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import {
GetOrgFeaturesResponse,
SetDefaultFeaturesRequest,
SetOrgFeaturesRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import { ActionsAllowed, Features } from 'src/app/proto/generated/zitadel/features_pb';
import { GetFeaturesResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { AdminService } from 'src/app/services/admin.service';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageKey, StorageLocation, StorageService } from 'src/app/services/storage.service';
import { StripeCustomer, SubscriptionService } from 'src/app/services/subscription.service';
import { ToastService } from 'src/app/services/toast.service';
import { COUNTRIES, Country } from './country';
import { PaymentInfoDialogComponent } from './payment-info-dialog/payment-info-dialog.component';
export enum FeatureServiceType {
MGMT,
ADMIN,
}
@Component({
selector: 'cnsl-features',
templateUrl: './features.component.html',
styleUrls: ['./features.component.scss'],
})
export class FeaturesComponent implements OnDestroy {
private managementService!: ManagementService;
public serviceType!: FeatureServiceType;
public features!: Features.AsObject;
private sub: Subscription = new Subscription();
public org!: Org.AsObject;
public FeatureServiceType: any = FeatureServiceType;
public customerLoading: boolean = false;
public stripeLoading: boolean = false;
public stripeURL: string = '';
public stripeCustomer!: StripeCustomer;
public actionsSelection: any = [
ActionsAllowed.ACTIONS_ALLOWED_NOT_ALLOWED,
ActionsAllowed.ACTIONS_ALLOWED_MAX,
ActionsAllowed.ACTIONS_ALLOWED_UNLIMITED,
];
public ActionsAllowed: any = ActionsAllowed;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private storage: StorageService,
private injector: Injector,
private adminService: AdminService,
private subService: SubscriptionService,
private dialog: MatDialog,
breadcrumbService: BreadcrumbService,
) {
const iamBread = new Breadcrumb({
type: BreadcrumbType.IAM,
name: 'IAM',
routerLink: ['/system'],
});
const bread: Breadcrumb = {
type: BreadcrumbType.ORG,
routerLink: ['/org'],
};
breadcrumbService.setBreadcrumb([iamBread, bread]);
const temporg: Org.AsObject | null = this.storage.getItem(StorageKey.organization, StorageLocation.session);
if (temporg) {
this.org = temporg;
}
const serviceType = this.route.snapshot.data.serviceType;
if (serviceType !== undefined) {
this.serviceType = serviceType;
if (this.serviceType === FeatureServiceType.MGMT) {
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
}
if (this.serviceType === FeatureServiceType.MGMT) {
this.customerLoading = true;
this.subService
.getCustomer(this.org.id)
.then((payload) => {
this.customerLoading = false;
this.stripeCustomer = payload;
if (this.customerValid) {
this.getLinkToStripe();
}
})
.catch((error) => {
this.customerLoading = false;
console.error(error);
});
}
}
this.fetchData();
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public setCustomer(): void {
const dialogRefPhone = this.dialog.open(PaymentInfoDialogComponent, {
data: {
customer: this.stripeCustomer,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe((customer) => {
if (customer) {
this.stripeCustomer = customer;
this.subService
.setCustomer(this.org.id, customer)
.then(() => {
this.getLinkToStripe();
})
.catch(console.error);
}
});
}
public getLinkToStripe(): void {
if (this.serviceType === FeatureServiceType.MGMT) {
this.stripeLoading = true;
this.subService
.getLink(this.org.id, window.location.href)
.then((payload) => {
this.stripeLoading = false;
this.stripeURL = payload.redirect_url;
})
.catch((error) => {
this.stripeLoading = false;
console.error(error);
});
}
}
public fetchData(): void {
this.getData().then((resp) => {
if (resp?.features) {
this.features = resp.features;
}
});
}
private async getData(): Promise<GetFeaturesResponse.AsObject | GetOrgFeaturesResponse.AsObject> {
switch (this.serviceType) {
case FeatureServiceType.MGMT:
return this.managementService.getFeatures();
case FeatureServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getDefaultFeatures();
} else {
return Promise.reject();
}
}
}
public savePolicy(): void {
switch (this.serviceType) {
case FeatureServiceType.MGMT:
const req = new SetOrgFeaturesRequest();
req.setOrgId(this.org.id);
req.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin);
req.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset);
req.setLoginPolicyRegistration(this.features.loginPolicyRegistration);
req.setLoginPolicyIdp(this.features.loginPolicyIdp);
req.setLoginPolicyFactors(this.features.loginPolicyFactors);
req.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
req.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
req.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
req.setCustomDomain(this.features.customDomain);
req.setCustomTextLogin(this.features.customTextLogin);
req.setCustomTextMessage(this.features.customTextMessage);
req.setPrivacyPolicy(this.features.privacyPolicy);
req.setMetadataUser(this.features.metadataUser);
req.setLockoutPolicy(this.features.lockoutPolicy);
req.setActionsAllowed(this.features.actionsAllowed);
req.setMaxActions(this.features.maxActions);
this.adminService
.setOrgFeatures(req)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
break;
case FeatureServiceType.ADMIN:
// update Default org iam policy?
const dreq = new SetDefaultFeaturesRequest();
dreq.setLoginPolicyUsernameLogin(this.features.loginPolicyUsernameLogin);
dreq.setLoginPolicyPasswordReset(this.features.loginPolicyPasswordReset);
dreq.setLoginPolicyRegistration(this.features.loginPolicyRegistration);
dreq.setLoginPolicyIdp(this.features.loginPolicyIdp);
dreq.setLoginPolicyFactors(this.features.loginPolicyFactors);
dreq.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
dreq.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
dreq.setCustomDomain(this.features.customDomain);
dreq.setCustomTextLogin(this.features.customTextLogin);
dreq.setCustomTextMessage(this.features.customTextMessage);
dreq.setMetadataUser(this.features.metadataUser);
dreq.setLockoutPolicy(this.features.lockoutPolicy);
dreq.setActionsAllowed(this.features.actionsAllowed);
dreq.setMaxActions(this.features.maxActions);
this.adminService
.setDefaultFeatures(dreq)
.then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
break;
}
}
public resetFeatures(): void {
if (this.serviceType === FeatureServiceType.MGMT) {
this.adminService
.resetOrgFeatures(this.org.id)
.then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
public get isDefault(): boolean {
if (this.features && this.serviceType === FeatureServiceType.MGMT) {
return this.features.isDefault;
} else {
return false;
}
}
get customerValid(): boolean {
return (
!!this.stripeCustomer?.contact &&
!!this.stripeCustomer?.address &&
!!this.stripeCustomer?.city &&
!!this.stripeCustomer?.postal_code
);
}
get customerCountry(): Country | undefined {
return COUNTRIES.find((country) => country.isoCode === this.stripeCustomer.country);
}
}
@@ -1,56 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
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';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import {
TimestampToRetentionPipeModule,
} from 'src/app/pipes/timestamp-to-retention-pipe/timestamp-to-retention-pipe.module';
import { FormFieldModule } from '../form-field/form-field.module';
import { InfoSectionModule } from '../info-section/info-section.module';
import { FeaturesRoutingModule } from './features-routing.module';
import { FeaturesComponent } from './features.component';
import { PaymentInfoDialogComponent } from './payment-info-dialog/payment-info-dialog.component';
@NgModule({
declarations: [
FeaturesComponent,
PaymentInfoDialogComponent,
],
imports: [
FeaturesRoutingModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
InputModule,
MatButtonModule,
FormFieldModule,
InputModule,
HasRoleModule,
MatSlideToggleModule,
MatSelectModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
MatTooltipModule,
MatProgressSpinnerModule,
InfoSectionModule,
TranslateModule,
DetailLayoutModule,
TimestampToRetentionPipeModule,
],
exports: [
FeaturesComponent,
],
})
export class FeaturesModule { }
@@ -1,59 +0,0 @@
<h1 mat-dialog-title>
<span class="title">{{'FEATURES.TIER.DETAILS' | translate}} {{data?.number}}</span>
</h1>
<p class="desc cnsl-secondary-text">{{'FEATURES.TIER.DETAILS_DESC' | translate}}</p>
<mat-spinner class="spinner" *ngIf="stripeLoading" diameter="20"></mat-spinner>
<div mat-dialog-content>
<form [formGroup]="form">
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.CONTACT' | translate }}</cnsl-label>
<input cnslInput name="contact" formControlName="contact" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.COMPANY' | translate }}</cnsl-label>
<input cnslInput name="company" formControlName="company" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.ADDRESS' | translate }}</cnsl-label>
<input cnslInput name="address" formControlName="address" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.CITY' | translate }}</cnsl-label>
<input cnslInput name="city" formControlName="city" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.POSTAL_CODE' | translate }}</cnsl-label>
<input cnslInput name="postal_code" formControlName="postal_code" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'FEATURES.TIER.COUNTRY' | translate }}</cnsl-label>
<mat-select formControlName="country" name="country" (selectionChange)="changeCountry($event)">
<mat-option *ngFor="let country of COUNTRIES" [value]="country.isoCode" class="center-row">
<div class="center">
<div class="img-wrapper">
<img height="20px" width="30px" style="margin-right: 1rem; border-radius: 2px; vertical-align: middle;"
src="../../../../assets/flags/{{country.isoCode.toLowerCase()}}.png" />
</div>
{{country.isoCode}} <span class="lighter cnsl-secondary-text">|&nbsp;{{country.name}}</span>
</div>
</mat-option>
</mat-select>
</cnsl-form-field>
</form>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial mat-stroked-button class="ok-button" (click)="dialogRef.close()">
{{'ACTIONS.CLOSE' | translate}}
</button>
<button [disabled]="form.invalid" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="submitAndCloseDialog()">
{{'ACTIONS.OK' | translate}}
</button>
</div>
@@ -1,45 +0,0 @@
h1 {
font-size: 1.5rem;
margin-top: 0;
}
.desc {
font-size: 14px;
}
.formfield {
width: 100%;
}
.action {
display: flex;
justify-content: space-between;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
}
img {
height: 15px;
width: auto;
}
.img-wrapper {
width: 50px;
display: flex;
}
.lighter {
font-size: 12px;
padding: 0 0.5rem;
}
.center {
display: flex;
align-items: center;
}
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PaymentInfoDialogComponent } from './payment-info-dialog.component';
describe('PaymentInfoDialogComponent', () => {
let component: PaymentInfoDialogComponent;
let fixture: ComponentFixture<PaymentInfoDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PaymentInfoDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PaymentInfoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,91 +0,0 @@
import { Component, Inject } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatSelectChange } from '@angular/material/select';
import { SubscriptionService } from 'src/app/services/subscription.service';
import { COUNTRIES, Country } from '../country';
function compare(a: Country, b: Country): number {
if (a.isoCode < b.isoCode) {
return -1;
}
if (a.isoCode > b.isoCode) {
return 1;
}
return 0;
}
@Component({
selector: 'cnsl-payment-info-dialog',
templateUrl: './payment-info-dialog.component.html',
styleUrls: ['./payment-info-dialog.component.scss'],
})
export class PaymentInfoDialogComponent {
public stripeLoading: boolean = false;
public COUNTRIES: Country[] = COUNTRIES.sort(compare);
public form!: FormGroup;
private orgId: string = '';
constructor(
private subService: SubscriptionService,
private fb: FormBuilder,
public dialogRef: MatDialogRef<PaymentInfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.orgId = data.orgId;
this.form = this.fb.group({
contact: ['', [Validators.required]],
company: ['', []],
address: ['', [Validators.required]],
city: ['', [Validators.required]],
postal_code: ['', [Validators.required]],
country: ['', [Validators.required]],
});
if (data.customer) {
this.form.patchValue(data.customer);
}
if (!data.customer?.country) {
this.form.get('country')?.setValue('CH');
}
this.getLink();
}
public getLink(): void {
if (this.orgId) {
this.stripeLoading = true;
this.subService
.getLink(this.orgId, window.location.href)
.then((payload) => {
this.stripeLoading = false;
if (payload.redirect_url) {
window.open(payload.redirect_url, '_blank');
}
})
.catch((error) => {
this.stripeLoading = false;
console.error(error);
});
}
}
public changeCountry(selection: MatSelectChange): void {
const country = COUNTRIES.find((c) => c.isoCode === selection.value);
if (country && country.phoneCode !== undefined && this.phone && this.phone.value !== `+${country.phoneCode}`) {
this.phone.setValue(`+${country.phoneCode}`);
}
}
submitAndCloseDialog(): void {
this.dialogRef.close(this.form.value);
}
get phone(): AbstractControl | null {
return this.form.get('phone');
}
}
@@ -2,26 +2,26 @@
<div class="footer-row">
<div class="footer-links">
<a target="_blank" *ngIf="policy?.tosLink" rel="noreferrer" [href]="policy.tosLink" external>
<span>{{'FOOTER.LINKS.TOS' | translate}}</span>
<span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
<a target="_blank" *ngIf="policy?.privacyLink" rel="noreferrer" [href]="policy.privacyLink" external>
<span>{{'FOOTER.LINKS.PP' | translate}}</span>
<span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
</div>
<div class="footer-socials" *ngIf="privateLabelPolicy && privateLabelPolicy.disableWatermark === false">
<a target="_blank" rel="noreferrer" href="https://github.com/caos">
<a target="_blank" rel="noreferrer" href="https://github.com/zitadel">
<i class="text-3xl lab la-github"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://twitter.com/caos_ch">
<a target="_blank" rel="noreferrer" href="https://twitter.com/zitadel_ch">
<i class="text-3xl lab la-twitter"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://www.linkedin.com/company/zitadel/">
<i class="text-3xl lab la-linkedin"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://discord.gg/erh5Brh7jE">
<a target="_blank" rel="noreferrer" href="https://zitadel.ch/chat">
<i class="text-3xl lab la-discord"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://www.youtube.com/channel/UCUAWJUNYaRn1yIVrZxEfsuA">
@@ -98,4 +98,4 @@
</a>
</div> -->
</div>
</div>
</div>
@@ -1,137 +1,234 @@
<mat-toolbar class="header-wrapper">
<div class="header-content">
<a class="title custom" [routerLink]="['/']"
*ngIf="org && labelpolicy && labelpolicy.disableWatermark; else defaultHome">
<img class="logo" alt="home logo" *ngIf="isDarkTheme; else customlighttheme"
[src]="labelpolicy?.iconUrlDark ? labelpolicy.iconUrlDark : './assets/images/zitadel-logo-solo-light.svg'"
onerror="this.src='./assets/images/zitadel-logo-solo-light.svg';this.onerror='';" />
<ng-template #customlighttheme>
<img alt="home logo" class="logo"
[src]="labelpolicy?.iconUrl ? labelpolicy.iconUrl : './assets/images/zitadel-logo-solo-dark.svg'"
onerror="this.src='./assets/images/zitadel-logo-solo-dark.svg';this.onerror='';" />
</ng-template>
</a>
<div class="header-content">
<a
class="title custom"
[routerLink]="['/']"
*ngIf="org && labelpolicy && labelpolicy.disableWatermark; else defaultHome"
>
<img
class="logo"
alt="home logo"
*ngIf="isDarkTheme; else customlighttheme"
[src]="labelpolicy?.iconUrlDark ? labelpolicy.iconUrlDark : './assets/images/zitadel-logo-solo-light.svg'"
onerror="this.src='./assets/images/zitadel-logo-solo-light.svg';this.onerror='';"
/>
<ng-template #customlighttheme>
<img
alt="home logo"
class="logo"
[src]="labelpolicy?.iconUrl ? labelpolicy.iconUrl : './assets/images/zitadel-logo-solo-dark.svg'"
onerror="this.src='./assets/images/zitadel-logo-solo-dark.svg';this.onerror='';"
/>
</ng-template>
</a>
<ng-template #defaultHome>
<a *ngIf="org" class="title" [routerLink]="['/']">
<img class="logo" alt="zitadel logo" *ngIf="isDarkTheme; else lighttheme"
src="./assets/images/zitadel-logo-solo-light.svg" />
<ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" />
</ng-template>
</a>
<ng-template #defaultHome>
<a *ngIf="org" class="title" [routerLink]="['/']">
<img
class="logo"
alt="zitadel logo"
*ngIf="isDarkTheme; else lighttheme"
src="./assets/images/zitadel-logo-solo-light.svg"
/>
<ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="../assets/images/zitadel-logo-solo-dark.svg" />
</ng-template>
</a>
</ng-template>
<ng-container *ngFor="let bread of (breadcrumbService.breadcrumbs$ | async) as bc; index as i">
<ng-container *ngIf="bread.type === BreadcrumbType.IAM">
<ng-template cnslHasRole [hasRole]="['iam.read']">
<svg class="slash hide-on-small" viewBox="0 0 24 24" width="32" height="32" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
shape-rendering="geometricPrecision">
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<ng-container *ngFor="let bread of breadcrumbService.breadcrumbs$ | async as bc; index as i">
<ng-container *ngIf="bread.type === BreadcrumbType.IAM">
<ng-template cnslHasRole [hasRole]="['iam.read']">
<svg
class="slash hide-on-small"
viewBox="0 0 24 24"
width="32"
height="32"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
shape-rendering="geometricPrecision"
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="breadcrumb-context hide-on-small">
<a matRipple [matRippleUnbounded]="false" class="breadcrumb-link"
[routerLink]="bread.routerLink">
{{'MENU.SYSTEM' | translate}}
</a>
</div>
</ng-template>
</ng-container>
<ng-container *ngIf="bread.type === BreadcrumbType.ORG">
<svg class="slash" viewBox="0 0 24 24" width="32" height="32" stroke="currentColor" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="org-context">
<a *ngIf="org" matRipple [matRippleUnbounded]="false" class="org-link" id="orglink"
[routerLink]="[ '/org']">
{{org?.name ? org.name : 'NO NAME'}}</a>
<div class="org-context-wrapper" *ngIf="org">
<button cdkOverlayOrigin #trigger="cdkOverlayOrigin" matRipple [matRippleUnbounded]="false"
id="orgswitchbutton" class="org-switch-button" (click)="showOrgContext = !showOrgContext">
<span class="svgspan">
<svg xmlns=" http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
clip-rule="evenodd"></path>
</svg>
</span>
<cnsl-action-keys (actionTriggered)="showOrgContext = !showOrgContext"
[type]="ActionKeysType.ORGSWITCHER"></cnsl-action-keys>
</button>
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="trigger"
[flexibleDimensions]="true" [lockPosition]="true" [cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true" [cdkConnectedOverlayPositions]="positions"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
[cdkConnectedOverlayOpen]="showOrgContext" (backdropClick)="showOrgContext = false"
(detach)="showOrgContext = false">
<cnsl-org-context class="context_card" *ngIf="org && showOrgContext"
(closedCard)="showOrgContext = false" [org]="org" (setOrg)="setActiveOrg($event)">
</cnsl-org-context>
</ng-template>
</div>
</div>
</ng-container>
<ng-container *ngIf="bread.type !== BreadcrumbType.IAM && bread.type !== BreadcrumbType.ORG">
<svg class="slash" viewBox="0 0 24 24" width="32" height="32" stroke="currentColor" stroke-width="1"
stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="breadcrumb-context">
<a matRipple [matRippleUnbounded]="false" class="breadcrumb-link"
[ngClass]="{'maxwidth': bc.length > 1}" [routerLink]="bread.routerLink">
<ng-container *ngIf="i !== bc.length - 1; else defLabel">
<span class="desk">{{bread.name}}</span>
<span class="mob">...</span>
</ng-container>
<ng-template #defLabel>
<span>{{bread.name}}</span>
</ng-template>
</a>
</div>
</ng-container>
</ng-container>
<span class="fill-space"></span>
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">
{{'MENU.DOCUMENTATION' | translate}}
</a>
<div class="system-rel" *ngIf="!isOnMe">
<a id="systembutton" *ngIf="!isOnSystem && (['iam.read$','iam.write$'] | hasRole | async)"
[routerLink]="[ '/system']" class="iam-settings cnsl-action-button" mat-stroked-button>
<span class="iam-label">{{'MENU.SYSTEM' | translate}}</span>
<i class="las la-cog"></i>
<div class="breadcrumb-context hide-on-small">
<a matRipple [matRippleUnbounded]="false" class="breadcrumb-link" [routerLink]="bread.routerLink">
{{ 'MENU.INSTANCE' | translate }}
</a>
<a id="orgbutton" *ngIf="isOnSystem && (['org.read'] | hasRole | async)" [routerLink]="[ '/org']"
class="org-settings cnsl-action-button" mat-stroked-button>
<span class="iam-label">{{'MENU.ORGANIZATION' | translate}}</span>
<i class="las la-cog"></i>
</a>
</div>
</div>
</ng-template>
</ng-container>
<div cnslOutsideClick (clickOutside)="closeAccountCard()" class="icon-container"
[ngClass]="{'iam-user': (['iam.write$'] | hasRole | async)}">
<cnsl-avatar id="avatartoggle"
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
class="avatar-toggle dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount"
[avatarUrl]="user.human?.profile?.avatarUrl || ''" [forColor]="user?.preferredLoginName || ''"
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName ?? '' : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
[size]="38">
</cnsl-avatar>
<cnsl-accounts-card @accounts class="a_card" *ngIf="showAccount" (closedCard)="showAccount = false"
[user]="user" [iamuser]="(['iam.write$'] | hasRole) | async">
</cnsl-accounts-card>
<ng-container *ngIf="bread.type === BreadcrumbType.ORG">
<svg
class="slash"
viewBox="0 0 24 24"
width="32"
height="32"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
shape-rendering="geometricPrecision"
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="org-context">
<a *ngIf="org" matRipple [matRippleUnbounded]="false" class="org-link" id="orglink" [routerLink]="['/org']">
{{ org?.name ? org.name : 'NO NAME' }}</a
>
<div class="org-context-wrapper" *ngIf="org">
<button
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
matRipple
[matRippleUnbounded]="false"
id="orgswitchbutton"
class="org-switch-button"
(click)="showOrgContext = !showOrgContext"
>
<span class="svgspan">
<svg xmlns=" http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</span>
<cnsl-action-keys
(actionTriggered)="showOrgContext = !showOrgContext"
[type]="ActionKeysType.ORGSWITCHER"
></cnsl-action-keys>
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[flexibleDimensions]="true"
[lockPosition]="true"
[cdkConnectedOverlayOffsetY]="10"
[cdkConnectedOverlayHasBackdrop]="true"
[cdkConnectedOverlayPositions]="positions"
cdkConnectedOverlayBackdropClass="transparent-backdrop"
[cdkConnectedOverlayOpen]="showOrgContext"
(backdropClick)="showOrgContext = false"
(detach)="showOrgContext = false"
>
<cnsl-org-context
class="context_card"
*ngIf="org && showOrgContext"
(closedCard)="showOrgContext = false"
[org]="org"
(setOrg)="setActiveOrg($event)"
>
</cnsl-org-context>
</ng-template>
</div>
</div>
</ng-container>
<ng-container *ngIf="bread.type !== BreadcrumbType.IAM && bread.type !== BreadcrumbType.ORG">
<svg
class="slash"
viewBox="0 0 24 24"
width="32"
height="32"
stroke="currentColor"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"
shape-rendering="geometricPrecision"
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="breadcrumb-context">
<a
matRipple
[matRippleUnbounded]="false"
class="breadcrumb-link"
[ngClass]="{ maxwidth: bc.length > 1 }"
[routerLink]="bread.routerLink"
>
<ng-container *ngIf="i !== bc.length - 1; else defLabel">
<span class="desk">{{ bread.name }}</span>
<span class="mob">...</span>
</ng-container>
<ng-template #defLabel>
<span>{{ bread.name }}</span>
</ng-template>
</a>
</div>
</ng-container>
</ng-container>
<span class="fill-space"></span>
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">
{{ 'MENU.DOCUMENTATION' | translate }}
</a>
<div class="system-rel" *ngIf="!isOnMe">
<a
id="systembutton"
*ngIf="!isOnSystem && (['iam.read$', 'iam.write$'] | hasRole | async)"
[routerLink]="['/system']"
class="iam-settings cnsl-action-button"
mat-stroked-button
>
<span class="iam-label">{{ 'MENU.INSTANCE' | translate }}</span>
<i class="las la-cog"></i>
</a>
<a
id="orgbutton"
*ngIf="isOnSystem && (['org.read'] | hasRole | async)"
[routerLink]="['/org']"
class="org-settings cnsl-action-button"
mat-stroked-button
>
<span class="iam-label">{{ 'MENU.ORGANIZATION' | translate }}</span>
<i class="las la-cog"></i>
</a>
</div>
</mat-toolbar>
<div
cnslOutsideClick
(clickOutside)="closeAccountCard()"
class="icon-container"
[ngClass]="{ 'iam-user': (['iam.write$'] | hasRole | async) }"
>
<cnsl-avatar
id="avatartoggle"
*ngIf="
user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))
"
class="avatar-toggle dontcloseonclick"
(click)="showAccount = !showAccount"
[active]="showAccount"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName || ''"
[name]="
user.human?.profile?.displayName
? user.human?.profile?.displayName ?? ''
: user.human?.profile?.firstName + ' ' + user.human?.profile?.lastName
"
[size]="38"
>
</cnsl-avatar>
<cnsl-accounts-card
@accounts
class="a_card"
*ngIf="showAccount"
(closedCard)="showAccount = false"
[user]="user"
[iamuser]="['iam.write$'] | hasRole | async"
>
</cnsl-accounts-card>
</div>
</div>
</mat-toolbar>
@@ -1,18 +1,35 @@
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
[emitRefreshOnPreviousRoutes]="['/system/idp/create']" [timestamp]="idpResult?.details?.viewTimestamp"
[selection]="selection" [hideRefresh]="true">
<cnsl-refresh-table
[loading]="loading$ | async"
(refreshed)="refreshPage()"
[dataSize]="dataSource.data.length"
[emitRefreshOnPreviousRoutes]="['/system/idp/create']"
[timestamp]="idpResult?.details?.viewTimestamp"
[selection]="selection"
[hideRefresh]="true"
>
<div actions>
<button (click)="deactivateSelectedIdps()" matTooltip="{{'IDP.DEACTIVATE' | translate}}"
class="margin-right bg-state inactive" mat-stroked-button *ngIf="selection.hasValue()" [disabled]="disabled">
{{'IDP.DEACTIVATE' | translate}}
<button
(click)="deactivateSelectedIdps()"
matTooltip="{{ 'IDP.DEACTIVATE' | translate }}"
class="margin-right bg-state inactive"
mat-stroked-button
*ngIf="selection.hasValue()"
[disabled]="disabled"
>
{{ 'IDP.DEACTIVATE' | translate }}
</button>
<button (click)="reactivateSelectedIdps()" matTooltip="{{'IDP.ACTIVATE' | translate}}" class="bg-state active"
mat-stroked-button *ngIf="selection.hasValue()" [disabled]="disabled">
{{'IDP.ACTIVATE' | translate}}
<button
(click)="reactivateSelectedIdps()"
matTooltip="{{ 'IDP.ACTIVATE' | translate }}"
class="bg-state active"
mat-stroked-button
*ngIf="selection.hasValue()"
[disabled]="disabled"
>
{{ 'IDP.ACTIVATE' | translate }}
</button>
<a [routerLink]="createRouterLink" class="cnsl-action-button" color="primary" mat-raised-button
[disabled]="disabled">
<a [routerLink]="createRouterLink" class="cnsl-action-button" color="primary" mat-raised-button [disabled]="disabled">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
</a>
</div>
@@ -21,18 +38,29 @@
<table class="table" mat-table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox color="primary" (change)="$event ? masterToggle() : null"
<mat-checkbox
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
[disabled]="serviceType === PolicyComponentServiceType.MGMT">
[disabled]="serviceType === PolicyComponentServiceType.MGMT"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let idp">
<mat-checkbox color="primary" (click)="$event.stopPropagation()" class="chbox"
<mat-checkbox
color="primary"
(click)="$event.stopPropagation()"
class="chbox"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && idp?.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM"
(change)="$event ? selection.toggle(idp) : null" [checked]="selection.isSelected(idp)">
<img src="../../../assets/images/google.png"
*ngIf="idp.stylingType === IDPStylingType.IDPSTYLINGTYPE_GOOGLE" alt="google" />
(change)="$event ? selection.toggle(idp) : null"
[checked]="selection.isSelected(idp)"
>
<img
src="../../../assets/images/google.png"
*ngIf="idp.stylingType === IDPStylingType.IDPSTYLINGTYPE_GOOGLE"
alt="google"
/>
</mat-checkbox>
</td>
</ng-container>
@@ -42,76 +70,123 @@
<!-- <span>{{ 'IDP.AVAILABILITY' | translate }}</span> -->
</th>
<td class="availability" [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
<i matTooltip="{{'IDP.AVAILABLE' | translate}}"
<i
matTooltip="{{ 'IDP.AVAILABLE' | translate }}"
*ngIf="isEnabled(idp) && idp.state === IDPState.IDP_STATE_ACTIVE"
class="idp-available las la-check-circle"></i>
<i matTooltip="{{'IDP.AVAILABLEBUTINACTIVE' | translate}}"
class="idp-available las la-check-circle"
></i>
<i
matTooltip="{{ 'IDP.AVAILABLEBUTINACTIVE' | translate }}"
*ngIf="isEnabled(idp) && idp.state === IDPState.IDP_STATE_INACTIVE"
class="idp-not-available las la-check-circle"></i>
class="idp-not-available las la-check-circle"
></i>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.NAME' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.NAME' | translate }}</th>
<td class="pointer" [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
<span>{{idp?.name}}</span>
<span>{{ idp?.name }}</span>
</td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.TYPE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.TYPE' | translate }}</th>
<td class="pointer" [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
<span class="state" *ngIf="idp?.oidcConfig">{{'IDP.OIDC.TITLE' | translate}}</span>
<span class="state" *ngIf="idp?.jwtConfig">{{'IDP.JWT.TITLE' | translate}}</span>
<span class="state" *ngIf="idp?.oidcConfig">{{ 'IDP.OIDC.TITLE' | translate }}</span>
<span class="state" *ngIf="idp?.jwtConfig">{{ 'IDP.JWT.TITLE' | translate }}</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.STATE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.STATE' | translate }}</th>
<td class="pointer" [routerLink]="routerLinkForRow(idp)" mat-cell *matCellDef="let idp">
<span class="state"
[ngClass]="{'active': idp.state === IDPState.IDP_STATE_ACTIVE, 'inactive': idp.state === IDPState.IDP_STATE_INACTIVE}">{{
'IDP.STATES.'+idp.state | translate }}</span>
<span
class="state"
[ngClass]="{
active: idp.state === IDPState.IDP_STATE_ACTIVE,
inactive: idp.state === IDPState.IDP_STATE_INACTIVE
}"
>{{ 'IDP.STATES.' + idp.state | translate }}</span
>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CREATIONDATE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.CREATIONDATE' | translate }}</th>
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
<span>{{idp.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
<span>{{ idp.details.creationDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.CHANGEDATE' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.CHANGEDATE' | translate }}</th>
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
<span>{{idp.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
<span>{{ idp.details.changeDate | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="owner">
<th mat-header-cell *matHeaderCellDef> {{ 'IDP.OWNER' | translate }} </th>
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.OWNER' | translate }}</th>
<td [routerLink]="routerLinkForRow(idp)" class="pointer" mat-cell *matCellDef="let idp">
{{'IDP.OWNERTYPES.'+idp.owner | translate }} </td>
{{ 'IDP.OWNERTYPES.' + idp.owner | translate }}
</td>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>
<th class="idp-table-actions" mat-header-cell *matHeaderCellDef></th>
<td class="idp-table-actions" mat-cell *matCellDef="let idp">
<cnsl-table-actions>
<button actions *ngIf="!isEnabled(idp)"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType === PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) === false || ((serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false))"
mat-icon-button matTooltip="{{'IDP.SETAVAILABLE' | translate}}" (click)="addIdp(idp)">
<button
actions
*ngIf="!isEnabled(idp)"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: ''
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETAVAILABLE' | translate }}"
(click)="addIdp(idp)"
>
<i class="las la-check-circle"></i>
</button>
<button actions *ngIf="isEnabled(idp)"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType === PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) === false || ((serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false))"
mat-icon-button matTooltip="{{'IDP.SETUNAVAILABLE' | translate}}" (click)="removeIdp(idp)">
<button
actions
*ngIf="isEnabled(idp)"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: ''
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETUNAVAILABLE' | translate }}"
(click)="removeIdp(idp)"
>
<i class="las la-times-circle"></i>
</button>
<button actions
[disabled]="serviceType === PolicyComponentServiceType.MGMT && idp?.providerType === IDPOwnerType.IDP_OWNER_TYPE_ORG"
mat-icon-button color="warn" matTooltip="{{'ACTIONS.REMOVE' | translate}}" (click)="deleteIdp(idp)">
<button
actions
[disabled]="
serviceType === PolicyComponentServiceType.MGMT && idp?.providerType === IDPOwnerType.IDP_OWNER_TYPE_ORG
"
mat-icon-button
color="warn"
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
(click)="deleteIdp(idp)"
>
<i class="las la-trash"></i>
</button>
</cnsl-table-actions>
@@ -119,11 +194,16 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns;">
</tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<cnsl-paginator #paginator class="paginator" [timestamp]="idpResult?.details?.viewTimestamp"
[length]="idpResult?.details?.totalResult || 0" [pageSize]="10" [pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"></cnsl-paginator>
</cnsl-refresh-table>
<cnsl-paginator
#paginator
class="paginator"
[timestamp]="idpResult?.details?.viewTimestamp"
[length]="idpResult?.details?.totalResult || 0"
[pageSize]="10"
[pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"
></cnsl-paginator>
</cnsl-refresh-table>
@@ -10,7 +10,6 @@ import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
@@ -40,7 +39,6 @@ import { IdpTableComponent } from './idp-table.component';
RefreshTableModule,
HasRoleModule,
HasRolePipeModule,
HasFeaturePipeModule,
TruncatePipeModule,
],
exports: [IdpTableComponent],
@@ -1,31 +1,33 @@
<h1 class="title" mat-dialog-title>{{'KEYBOARDSHORTCUTS.TITLE' | translate}}</h1>
<h1 class="title" mat-dialog-title>{{ 'KEYBOARDSHORTCUTS.TITLE' | translate }}</h1>
<div mat-dialog-content>
<div *ngIf="isNotOnSystem" class="keyboard-shortcuts-group">
<h2>{{'KEYBOARDSHORTCUTS.UNDERORGCONTEXT' | translate}}</h2>
<h2>{{ 'KEYBOARDSHORTCUTS.UNDERORGCONTEXT' | translate }}</h2>
<div class="keyboard-shortcuts-wrapper">
<div class="keyboard-shortcut" *ngFor="let shortcut of orgShortcuts">
<span class="keyboard-shortcut-name cnsl-secondary-text" [innerHTML]="shortcut.i18nKey | translate"></span>
<span class="fill-space"></span>
<template *ngFor="let key of shortcut.keyboardKeys; index as i" [ngTemplateOutlet]="actionkey"
[ngTemplateOutletContext]="{key: key, last: i === shortcut.keyboardKeys.length-1}"></template>
<template
*ngFor="let key of shortcut.keyboardKeys; index as i"
[ngTemplateOutlet]="actionkey"
[ngTemplateOutletContext]="{ key: key, last: i === shortcut.keyboardKeys.length - 1 }"
></template>
</div>
</div>
</div>
<div class="keyboard-shortcuts-group">
<h2>{{'KEYBOARDSHORTCUTS.SIDEWIDE' | translate}}</h2>
<h2>{{ 'KEYBOARDSHORTCUTS.SIDEWIDE' | translate }}</h2>
<div class="keyboard-shortcuts-wrapper">
<ng-container *ngFor="let shortcut of shortcuts">
<ng-template cnslHasRole [hasRole]="shortcut.permissions">
<ng-template cnslHasFeature [hasFeature]="shortcut.features">
<div class="keyboard-shortcut">
<span class="keyboard-shortcut-name cnsl-secondary-text"
[innerHTML]="shortcut.i18nKey | translate"></span>
<span class="fill-space"></span>
<template *ngFor="let key of shortcut.keyboardKeys; index as i" [ngTemplateOutlet]="actionkey"
[ngTemplateOutletContext]="{key: key, last: i === shortcut.keyboardKeys.length-1}"></template>
</div>
</ng-template>
<div class="keyboard-shortcut">
<span class="keyboard-shortcut-name cnsl-secondary-text" [innerHTML]="shortcut.i18nKey | translate"></span>
<span class="fill-space"></span>
<template
*ngFor="let key of shortcut.keyboardKeys; index as i"
[ngTemplateOutlet]="actionkey"
[ngTemplateOutletContext]="{ key: key, last: i === shortcut.keyboardKeys.length - 1 }"
></template>
</div>
</ng-template>
</ng-container>
</div>
@@ -33,7 +35,7 @@
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
{{ 'ACTIONS.CLOSE' | translate }}
</button>
<span class="fill-space"></span>
</div>
@@ -41,7 +43,7 @@
<ng-template #actionkey let-key="key" let-last="last">
<div class="keyboard-shortcuts-action-key">
<div class="keyboard-shortcuts-key-overlay"></div>
<span class="keyboard-shortcuts-span">{{key}}</span>
<span class="keyboard-shortcuts-span">{{ key }}</span>
</div>
<!-- <span *ngIf="!last" class="keyboard-shortcuts-plus cnsl-secondary-text">+</span> -->
</ng-template>
</ng-template>
@@ -4,13 +4,12 @@ import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { HasFeatureModule } from 'src/app/directives/has-feature/has-feature.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { KeyboardShortcutsComponent } from './keyboard-shortcuts.component';
@NgModule({
declarations: [KeyboardShortcutsComponent],
imports: [CommonModule, FormsModule, TranslateModule, HasFeatureModule, HasRoleModule, MatButtonModule, MatIconModule],
imports: [CommonModule, FormsModule, TranslateModule, HasRoleModule, MatButtonModule, MatIconModule],
})
export class KeyboardShortcutsModule {}
+122 -67
View File
@@ -1,37 +1,52 @@
<ng-container *ngIf="((['iam.read$','iam.write$'] | hasRole)) as iamuser$">
<div class="nav-col" [ngClass]="{'is-admin': (iamuser$ | async)}">
<ng-container *ngIf="['iam.read$', 'iam.write$'] | hasRole as iamuser$">
<div class="nav-col" [ngClass]="{ 'is-admin': (iamuser$ | async) }">
<ng-container
*ngIf="breadcrumbService.breadcrumbsExtended$ && (breadcrumbService.breadcrumbsExtended$ | async) as breadc">
*ngIf="breadcrumbService.breadcrumbsExtended$ && (breadcrumbService.breadcrumbsExtended$ | async) as breadc"
>
<ng-container
*ngIf="breadc[breadc.length - 1] && !breadc[breadc.length - 1].hideNav && breadc[breadc.length - 1].type !== BreadcrumbType.GRANTEDPROJECT && breadc[breadc.length - 1].type !== BreadcrumbType.APP && breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER"
[ngSwitch]="breadc[breadc.length - 1].type">
*ngIf="
breadc[breadc.length - 1] &&
!breadc[breadc.length - 1].hideNav &&
breadc[breadc.length - 1].type !== BreadcrumbType.GRANTEDPROJECT &&
breadc[breadc.length - 1].type !== BreadcrumbType.APP &&
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER
"
[ngSwitch]="breadc[breadc.length - 1].type"
>
<div class="nav-row" @navrow>
<ng-container *ngSwitchCase="BreadcrumbType.IAM">
<div class="nav-row-abs" @navroworg>
<ng-template cnslHasRole [hasRole]="['iam.read']">
<a class="nav-item" [routerLinkActiveOptions]="{ exact: false }" [routerLinkActive]="['active']"
[routerLink]="[ '/system' ]">
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/system']"
>
<div class="c_label">
<span> {{'MENU.IAM' | translate}} </span>
<span> {{ 'MENU.INSTANCEOVERVIEW' | translate }} </span>
</div>
</a>
<a class="nav-item" [routerLinkActiveOptions]="{ exact: false }" [routerLinkActive]="['active']"
[routerLink]="[ '/views']">
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/views']"
>
<div class="c_label">
<span> {{'MENU.VIEWS' | translate}} </span>
<span> {{ 'MENU.VIEWS' | translate }} </span>
</div>
</a>
<a class="nav-item" [routerLinkActiveOptions]="{ exact: false }" [routerLinkActive]="['active']"
[routerLink]="[ '/failed-events']">
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/failed-events']"
>
<div class="c_label">
<span> {{'MENU.FAILEDEVENTS' | translate}} </span>
<span> {{ 'MENU.FAILEDEVENTS' | translate }} </span>
</div>
</a>
</ng-template>
@@ -42,60 +57,89 @@
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
<div class="nav-row-abs" @navroworg>
<a class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }"
[routerLink]="[ '/']">
<span class="label">{{'MENU.DASHBOARD' | translate}}</span>
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: true }"
[routerLink]="['/']"
>
<span class="label">{{ 'MENU.DASHBOARD' | translate }}</span>
</a>
<ng-container class="org-list" *ngIf="org" [@navAnimation]="org">
<ng-template cnslHasRole [hasRole]="['org.read']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: false }"
[routerLink]="[ '/org']">
<span class="label">{{'MENU.ORGANIZATION' | translate}}</span>
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/org']"
>
<span class="label">{{ 'MENU.ORGANIZATION' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: false }"
[routerLink]="[ '/projects']">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/projects']"
>
<div class="c_label">
<span> {{'MENU.PROJECT' | translate}} </span>
<span> {{ 'MENU.PROJECT' | translate }} </span>
<small
*ngIf="(mgmtService?.ownedProjectsCount | async) && (mgmtService?.grantedProjectsCount | async)"
class="count">({{((mgmtService?.ownedProjectsCount | async) ?? 0) +
((mgmtService?.grantedProjectsCount | async) ?? 0)}})</small>
class="count"
>({{
((mgmtService?.ownedProjectsCount | async) ?? 0) +
((mgmtService?.grantedProjectsCount | async) ?? 0)
}})</small
>
</div>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['user.read(:[0-9]*)?']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/users']"
[routerLinkActiveOptions]="{ exact: false }">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/users']"
[routerLinkActiveOptions]="{ exact: false }"
>
<span class="label">{{ 'MENU.HUMANUSERS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['user.grant.read(:[0-9]*)?']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/grants']"
[routerLinkActiveOptions]="{ exact: true }">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/grants']"
[routerLinkActiveOptions]="{ exact: true }"
>
<span class="label">{{ 'MENU.GRANTS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasFeature [hasFeature]="['actions']">
<ng-template cnslHasRole [hasRole]="['org.action.read']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/actions']"
[routerLinkActiveOptions]="{ exact: true }">
<span class="label">{{ 'MENU.ACTIONS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['org.action.read']">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/actions']"
[routerLinkActiveOptions]="{ exact: true }"
>
<span class="label">{{ 'MENU.ACTIONS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['org.read']">
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/domains']"
[routerLinkActiveOptions]="{ exact: true }">
<span class="label">{{'MENU.DOMAINS' | translate}}</span>
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/domains']"
[routerLinkActiveOptions]="{ exact: true }"
>
<span class="label">{{ 'MENU.DOMAINS' | translate }}</span>
</a>
</ng-template>
</ng-container>
@@ -107,44 +151,56 @@
<ng-container *ngSwitchCase="BreadcrumbType.PROJECT">
<div *ngIf="breadc[breadc.length - 1]?.param?.value" class="nav-row-abs" @navrowproject>
<ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a class="nav-item" [routerLinkActiveOptions]="{ exact: true }" [routerLinkActive]="['active']"
[routerLink]="[ '/projects', breadc[breadc.length - 1]?.param?.value ]">
<a
class="nav-item"
[routerLinkActiveOptions]="{ exact: true }"
[routerLinkActive]="['active']"
[routerLink]="['/projects', breadc[breadc.length - 1]?.param?.value]"
>
<div class="c_label">
<span> {{'MENU.PROJECTOVERVIEW' | translate}} </span>
<span> {{ 'MENU.PROJECTOVERVIEW' | translate }} </span>
</div>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a *ngIf="!breadc[breadc.length - 1]?.isZitadel" class="nav-item"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActive]="['active']"
[routerLink]="[ '/projects', breadc[breadc.length - 1]?.param?.value, 'roles']">
<a
*ngIf="!breadc[breadc.length - 1]?.isZitadel"
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/projects', breadc[breadc.length - 1]?.param?.value, 'roles']"
>
<div class="c_label">
<span> {{'MENU.ROLES' | translate}} </span>
<span> {{ 'MENU.ROLES' | translate }} </span>
</div>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a *ngIf="!breadc[breadc.length - 1]?.isZitadel" class="nav-item"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActive]="['active']"
[routerLink]="[ '/projects', breadc[breadc.length - 1]?.param?.value, 'projectgrants' ]">
<a
*ngIf="!breadc[breadc.length - 1]?.isZitadel"
class="nav-item"
[routerLinkActiveOptions]="{ exact: false }"
[routerLinkActive]="['active']"
[routerLink]="['/projects', breadc[breadc.length - 1]?.param?.value, 'projectgrants']"
>
<div class="c_label">
<span> {{'MENU.PROJECTGRANTS' | translate}} </span>
<span> {{ 'MENU.PROJECTGRANTS' | translate }} </span>
</div>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['project.read(:[0-9]*)?']">
<a *ngIf="!breadc[breadc.length - 1]?.isZitadel" class="nav-item"
[routerLinkActiveOptions]="{ exact: true }" [routerLinkActive]="['active']"
[routerLink]="[ '/projects', breadc[breadc.length - 1]?.param?.value, 'grants' ]">
<a
*ngIf="!breadc[breadc.length - 1]?.isZitadel"
class="nav-item"
[routerLinkActiveOptions]="{ exact: true }"
[routerLinkActive]="['active']"
[routerLink]="['/projects', breadc[breadc.length - 1]?.param?.value, 'grants']"
>
<div class="c_label">
<span> {{'MENU.GRANTS' | translate}} </span>
<span> {{ 'MENU.GRANTS' | translate }} </span>
</div>
</a>
</ng-template>
@@ -156,15 +212,14 @@
</ng-container>
</ng-container>
</div>
</ng-container>
<ng-template #shortcutKeyRef>
<ng-container *ngIf="(isHandset$ | async) === false">
<span class="fill-space"></span>
<div class="nav-shortcut-action-key" matTooltip="{{'MENU.OPENSHORTCUTSTOOLTIP' | translate}}">
<div class="nav-shortcut-action-key" matTooltip="{{ 'MENU.OPENSHORTCUTSTOOLTIP' | translate }}">
<div class="nav-key-overlay"></div>
<span>?</span>
</div>
</ng-container>
</ng-template>
</ng-template>
@@ -9,9 +9,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasFeatureModule } from 'src/app/directives/has-feature/has-feature.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { SharedModule } from '../shared/shared.module';
@@ -28,8 +26,6 @@ import { NavComponent } from './nav.component';
RouterModule,
MatTooltipModule,
HasRolePipeModule,
HasFeaturePipeModule,
HasFeatureModule,
HasRoleModule,
MatMenuModule,
MatButtonModule,
@@ -1,190 +1,236 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.LOGIN_POLICY.TITLE' | translate"
[description]="(serviceType === PolicyComponentServiceType.MGMT ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEMGMT' : PolicyComponentServiceType.ADMIN ? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEADMIN' : '') | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
</ng-template>
</p>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.LOGIN_POLICY.TITLE' | translate"
[description]="
(serviceType === PolicyComponentServiceType.MGMT
? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEMGMT'
: PolicyComponentServiceType.ADMIN
? 'POLICY.LOGIN_POLICY.DESCRIPTIONCREATEADMIN'
: ''
) | translate
"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="!isDefault" color="primary" matTooltip="{{'POLICY.RESET' | translate}}" color="warn"
(click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<ng-template cnslHasRole [hasRole]="['policy.write']">
<button *ngIf="isDefault" color="primary" matTooltip="{{'POLICY.CREATECUSTOM' | translate}}"
(click)="savePolicy()" mat-raised-button>
{{'POLICY.CREATECUSTOM' | translate}}
</button>
</ng-template>
</ng-container>
<ng-template cnslHasRole [hasRole]="['org.idp.read']">
<cnsl-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<cnsl-idp-table [service]="service" [serviceType]="serviceType"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType === PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) === false || ((serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false))">
</cnsl-idp-table>
</cnsl-card>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
*ngIf="!isDefault"
color="primary"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<cnsl-card title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}"
description="{{'MFA.LIST.MULTIFACTORDESCRIPTION' | translate}}" [expanded]="true">
<div class="login-policy-row">
<cnsl-form-field *ngIf="loginData" class="passwordless-allowed" label="Access Code" required="true">
<cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType"
[disabled]="disabled || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) === false)">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
<ng-template cnslHasRole [hasRole]="['policy.write']">
<button
*ngIf="isDefault"
color="primary"
matTooltip="{{ 'POLICY.CREATECUSTOM' | translate }}"
(click)="savePolicy()"
mat-raised-button
>
{{ 'POLICY.CREATECUSTOM' | translate }}
</button>
</ng-template>
</ng-container>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.passwordless'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.passwordless'})"></span>
</cnsl-info-section>
</div>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<cnsl-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.MultiFactor"
[disabled]="(loginData?.passwordlessType === PasswordlessType.PASSWORDLESS_TYPE_NOT_ALLOWED) || (([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType === PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false)">
</cnsl-mfa-table>
<ng-template cnslHasRole [hasRole]="['org.idp.read']">
<cnsl-card title="{{ 'IDP.LIST.TITLE' | translate }}" description="{{ 'IDP.LIST.DESCRIPTION' | translate }}">
<cnsl-idp-table
[service]="service"
[serviceType]="serviceType"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: ''
]
| hasRole
| async) === false
"
>
</cnsl-idp-table>
</cnsl-card>
</ng-template>
<cnsl-card *ngIf="loginData" title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}"
description="{{'MFA.LIST.SECONDFACTORDESCRIPTION' | translate}}" [expanded]="true">
<mat-slide-toggle card-actions class="force-mfa-toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.forceMfa">
{{'POLICY.DATA.FORCEMFA' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.factors'})"></span>
</cnsl-info-section>
<cnsl-mfa-table [service]="service" [serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[disabled]="([serviceType === PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType === PolicyComponentServiceType.MGMT ? 'policy.write' : ''] | hasRole | async) === false || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) === false)">
</cnsl-mfa-table>
</cnsl-card>
<cnsl-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}">
<div class="login-policy-content" *ngIf="loginData">
<div class="login-policy-row">
<mat-slide-toggle color="primary" matTooltip="{{'POLICY.DATA.FORCEMFA_DESC' | translate}}"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.allowUsernamePassword">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) === false; else usernameInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span
[innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.username_login'})"></span>
</cnsl-info-section>
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary"
[disabled]="disabled || (serviceType === PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) === false)"
ngDefaultControl [(ngModel)]="loginData.allowRegister">
{{'POLICY.DATA.ALLOWREGISTER' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.registration'] | hasFeature | async) === false; else regInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span
[innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.registration'})"></span>
</cnsl-info-section>
<ng-template #regInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWREGISTER_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.allowExternalIdp">
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) === false; else idpInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.idp'})"></span>
</cnsl-info-section>
<ng-template #idpInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle class="login-policy-toggle" color="primary"
[disabled]="disabled || serviceType === PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) === false"
ngDefaultControl [(ngModel)]="loginData.hidePasswordReset">
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
</mat-slide-toggle>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) === false; else passwordResetInfo"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span
[innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'login_policy.password_reset'})"></span>
</cnsl-info-section>
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate}}
</cnsl-info-section>
</ng-template>
</div>
</div>
</cnsl-card>
<div class="login-policy-btn-container">
<button [disabled]="disabled" class="login-policy-save-button" (click)="savePolicy()" color="primary"
type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
<cnsl-card
title="{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}"
description="{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}"
[expanded]="true"
>
<div class="login-policy-row">
<cnsl-form-field *ngIf="loginData" class="passwordless-allowed" label="Access Code" required="true">
<cnsl-label>{{ 'LOGINPOLICY.PASSWORDLESS' | translate }}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType" [disabled]="disabled">
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{ 'LOGINPOLICY.PASSWORDLESSTYPE.' + pt | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<cnsl-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security">
</cnsl-policy-grid>
</cnsl-detail-layout>
<cnsl-mfa-table
[service]="service"
[serviceType]="serviceType"
[componentType]="LoginMethodComponentType.MultiFactor"
[disabled]="
loginData?.passwordlessType === PasswordlessType.PASSWORDLESS_TYPE_NOT_ALLOWED ||
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
</cnsl-mfa-table>
</cnsl-card>
<cnsl-card
*ngIf="loginData"
title="{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}"
description="{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}"
[expanded]="true"
>
<mat-slide-toggle
card-actions
class="force-mfa-toggle"
color="primary"
[disabled]="disabled"
ngDefaultControl
[(ngModel)]="loginData.forceMfa"
>
{{ 'POLICY.DATA.FORCEMFA' | translate }}
</mat-slide-toggle>
<cnsl-mfa-table
[service]="service"
[serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
</cnsl-mfa-table>
</cnsl-card>
<cnsl-card title="{{ 'POLICY.LOGIN_POLICY.ADVANCED' | translate }}">
<div class="login-policy-content" *ngIf="loginData">
<div class="login-policy-row">
<mat-slide-toggle
color="primary"
matTooltip="{{ 'POLICY.DATA.FORCEMFA_DESC' | translate }}"
[disabled]="disabled"
ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword"
>
{{ 'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate }}
</mat-slide-toggle>
<ng-template #usernameInfo>
<cnsl-info-section class="info">
{{ 'POLICY.DATA.ALLOWUSERNAMEPASSWORD_DESC' | translate }}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle
class="login-policy-toggle"
color="primary"
[disabled]="disabled"
ngDefaultControl
[(ngModel)]="loginData.allowRegister"
>
{{ 'POLICY.DATA.ALLOWREGISTER' | translate }}
</mat-slide-toggle>
<ng-template #regInfo>
<cnsl-info-section class="info">
{{ 'POLICY.DATA.ALLOWREGISTER_DESC' | translate }}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle
class="login-policy-toggle"
color="primary"
[disabled]="disabled"
ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp"
>
{{ 'POLICY.DATA.ALLOWEXTERNALIDP' | translate }}
</mat-slide-toggle>
<ng-template #idpInfo>
<cnsl-info-section class="info">
{{ 'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate }}
</cnsl-info-section>
</ng-template>
</div>
<div class="login-policy-row">
<mat-slide-toggle
class="login-policy-toggle"
color="primary"
[disabled]="disabled"
ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset"
>
{{ 'POLICY.DATA.HIDEPASSWORDRESET' | translate }}
</mat-slide-toggle>
<ng-template #passwordResetInfo>
<cnsl-info-section class="info">
{{ 'POLICY.DATA.HIDEPASSWORDRESET_DESC' | translate }}
</cnsl-info-section>
</ng-template>
</div>
</div>
</cnsl-card>
<div class="login-policy-btn-container">
<button
[disabled]="disabled"
class="login-policy-save-button"
(click)="savePolicy()"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security">
</cnsl-policy-grid>
</cnsl-detail-layout>
@@ -15,7 +15,6 @@ import { CardModule } from 'src/app/modules/card/card.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { IdpTableModule } from 'src/app/modules/idp-table/idp-table.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
@@ -36,7 +35,6 @@ import { MfaTableComponent } from './mfa-table/mfa-table.component';
InputModule,
MatIconModule,
MatButtonModule,
HasFeaturePipeModule,
MatSlideToggleModule,
HasRoleModule,
MatDialogModule,
@@ -1,29 +1,38 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.LOGIN_TEXTS.TITLE' | translate"
[description]="'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.LOGIN_TEXTS.TITLE' | translate"
[description]="'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<div class="date">
<div>
<p class="newer-title" *ngIf="newerVersionExists">{{'POLICY.LOGIN_TEXTS.NEWERVERSIONEXISTS' | translate}}</p>
<p *ngIf="newerPolicyChangeDate && newerVersionExists">{{'POLICY.LOGIN_TEXTS.CHANGEDATE' | translate}}:
{{newerPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss'}}</p>
<p class="cnsl-secondary-text" *ngIf="currentPolicyChangeDate">{{'POLICY.LOGIN_TEXTS.CURRENTDATE' | translate}}:
{{currentPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss'}}</p>
<p class="newer-title" *ngIf="newerVersionExists">{{ 'POLICY.LOGIN_TEXTS.NEWERVERSIONEXISTS' | translate }}</p>
<p *ngIf="newerPolicyChangeDate && newerVersionExists">
{{ 'POLICY.LOGIN_TEXTS.CHANGEDATE' | translate }}:
{{ newerPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss' }}
</p>
<p class="cnsl-secondary-text" *ngIf="currentPolicyChangeDate">
{{ 'POLICY.LOGIN_TEXTS.CURRENTDATE' | translate }}:
{{ currentPolicyChangeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm:ss' }}
</p>
</div>
<button [disabled]="!newerVersionExists" color="primary" mat-raised-button (click)="loadData()">
<i class="las la-sync-alt"></i>
{{'ACTIONS.REFRESH' | translate}}</button>
{{ 'ACTIONS.REFRESH' | translate }}
</button>
</div>
<form *ngIf="form" class="top-actions" [formGroup]="form">
<cnsl-form-field class="keys" appearance="outline">
<cnsl-label>{{ 'POLICY.LOGIN_TEXTS.KEYNAME' | translate }}</cnsl-label>
<mat-select formControlName="currentSubMap" name="currentSubMap">
<mat-option *ngFor="let key of KeyNamesArray" [value]="key">
{{'POLICY.LOGIN_TEXTS.KEYS.'+key | translate }}
{{ 'POLICY.LOGIN_TEXTS.KEYS.' + key | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -33,40 +42,53 @@
<mat-select formControlName="locale" name="locale">
<mat-option *ngFor="let loc of LOCALES" [value]="loc">
<div class="centerline">
<span>{{loc}} <span class="lighter cnsl-secondary-text">|&nbsp;{{'POLICY.LOGIN_TEXTS.LOCALES.'+loc |
translate }}</span></span>
<span
>{{ loc }}
<span class="lighter cnsl-secondary-text"
>|&nbsp;{{ 'POLICY.LOGIN_TEXTS.LOCALES.' + loc | translate }}</span
></span
>
</div>
</mat-option>
</mat-select>
</cnsl-form-field>
</form>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.login'})"></span>
</cnsl-info-section>
<div class="divider"></div>
<div class="content">
<cnsl-edit-text label="one"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false) || (canWrite$ | async) === false"
[default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues(
$event)"></cnsl-edit-text>
<cnsl-edit-text
label="one"
[disabled]="(canWrite$ | async) === false"
[default$]="getDefaultInitMessageTextMap$"
[current$]="getCustomInitMessageTextMap$"
(changedValues)="updateCurrentValues($event)"
></cnsl-edit-text>
</div>
<div class="actions">
<!-- *ngIf="totalCustomPolicy && totalCustomPolicy.isDefault === false" -->
<button class="reset-button"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false) || (canWrite$ | async) === false"
(click)="resetDefault()" color="warn" type="submit" mat-stroked-button><i class="las la-history"></i> {{
'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['custom_text.login'] | hasFeature | async) === false) || (canWrite$ | async) === false"
(click)="saveCurrentMessage()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
<button
class="reset-button"
[disabled]="(canWrite$ | async) === false"
(click)="resetDefault()"
color="warn"
type="submit"
mat-stroked-button
>
<i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}
</button>
<button
class="save-button"
[disabled]="(canWrite$ | async) === false"
(click)="saveCurrentMessage()"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -15,7 +15,6 @@ import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/
import { HasRoleModule } from '../../../directives/has-role/has-role.module';
import { DetailLayoutModule } from '../../../modules/detail-layout/detail-layout.module';
import { InputModule } from '../../../modules/input/input.module';
import { HasFeaturePipeModule } from '../../../pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from '../../../pipes/has-role-pipe/has-role-pipe.module';
import { EditTextModule } from '../../edit-text/edit-text.module';
import { FormFieldModule } from '../../form-field/form-field.module';
@@ -38,7 +37,6 @@ import { LoginTextsComponent } from './login-texts.component';
FormFieldModule,
EditTextModule,
MatButtonModule,
HasFeaturePipeModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
@@ -55,4 +53,4 @@ import { LoginTextsComponent } from './login-texts.component';
LocalizedDatePipeModule,
],
})
export class LoginTextsPolicyModule { }
export class LoginTextsPolicyModule {}
@@ -1,8 +1,13 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.MESSAGE_TEXTS.TITLE' | translate"
[description]="'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.MESSAGE_TEXTS.TITLE' | translate"
[description]="'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
@@ -10,8 +15,8 @@
<cnsl-form-field class="type">
<cnsl-label>{{ 'POLICY.MESSAGE_TEXTS.TYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="currentType" name="currentSubMap" (selectionChange)="changedCurrentType()">
<mat-option *ngFor="let type of (MESSAGETYPES | keyvalue)" [value]="type.value">
{{'POLICY.MESSAGE_TEXTS.TYPES.'+type.value | translate}}
<mat-option *ngFor="let type of MESSAGETYPES | keyvalue" [value]="type.value">
{{ 'POLICY.MESSAGE_TEXTS.TYPES.' + type.value | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
@@ -21,38 +26,52 @@
<mat-select [(ngModel)]="locale" name="locale" (selectionChange)="changeLocale($event)">
<mat-option *ngFor="let loc of LOCALES" [value]="loc">
<div class="centerline">
<span>{{loc}} <span class="lighter cnsl-secondary-text">|&nbsp;{{'POLICY.LOGIN_TEXTS.LOCALES.'+loc |
translate }}</span></span>
<span
>{{ loc }}
<span class="lighter cnsl-secondary-text"
>|&nbsp;{{ 'POLICY.LOGIN_TEXTS.LOCALES.' + loc | translate }}</span
></span
>
</div>
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'custom_text.message'})"></span>
</cnsl-info-section>
<div class="content">
<cnsl-edit-text [chips]="chips[currentType]"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false) || (canWrite$ | async) === false"
label="one" [default$]="getDefaultInitMessageTextMap$" [current$]="getCustomInitMessageTextMap$" (changedValues)="updateCurrentValues(
$event)"></cnsl-edit-text>
<cnsl-edit-text
[chips]="chips[currentType]"
[disabled]="(canWrite$ | async) === false"
label="one"
[default$]="getDefaultInitMessageTextMap$"
[current$]="getCustomInitMessageTextMap$"
(changedValues)="updateCurrentValues($event)"
></cnsl-edit-text>
</div>
<div class="actions">
<button class="reset-button"
*ngIf="(getCustomInitMessageTextMap$ | async) && ((getCustomInitMessageTextMap$ | async)?.isDefault === false)"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false) || (canWrite$ | async) === false"
(click)="resetDefault()" color="warn" type="submit" mat-stroked-button><i class="las la-history"></i> {{
'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button"
[disabled]="!updateRequest || (serviceType === PolicyComponentServiceType.MGMT && (['custom_text.message'] | hasFeature | async) === false) || (canWrite$ | async) === false"
(click)="saveCurrentMessage()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
<button
class="reset-button"
*ngIf="(getCustomInitMessageTextMap$ | async) && (getCustomInitMessageTextMap$ | async)?.isDefault === false"
[disabled]="(canWrite$ | async) === false"
(click)="resetDefault()"
color="warn"
type="submit"
mat-stroked-button
>
<i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}
</button>
<button
class="save-button"
[disabled]="!updateRequest || (canWrite$ | async) === false"
(click)="saveCurrentMessage()"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -11,7 +11,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { EditTextModule } from '../../edit-text/edit-text.module';
@@ -33,7 +32,6 @@ import { MessageTextsComponent } from './message-texts.component';
FormFieldModule,
EditTextModule,
MatButtonModule,
HasFeaturePipeModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
@@ -47,4 +45,4 @@ import { MessageTextsComponent } from './message-texts.component';
TextFieldModule,
],
})
export class MessageTextsPolicyModule { }
export class MessageTextsPolicyModule {}
@@ -1,34 +1,55 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.IAM_POLICY.TITLE' | translate"
[description]="'POLICY.IAM_POLICY.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.IAM_POLICY.TITLE' | translate"
[description]="'POLICY.IAM_POLICY.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<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
*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
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>
<button
(click)="savePolicy()"
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="login"></cnsl-policy-grid>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -1,88 +1,113 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.PWD_COMPLEXITY.TITLE' | translate"
[description]="'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.PWD_COMPLEXITY.TITLE' | translate"
[description]="'POLICY.PWD_COMPLEXITY.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['password_complexity_policy'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'password_complexity_policy'})"></span>
</cnsl-info-section>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false)"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
<button
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<div *ngIf="complexityData" class="complexity-content">
<div class="row">
<mat-icon class="icon" svgIcon="mdi_counter"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.MINLENGTH' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.MINLENGTH' | translate }}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button mat-icon-button (click)="decrementLength()"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<button mat-icon-button (click)="decrementLength()" [disabled]="(['policy.write'] | hasRole | async) === false">
<mat-icon>remove</mat-icon>
</button>
<span>{{complexityData?.minLength}}</span>
<button mat-icon-button (click)="incrementLength()"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<span>{{ complexityData?.minLength }}</span>
<button mat-icon-button (click)="incrementLength()" [disabled]="(['policy.write'] | hasRole | async) === false">
<mat-icon>add</mat-icon>
</button>
</div>
</div>
<div class="row">
<mat-icon class="icon" svgIcon="mdi_numeric"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASNUMBER' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.HASNUMBER' | translate }}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="complexityData.hasNumber"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<mat-slide-toggle
color="primary"
name="hasNumber"
ngDefaultControl
[(ngModel)]="complexityData.hasNumber"
[disabled]="(['policy.write'] | hasRole | async) === false"
>
</mat-slide-toggle>
</div>
<div class="row">
<mat-icon class="icon" svgIcon="mdi_symbol"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASSYMBOL' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.HASSYMBOL' | translate }}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasSymbol" ngDefaultControl [(ngModel)]="complexityData.hasSymbol"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<mat-slide-toggle
color="primary"
name="hasSymbol"
ngDefaultControl
[(ngModel)]="complexityData.hasSymbol"
[disabled]="(['policy.write'] | hasRole | async) === false"
>
</mat-slide-toggle>
</div>
<div class="row">
<mat-icon class="icon" svgIcon="mdi_format-letter-case-lower"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASLOWERCASE' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.HASLOWERCASE' | translate }}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasLowercase" ngDefaultControl [(ngModel)]="complexityData.hasLowercase"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<mat-slide-toggle
color="primary"
name="hasLowercase"
ngDefaultControl
[(ngModel)]="complexityData.hasLowercase"
[disabled]="(['policy.write'] | hasRole | async) === false"
>
</mat-slide-toggle>
</div>
<div class="row">
<mat-icon class="icon" svgIcon="mdi_format-letter-case-upper"></mat-icon>
<span class="left-desc">{{'POLICY.DATA.HASUPPERCASE' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.HASUPPERCASE' | translate }}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasUppercase" ngDefaultControl [(ngModel)]="complexityData.hasUppercase"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)">
<mat-slide-toggle
color="primary"
name="hasUppercase"
ngDefaultControl
[(ngModel)]="complexityData.hasUppercase"
[disabled]="(['policy.write'] | hasRole | async) === false"
>
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">
<button (click)="savePolicy()"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['password_complexity_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)"
color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
<button
(click)="savePolicy()"
[disabled]="(['policy.write'] | hasRole | async) === false"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></cnsl-policy-grid>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -7,11 +7,9 @@ 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 { HasFeatureModule } from 'src/app/directives/has-feature/has-feature.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
@@ -33,8 +31,6 @@ import { PasswordComplexityPolicyComponent } from './password-complexity-policy.
MatTooltipModule,
HasRolePipeModule,
TranslateModule,
HasFeatureModule,
HasFeaturePipeModule,
DetailLayoutModule,
MatProgressSpinnerModule,
PolicyGridModule,
@@ -1,42 +1,40 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.PWD_LOCKOUT.TITLE' | translate"
[description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.PWD_LOCKOUT.TITLE' | translate"
[description]="'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<cnsl-info-section class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['lockout_policy'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'lockout_policy'})"></span>
</cnsl-info-section>
<cnsl-info-section class="default" *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['lockout_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)"
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault" matTooltip="{{'POLICY.RESET' | translate}}"
color="warn" (click)="resetPolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
[disabled]="(['policy.write'] | hasRole | async) === false"
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="resetPolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<div class="content" *ngIf="lockoutData">
<div class="row">
<span class="left-desc">{{'POLICY.DATA.MAXATTEMPTS' | translate}}</span>
<span class="left-desc">{{ 'POLICY.DATA.MAXATTEMPTS' | translate }}</span>
<span class="fill-space"></span>
<div class="length-wrapper">
<button
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['lockout_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)"
mat-icon-button (click)="decrementMaxAttempts()">
<button [disabled]="(['policy.write'] | hasRole | async) === false" mat-icon-button (click)="decrementMaxAttempts()">
<mat-icon>remove</mat-icon>
</button>
<span>{{lockoutData?.maxPasswordAttempts}}</span>
<button
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['lockout_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)"
mat-icon-button (click)="incrementMaxAttempts()">
<span>{{ lockoutData?.maxPasswordAttempts }}</span>
<button [disabled]="(['policy.write'] | hasRole | async) === false" mat-icon-button (click)="incrementMaxAttempts()">
<mat-icon>add</mat-icon>
</button>
</div>
@@ -44,12 +42,16 @@
</div>
<div class="btn-container">
<button (click)="savePolicy()"
[disabled]="serviceType === PolicyComponentServiceType.MGMT && ((['lockout_policy'] | hasFeature | async) === false || (['policy.write'] | hasRole | async) === false) || (serviceType === PolicyComponentServiceType.ADMIN && (['policy.write'] | hasRole | async) === false)"
color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
<button
(click)="savePolicy()"
[disabled]="(['policy.write'] | hasRole | async) === false"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="security"></cnsl-policy-grid>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -9,7 +9,6 @@ import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
@@ -32,7 +31,6 @@ import { PasswordLockoutPolicyComponent } from './password-lockout-policy.compon
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
HasFeaturePipeModule,
PolicyGridModule,
InfoSectionModule,
],
@@ -1,53 +1,64 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.PRIVACY_POLICY.TITLE' | translate"
[description]="'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="orgName; else iam">{{orgName}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<cnsl-detail-layout
[hasBackButton]="true"
[title]="'POLICY.PRIVACY_POLICY.TITLE' | translate"
[description]="'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate"
>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="orgName; else iam">{{ orgName }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['privacy_policy'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'privacy_policy'})"></span>
</cnsl-info-section>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<div class="divider"></div>
<div class="content">
<form *ngIf="form" [formGroup]="form">
<div>
<form *ngIf="form" [formGroup]="form" class="content">
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.TOSLINK' | translate }}</cnsl-label>
<input cnslInput name="tosLink" formControlName="tosLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{key: 'tosLink'}"></template>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'tosLink' }"></template>
</cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.POLICYLINK' | translate }}</cnsl-label>
<input cnslInput name="privacyLink" formControlName="privacyLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{key: 'privacyLink'}"></template>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'privacyLink' }"></template>
</cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.HELPLINK' | translate }}</cnsl-label>
<input cnslInput name="helpLink" formControlName="helpLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{key: 'helpLink'}"></template>
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'helpLink' }"></template>
</cnsl-form-field>
</form>
</div>
<div class="actions">
<button *ngIf="privacyPolicy && privacyPolicy.isDefault === false" class="reset-button"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && ((['privacy_policy'] | hasFeature | async) === false) || (canWrite$ | async) === false)"
(click)="resetDefault()" color="warn" type="submit" mat-stroked-button><i class="las la-history"></i> {{
'ACTIONS.RESETDEFAULT' | translate }}</button>
<button class="save-button"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && ((['privacy_policy'] | hasFeature | async) === false) || (canWrite$ | async) === false)"
(click)="saveCurrentMessage()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
<button
*ngIf="privacyPolicy && privacyPolicy.isDefault === false"
class="reset-button"
[disabled]="(canWrite$ | async) === false"
(click)="resetDefault()"
color="warn"
type="submit"
mat-stroked-button
>
<i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}
</button>
<button
class="save-button"
[disabled]="(canWrite$ | async) === false"
(click)="saveCurrentMessage()"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
<cnsl-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></cnsl-policy-grid>
@@ -55,9 +66,14 @@
<ng-template #templateRef let-key="key">
<div class="chips">
<div class="chip" cnslCopyToClipboard [valueToCopy]="LANGPLACEHOLDER" (copiedValue)="copied = $event"
(click)="addChip(key, LANGPLACEHOLDER)">
<span class="key">{{LANGPLACEHOLDER}}</span>
<div
class="chip"
cnslCopyToClipboard
[valueToCopy]="LANGPLACEHOLDER"
(copiedValue)="copied = $event"
(click)="addChip(key, LANGPLACEHOLDER)"
>
<span class="key">{{ LANGPLACEHOLDER }}</span>
<i *ngIf="copied !== LANGPLACEHOLDER" class="las la-clipboard"></i>
<i *ngIf="copied === LANGPLACEHOLDER" class="las la-clipboard-check"></i>
</div>
@@ -26,6 +26,13 @@
.content {
padding-top: 1rem;
display: grid;
grid-template-columns: 1fr;
column-gap: 1rem;
@media only screen and (min-width: 800px) {
grid-template-columns: 1fr 1fr 1fr;
}
}
.privacy-policy-formfield {
@@ -14,7 +14,6 @@ import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy
import { HasRoleModule } from '../../../directives/has-role/has-role.module';
import { DetailLayoutModule } from '../../../modules/detail-layout/detail-layout.module';
import { InputModule } from '../../../modules/input/input.module';
import { HasFeaturePipeModule } from '../../../pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from '../../../pipes/has-role-pipe/has-role-pipe.module';
import { FormFieldModule } from '../../form-field/form-field.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
@@ -35,7 +34,6 @@ import { PrivacyPolicyComponent } from './privacy-policy.component';
FormFieldModule,
CopyToClipboardModule,
MatButtonModule,
HasFeaturePipeModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
@@ -1,18 +1,14 @@
<cnsl-detail-layout [hasBackButton]="true" [title]="'POLICY.PRIVATELABELING.TITLE' | translate">
<p class="policy-applied-to" sub>{{'POLICY.APPLIEDTO' | translate}}:
<strong *ngIf="org; else iam">{{org.name}}</strong>
<ng-template #iam><strong>{{'MENU.SYSTEM' | translate}}</strong>
<p class="policy-applied-to" sub>
{{ 'POLICY.APPLIEDTO' | translate }}:
<strong *ngIf="org; else iam">{{ org.name }}</strong>
<ng-template #iam
><strong>{{ 'MENU.INSTANCE' | translate }}</strong>
</ng-template>
</p>
<div class="privatelabeling-policy">
<cnsl-info-section *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</cnsl-info-section>
<cnsl-info-section
*ngIf="serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false"
[featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span>
</cnsl-info-section>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
@@ -23,30 +19,35 @@
<mat-button-toggle [value]="Theme.LIGHT">
<div class="toggle-row">
<i class="icon las la-sun"></i>
<span>{{'POLICY.PRIVATELABELING.LIGHT' | translate}}</span>
<span>{{ 'POLICY.PRIVATELABELING.LIGHT' | translate }}</span>
<div *ngIf="theme === Theme.LIGHT" class="current-dot"></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]=" Theme.DARK">
<mat-button-toggle [value]="Theme.DARK">
<div class="toggle-row">
<i class="icon las la-moon"></i>
<span> {{'POLICY.PRIVATELABELING.DARK' | translate}}</span>
<span> {{ 'POLICY.PRIVATELABELING.DARK' | translate }}</span>
<div *ngIf="theme === Theme.DARK" class="current-dot"></div>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
<mat-button-toggle-group class="theme-toggle" class="buttongroup" [(ngModel)]="view" name="displayview"
aria-label="Display View">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="view"
name="displayview"
aria-label="Display View"
>
<mat-button-toggle [value]="View.PREVIEW">
<div class="toggle-row">
<span>{{'POLICY.PRIVATELABELING.VIEWS.PREVIEW' | translate}}</span>
<span>{{ 'POLICY.PRIVATELABELING.VIEWS.PREVIEW' | translate }}</span>
<div *ngIf="view === View.PREVIEW" class="current-dot"></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="View.CURRENT">
<div class="toggle-row">
<span> {{'POLICY.PRIVATELABELING.VIEWS.CURRENT' | translate}}</span>
<span> {{ 'POLICY.PRIVATELABELING.VIEWS.CURRENT' | translate }}</span>
<div *ngIf="view === View.CURRENT" class="current-dot"></div>
</div>
</mat-button-toggle>
@@ -55,22 +56,31 @@
<span class="fill-space"></span>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button class="pl-action-button"
<button
class="pl-action-button"
*ngIf="view === View.CURRENT && serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<button *ngIf="view === View.PREVIEW" class="pl-action-button"
[disabled]="(serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false)"
mat-raised-button color="primary" (click)="activatePolicy()">
{{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}}
<button
*ngIf="view === View.PREVIEW"
class="pl-action-button"
mat-raised-button
color="primary"
(click)="activatePolicy()"
>
{{ 'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate }}
</button>
</div>
<cnsl-info-section *ngIf="view === View.PREVIEW" class="desc cnsl-secondary-text">
{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}
{{ 'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate }}
</cnsl-info-section>
<div *ngIf="previewData && data" class="lab-policy-content">
@@ -82,42 +92,67 @@
<i class="icon las la-image"></i>
<span>Logos</span>
<span class="space"></span>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.DARK">({{'POLICY.PRIVATELABELING.DARK' |
translate}})</small>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.LIGHT">({{'POLICY.PRIVATELABELING.LIGHT' |
translate}})</small>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.DARK"
>({{ 'POLICY.PRIVATELABELING.DARK' | translate }})</small
>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.LIGHT"
>({{ 'POLICY.PRIVATELABELING.LIGHT' | translate }})</small
>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div>
<p class="cnsl-secondary-button">{{'POLICY.PRIVATELABELING.USEOFLOGO' | translate}}</p>
<p class="cnsl-secondary-button">{{ 'POLICY.PRIVATELABELING.USEOFLOGO' | translate }}</p>
<cnsl-info-section *ngIf="view !== View.CURRENT" class="max-size-desc"> {{'POLICY.PRIVATELABELING.MAXSIZE' |
translate}}
<cnsl-info-section *ngIf="view !== View.CURRENT" class="max-size-desc">
{{ 'POLICY.PRIVATELABELING.MAXSIZE' | translate }}
</cnsl-info-section>
<cnsl-info-section *ngIf="view !== View.CURRENT" class="max-size-desc">
{{'POLICY.PRIVATELABELING.EMAILNOSVG' | translate}}
{{ 'POLICY.PRIVATELABELING.EMAILNOSVG' | translate }}
</cnsl-info-section>
<div class="logo-view" [attr.data-e2e]="'image-part-logo'">
<span class="label cnsl-secondary-text">Logo</span>
<div class="img-wrapper">
<ng-container
*ngIf="view === View.PREVIEW ? (theme === Theme.DARK ? previewData.logoUrlDark : previewData.logoUrl): (theme === Theme.DARK ? data.logoUrlDark : data.logoUrl) as logoSrc; else addLogoButton">
*ngIf="
view === View.PREVIEW
? theme === Theme.DARK
? previewData.logoUrlDark
: previewData.logoUrl
: theme === Theme.DARK
? data.logoUrlDark
: data.logoUrl as logoSrc;
else addLogoButton
"
>
<img [src]="logoSrc" alt="logo" />
<button class="dl-btn" mat-icon-button color="warn" (click)="deleteAsset(AssetType.LOGO, theme)"
[disabled]="view === View.CURRENT || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false)"
matTooltip="{{'ACTIONS.DELETE' | translate}}">
<button
class="dl-btn"
mat-icon-button
color="warn"
(click)="deleteAsset(AssetType.LOGO, theme)"
[disabled]="view === View.CURRENT"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
</button>
</ng-container>
<ng-template #addLogoButton>
<input #selectedFile style="display: none;" class="file-input" type="file"
(change)="onDropLogo(theme, $any($event.target).files)">
<button mat-icon-button matTooltip="{{'POLICY.PRIVATELABELING.BTN' | translate}}"
*ngIf="view !== View.CURRENT && (serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async)))"
(click)="$event.preventDefault(); selectedFile.click();">
<input
#selectedFile
style="display: none"
class="file-input"
type="file"
(change)="onDropLogo(theme, $any($event.target).files)"
/>
<button
mat-icon-button
matTooltip="{{ 'POLICY.PRIVATELABELING.BTN' | translate }}"
*ngIf="view !== View.CURRENT"
(click)="$event.preventDefault(); selectedFile.click()"
>
<mat-icon>add</mat-icon>
</button>
</ng-template>
@@ -128,20 +163,43 @@
<span class="label cnsl-secondary-text">Icon</span>
<div class="img-wrapper icon">
<ng-container
*ngIf="view === View.PREVIEW ? (theme === Theme.DARK ? previewData.iconUrlDark : previewData.iconUrl): (theme === Theme.DARK ? data.iconUrlDark : data.iconUrl) as iconSrc; else addIconButton">
*ngIf="
view === View.PREVIEW
? theme === Theme.DARK
? previewData.iconUrlDark
: previewData.iconUrl
: theme === Theme.DARK
? data.iconUrlDark
: data.iconUrl as iconSrc;
else addIconButton
"
>
<img [src]="iconSrc" alt="icon" />
<button class="dl-btn" mat-icon-button color="warn" (click)="deleteAsset(AssetType.ICON, theme)"
[disabled]="view === View.CURRENT || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false)"
matTooltip="{{'ACTIONS.DELETE' | translate}}">
<button
class="dl-btn"
mat-icon-button
color="warn"
(click)="deleteAsset(AssetType.ICON, theme)"
[disabled]="view === View.CURRENT"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
</button>
</ng-container>
<ng-template #addIconButton>
<input #selectedIconFile style="display: none;" class="file-input" type="file"
(change)="onDropIcon(theme, $any($event.target).files)">
<button mat-icon-button matTooltip="{{'POLICY.PRIVATELABELING.BTN' | translate}}"
*ngIf="view !== View.CURRENT && (serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async)))"
(click)="$event.preventDefault(); selectedIconFile.click();">
<input
#selectedIconFile
style="display: none"
class="file-input"
type="file"
(change)="onDropIcon(theme, $any($event.target).files)"
/>
<button
mat-icon-button
matTooltip="{{ 'POLICY.PRIVATELABELING.BTN' | translate }}"
*ngIf="view !== View.CURRENT"
(click)="$event.preventDefault(); selectedIconFile.click()"
>
<mat-icon>add</mat-icon>
</button>
</ng-template>
@@ -155,12 +213,14 @@
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-palette"></i>
<span>{{'POLICY.PRIVATELABELING.COLORS' | translate}}</span>
<span>{{ 'POLICY.PRIVATELABELING.COLORS' | translate }}</span>
<span class="space"></span>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.DARK">({{'POLICY.PRIVATELABELING.DARK' |
translate}})</small>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.LIGHT">({{'POLICY.PRIVATELABELING.LIGHT' |
translate}})</small>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.DARK"
>({{ 'POLICY.PRIVATELABELING.DARK' | translate }})</small
>
<small class="cnsl-secondary-text" *ngIf="theme === Theme.LIGHT"
>({{ 'POLICY.PRIVATELABELING.LIGHT' | translate }})</small
>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
@@ -168,29 +228,49 @@
<ng-container *ngIf="theme === Theme.DARK">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.BACKGROUNDDARK"
(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()" name="Background Color"
[color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.BACKGROUNDDARK"
(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()"
name="Background Color"
[color]="data.backgroundColorDark"
[previewColor]="previewData.backgroundColorDark"
></cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColorDark = $event; savePolicy()" name="Primary Color"
[color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark">
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColorDark = $event; savePolicy()"
name="Primary Color"
[color]="data.primaryColorDark"
[previewColor]="previewData.primaryColorDark"
>
</cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.WARN"
(previewChanged)="previewData.warnColorDark = $event; savePolicy()" name="Warn Color"
[color]="data.warnColorDark" [previewColor]="previewData.warnColorDark">
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.WARN"
(previewChanged)="previewData.warnColorDark = $event; savePolicy()"
name="Warn Color"
[color]="data.warnColorDark"
[previewColor]="previewData.warnColorDark"
>
</cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.FONTDARK"
(previewChanged)="previewData.fontColorDark = $event; savePolicy()" name="Font Color"
[color]="data.fontColorDark" [previewColor]="previewData.fontColorDark">
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.FONTDARK"
(previewChanged)="previewData.fontColorDark = $event; savePolicy()"
name="Font Color"
[color]="data.fontColorDark"
[previewColor]="previewData.fontColorDark"
>
</cnsl-color>
</div>
</div>
@@ -199,28 +279,48 @@
<ng-container *ngIf="theme === Theme.LIGHT">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.BACKGROUNDLIGHT"
(previewChanged)="previewData.backgroundColor = $event; savePolicy()" name="Background Color"
[color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.BACKGROUNDLIGHT"
(previewChanged)="previewData.backgroundColor = $event; savePolicy()"
name="Background Color"
[color]="data.backgroundColor"
[previewColor]="previewData.backgroundColor"
></cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColor = $event; savePolicy()" name="Primary Color"
[color]="data.primaryColor" [previewColor]="previewData.primaryColor">
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.PRIMARY"
(previewChanged)="previewData.primaryColor = $event; savePolicy()"
name="Primary Color"
[color]="data.primaryColor"
[previewColor]="previewData.primaryColor"
>
</cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.WARN" name="Warn Color"
(previewChanged)="previewData.warnColor= $event; savePolicy()" [color]="data.warnColor"
[previewColor]="previewData.warnColor"></cnsl-color>
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.WARN"
name="Warn Color"
(previewChanged)="previewData.warnColor = $event; savePolicy()"
[color]="data.warnColor"
[previewColor]="previewData.warnColor"
></cnsl-color>
</div>
<div class="color">
<cnsl-color [disabled]="view === View.CURRENT" [colorType]="ColorType.FONTLIGHT"
(previewChanged)="previewData.fontColor = $event; savePolicy()" name="Font Color"
[color]="data.fontColor" [previewColor]="previewData.fontColor"></cnsl-color>
<cnsl-color
[disabled]="view === View.CURRENT"
[colorType]="ColorType.FONTLIGHT"
(previewChanged)="previewData.fontColor = $event; savePolicy()"
name="Font Color"
[color]="data.fontColor"
[previewColor]="previewData.fontColor"
></cnsl-color>
</div>
</div>
</ng-container>
@@ -231,33 +331,53 @@
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-font"></i>
{{'POLICY.PRIVATELABELING.FONT' | translate}}
{{ 'POLICY.PRIVATELABELING.FONT' | translate }}
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="fonts">
<cnsl-info-section class="info-section">{{'POLICY.PRIVATELABELING.FONTINLOGINONLY' | translate}}
<cnsl-info-section class="info-section"
>{{ 'POLICY.PRIVATELABELING.FONTINLOGINONLY' | translate }}
</cnsl-info-section>
<div class="font-preview" *ngIf="previewData.fontUrl; else addFontButton">
<mat-icon class="icon">text_fields</mat-icon>
<span class="fill-space"></span>
<button class="dl-btn"
[disabled]="view === View.CURRENT || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false)"
mat-icon-button color="warn" (click)="deleteFont()" matTooltip="{{'ACTIONS.DELETE' | translate}}">
<button
class="dl-btn"
[disabled]="view === View.CURRENT"
mat-icon-button
color="warn"
(click)="deleteFont()"
matTooltip="{{ 'ACTIONS.DELETE' | translate }}"
>
<i class="las la-ban"></i>
</button>
</div>
<ng-template #addFontButton>
<div class="font-add" cnslDropzone (hovered)="toggleHoverFont($event)" (dropped)="onDropFont($event)"
[class.hovering]="isHoveringOverFont">
<input #selectedFontFile style="display: none;" class="file-input" type="file"
(change)="onDropFont($any($event.target).files)">
<a class="btn" mat-icon-button
*ngIf="view !== View.CURRENT && (serviceType === PolicyComponentServiceType.ADMIN || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async)))"
(click)="selectedFontFile.click()" matTooltip="{{'POLICY.PRIVATELABELING.BTN' | translate}}">
<div
class="font-add"
cnslDropzone
(hovered)="toggleHoverFont($event)"
(dropped)="onDropFont($event)"
[class.hovering]="isHoveringOverFont"
>
<input
#selectedFontFile
style="display: none"
class="file-input"
type="file"
(change)="onDropFont($any($event.target).files)"
/>
<a
class="btn"
mat-icon-button
*ngIf="view !== View.CURRENT"
(click)="selectedFontFile.click()"
matTooltip="{{ 'POLICY.PRIVATELABELING.BTN' | translate }}"
>
<mat-icon>add</mat-icon>
</a>
</div>
@@ -270,37 +390,31 @@
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-universal-access"></i>
{{'POLICY.PRIVATELABELING.ADVANCEDBEHAVIOR' | translate}}
{{ 'POLICY.PRIVATELABELING.ADVANCEDBEHAVIOR' | translate }}
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="adv-container" *ngIf="previewData">
<ng-container
*ngIf="view !== View.CURRENT && serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" [type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.private_label'})"></span>
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[disabled]="view === View.CURRENT || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) === false)"
<mat-slide-toggle
class="toggle"
color="primary"
ngDefaultControl
[disabled]="view === View.CURRENT"
[(ngModel)]="view === View.CURRENT ? data.hideLoginNameSuffix : previewData.hideLoginNameSuffix"
(change)="savePolicy()">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
(change)="savePolicy()"
>
{{ 'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate }}
</mat-slide-toggle>
<ng-container
*ngIf="view !== View.CURRENT && serviceType === PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) === false">
<cnsl-info-section [featureLink]="['/org/features']" class="info" type]="InfoSectionType.WARN">
<span [innerHTML]="'FEATURES.NOTAVAILABLE' | translate: ({value: 'label_policy.watermark'})"></span>
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[disabled]="view === View.CURRENT || (serviceType === PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) === false)"
<mat-slide-toggle
class="toggle"
color="primary"
ngDefaultControl
[disabled]="view === View.CURRENT"
[(ngModel)]="view === View.CURRENT ? data.disableWatermark : previewData.disableWatermark"
(change)="savePolicy()">
{{'POLICY.DATA.DISABLEWATERMARK' | translate}}
(change)="savePolicy()"
>
{{ 'POLICY.DATA.DISABLEWATERMARK' | translate }}
</mat-slide-toggle>
</div>
</mat-expansion-panel>
@@ -310,8 +424,12 @@
<div class="preview-wrapper">
<div class="col">
<cnsl-preview [refresh]="refreshPreview" [theme]="theme" class="preview"
[policy]="view === View.PREVIEW ? previewData : data">
<cnsl-preview
[refresh]="refreshPreview"
[theme]="theme"
class="preview"
[policy]="view === View.PREVIEW ? previewData : data"
>
</cnsl-preview>
</div>
</div>
@@ -320,4 +438,4 @@
<cnsl-policy-grid class="grid" [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text">
</cnsl-policy-grid>
</div>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -119,6 +119,7 @@
.expansion {
box-shadow: none;
border: 1px solid $p-border-color;
background-color: map-get($background, cards);
.header {
justify-content: flex-start;
@@ -2,17 +2,17 @@ import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Injector, OnDestroy, OnInit, Type } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject, Subscription } from 'rxjs';
import { switchMap, take, takeUntil } from 'rxjs/operators';
import { switchMap, takeUntil } from 'rxjs/operators';
import {
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
UpdateLabelPolicyRequest,
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
UpdateLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddCustomLabelPolicyRequest,
GetLabelPolicyResponse as MgmtGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest,
AddCustomLabelPolicyRequest,
GetLabelPolicyResponse as MgmtGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
@@ -371,31 +371,26 @@ export class PrivateLabelingPolicyComponent implements OnInit, OnDestroy {
public fetchData(): void {
this.loading = true;
this.authService
.canUseFeature(['label_policy.private_label'])
.pipe(take(1))
.subscribe((canUse) => {
this.getPreviewData()
.then((data) => {
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
}
})
.catch((error) => {
this.toast.showError(error);
});
this.getPreviewData()
.then((data) => {
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
}
})
.catch((error) => {
this.toast.showError(error);
});
this.getData()
.then((data) => {
if (data.policy) {
this.data = data.policy;
this.loading = false;
}
})
.catch((error) => {
this.toast.showError(error);
});
this.getData()
.then((data) => {
if (data.policy) {
this.data = data.policy;
this.loading = false;
}
})
.catch((error) => {
this.toast.showError(error);
});
}
@@ -12,7 +12,6 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { ColorChromeModule } from 'ngx-color/chrome';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { DropzoneModule } from '../../../directives/dropzone/dropzone.module';
import { DetailLayoutModule } from '../../detail-layout/detail-layout.module';
@@ -46,7 +45,6 @@ import { PrivateLabelingPolicyComponent } from './private-labeling-policy.compon
PolicyGridModule,
MatExpansionModule,
InfoSectionModule,
HasFeaturePipeModule,
],
})
export class PrivateLabelingPolicyModule {}
@@ -3,39 +3,39 @@
<p class="top-desc cnsl-secondary-text">{{'POLICY.DESCRIPTION' | translate}}</p>
<div class="tags" *ngIf="tags">
<span class="tag" [ngClass]="{'active': tag === tagForFilter}"
(click)="tagForFilter !== tag ? tagForFilter = tag : tagForFilter = ''" *ngFor="let tag of tags"><i
class="las la-hashtag"></i>{{tag}}</span>
<span class="tag" [ngClass]="{'active': tag === tagForFilter}"
(click)="tagForFilter !== tag ? tagForFilter = tag : tagForFilter = ''" *ngFor="let tag of tags"><i
class="las la-hashtag"></i>{{tag}}</span>
</div>
<div class="row-lyt">
<ng-container *ngFor="let policy of filteredPolicies">
<ng-template cnslHasRole
[hasRole]="type === PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type === PolicyComponentServiceType.MGMT ? policy.orgWithRole : []">
<div class="p-item card" @policy [attr.data-e2e]="'policy-card'">
<div class="avatar {{policy.color}}">
<mat-icon *ngIf="policy.svgIcon" class="icon" [svgIcon]="policy.svgIcon"></mat-icon>
<i *ngIf="policy.icon" class="icon {{policy.icon}}"></i>
</div>
<div class="title">
<span>{{policy.i18nTitle | translate}}</span>
</div>
<ng-container *ngFor="let policy of filteredPolicies">
<ng-template cnslHasRole
[hasRole]="type === PolicyComponentServiceType.ADMIN ? policy.iamWithRole : type === PolicyComponentServiceType.MGMT ? policy.orgWithRole : []">
<div class="p-item card" @policy [attr.data-e2e]="'policy-card'">
<div class="avatar {{policy.color}}">
<mat-icon *ngIf="policy.svgIcon" class="mat-icon" [svgIcon]="policy.svgIcon"></mat-icon>
<i *ngIf="policy.icon" class="icon {{policy.icon}}"></i>
</div>
<div class="title">
<span>{{policy.i18nTitle | translate}}</span>
</div>
<p class="desc cnsl-secondary-text">
{{policy.i18nDesc | translate}}</p>
<p class="desc cnsl-secondary-text">
{{policy.i18nDesc | translate}}</p>
<span class="fill-space"></span>
<div class="tags" *ngIf="policy.tags">
<span class="tag cnsl-secondary-text" *ngFor="let tag of policy.tags"
(click)="tagForFilter !== tag ? tagForFilter = tag : tagForFilter = ''"><i
class="las la-hashtag"></i>{{tag}}</span>
</div>
<div class="btn-wrapper">
<button
[routerLink]="type === PolicyComponentServiceType.ADMIN ? policy.iamRouterLink : type === PolicyComponentServiceType.MGMT ? policy.orgRouterLink : null"
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
</div>
</div>
</ng-template>
</ng-container>
<span class="fill-space"></span>
<div class="tags" *ngIf="policy.tags">
<span class="tag cnsl-secondary-text" *ngFor="let tag of policy.tags"
(click)="tagForFilter !== tag ? tagForFilter = tag : tagForFilter = ''"><i
class="las la-hashtag"></i>{{tag}}</span>
</div>
<div class="btn-wrapper">
<button
[routerLink]="type === PolicyComponentServiceType.ADMIN ? policy.iamRouterLink : type === PolicyComponentServiceType.MGMT ? policy.orgRouterLink : null"
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
</div>
</div>
</ng-template>
</ng-container>
</div>
@@ -89,10 +89,15 @@ h2 {
background: linear-gradient(40deg, #1f2937, #111827);
}
.mat-icon {
height: 2rem;
width: 2rem;
color: white;
}
.icon,
i {
font-size: 2.5rem;
height: 2.5rem;
line-height: 2.5rem;
color: white;
}
@@ -6,28 +6,24 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasFeaturePipeModule } from 'src/app/pipes/has-feature-pipe/has-feature-pipe.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../info-section/info-section.module';
import { PolicyGridComponent } from './policy-grid.component';
@NgModule({
declarations: [PolicyGridComponent],
imports: [
CommonModule,
HasRolePipeModule,
HasRoleModule,
TranslateModule,
RouterModule,
MatButtonModule,
MatIconModule,
MatTooltipModule,
InfoSectionModule,
HasFeaturePipeModule,
],
exports: [
PolicyGridComponent,
],
declarations: [PolicyGridComponent],
imports: [
CommonModule,
HasRolePipeModule,
HasRoleModule,
TranslateModule,
RouterModule,
MatButtonModule,
MatIconModule,
MatTooltipModule,
InfoSectionModule,
],
exports: [PolicyGridComponent],
})
export class PolicyGridModule { }
export class PolicyGridModule {}
@@ -1,22 +1,32 @@
<div class="sidenav-container">
<div class="sidenav-settings-list">
<div class="sidenav-sticky-rel">
<button *ngIf="currentSetting !== undefined" (click)="value = undefined"
class="sidenav-setting-list-element mob-only" [ngClass]="{'active': true}">
<button
*ngIf="currentSetting !== undefined"
(click)="value = undefined"
class="sidenav-setting-list-element mob-only"
[ngClass]="{ active: true }"
>
<i class="las la-angle-left"></i>
<span>{{'USER.SETTINGS.TITLE' | translate}}</span>
<span>{{ 'USER.SETTINGS.TITLE' | translate }}</span>
</button>
<ng-container *ngFor="let setting of settingsList">
<ng-template *ngIf="setting.featureRequired, else btn" cnslHasFeature [hasFeature]="setting.featureRequired">
<button (click)="value = setting.id" class="sidenav-setting-list-element hide-on-mobile"
[ngClass]="{'active': currentSetting === setting.id, 'show': currentSetting === undefined}">
<span>{{setting.i18nKey | translate}}</span>
<ng-container *ngIf="setting.featureRequired; else btn">
<button
(click)="value = setting.id"
class="sidenav-setting-list-element hide-on-mobile"
[ngClass]="{ active: currentSetting === setting.id, show: currentSetting === undefined }"
>
<span>{{ setting.i18nKey | translate }}</span>
</button>
</ng-template>
</ng-container>
<ng-template #btn>
<button (click)="value = setting.id" class="sidenav-setting-list-element hide-on-mobile"
[ngClass]="{'active': currentSetting === setting.id, 'show': currentSetting === undefined}">
<span>{{setting.i18nKey | translate}}</span>
<button
(click)="value = setting.id"
class="sidenav-setting-list-element hide-on-mobile"
[ngClass]="{ active: currentSetting === setting.id, show: currentSetting === undefined }"
>
<span>{{ setting.i18nKey | translate }}</span>
</button>
</ng-template>
</ng-container>
@@ -28,4 +38,4 @@
<ng-content></ng-content>
</div>
</div>
</div>
</div>
@@ -1,17 +1,23 @@
<span class="title" mat-dialog-title>{{data.i18nTitle | translate}}</span>
<span class="title" mat-dialog-title>{{ data.i18nTitle | translate }}</span>
<div mat-dialog-content>
<cnsl-project-roles-table class="role-table" *ngIf="projectId"
[displayedColumns]="['select', 'key', 'displayname', 'group']" (changedSelection)="selectRoles($event)"
[projectId]="projectId" [grantId]="grantId" [selectedKeys]="selectedRoleKeysList"
[showSelectionActionButton]="false">
<cnsl-project-roles-table
class="role-table"
*ngIf="projectId"
[displayedColumns]="['select', 'key', 'displayname', 'group']"
(changedSelection)="selectRoles($event)"
[projectId]="projectId"
[grantId]="grantId"
[selectedKeys]="selectedRoleKeysList"
[showSelectionActionButton]="false"
>
</cnsl-project-roles-table>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
{{'ACTIONS.CANCEL' | translate}}
{{ 'ACTIONS.CANCEL' | translate }}
</button>
<span class="fill-space"></span>
<button mat-raised-button color="primary" class="ok-button" (click)="closeDialogWithSuccess()">
{{'ACTIONS.CHANGE' | translate}}
{{ 'ACTIONS.CHANGE' | translate }}
</button>
</div>
</div>
@@ -1,14 +0,0 @@
<div *ngIf="features" class="tier card">
<img class="logo dark" alt="zitadel logo" src="../assets/images/zitadel-logo-solo-light-icon.svg" />
<img alt="zitadel logo" class="logo light" src="../assets/images/zitadel-logo-solo-dark-icon.svg" />
<div class="tier-text" *ngIf="features.tier">
<p class="title"><strong>{{features.tier.name}}</strong></p>
<p>{{features.tier?.description}}</p>
<p class="state" [ngClass]="{'active': features.tier.state === FeaturesState.FEATURES_STATE_ACTIVE}">
{{'FEATURES.TIERSTATES.'+features.tier.state | translate}}</p>
<p>{{features.tier?.statusInfo}}</p>
</div>
<span class="fill-space"></span>
<button mat-raised-button color="primary"
[routerLink]="[ iam ? '/system/features' : '/org/features']">{{'FEATURES.BTN-EDIT' | translate}}</button>
</div>
@@ -1,59 +0,0 @@
@mixin tier-theme($theme) {
$is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background);
.tier {
display: flex;
flex-direction: row;
padding: 1rem;
border-radius: 0.5rem;
margin-bottom: 0.5rem;
.ext {
margin-right: 0.5rem;
align-self: center;
color: inherit;
}
.logo {
margin-right: 1rem;
max-height: 40px;
object-fit: contain;
filter: grayscale(1);
transition: filter 0.3s ease;
&.dark {
display: if($is-dark-theme, inline-block, none);
}
&.light {
display: if($is-dark-theme, none, inline-block);
}
}
.tier-text {
display: flex;
flex-direction: column;
align-items: flex-start;
p:not(.state) {
margin: 0;
font-size: 14px;
}
.title {
font-size: 16px;
margin-bottom: 0.5rem;
}
}
.fill-space {
flex: 1;
}
button {
// min-width: 200px;
align-self: center;
}
}
}
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ZitadelTierComponent } from './zitadel-tier.component';
describe('ZitadelTierComponent', () => {
let component: ZitadelTierComponent;
let fixture: ComponentFixture<ZitadelTierComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ZitadelTierComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ZitadelTierComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,14 +0,0 @@
import { Component, Input } from '@angular/core';
import { Features, FeaturesState } from 'src/app/proto/generated/zitadel/features_pb';
@Component({
selector: 'cnsl-zitadel-tier',
templateUrl: './zitadel-tier.component.html',
styleUrls: ['./zitadel-tier.component.scss'],
})
export class ZitadelTierComponent {
@Input() public features!: Features.AsObject;
@Input() public iam: boolean = false;
FeaturesState: any = FeaturesState;
}
@@ -1,31 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { ZitadelTierComponent } from './zitadel-tier.component';
@NgModule({
declarations: [
ZitadelTierComponent,
],
imports: [
CommonModule,
RouterModule,
MatButtonModule,
MatIconModule,
HasRoleModule,
HasRolePipeModule,
TranslateModule,
],
exports: [
ZitadelTierComponent,
],
})
export class ZitadelTierModule { }