feat: label policy (#1708)

* feat: label policy proto extension

* feat: label policy and activate event

* feat: label policy asset events

* feat: label policy asset commands

* feat: add storage key

* feat: storage key validation

* feat: label policy asset tests

* feat: label policy query side

* feat: avatar

* feat: avatar event

* feat: human avatar

* feat: avatar read side

* feat: font on iam label policy

* feat: label policy font

* feat: possiblity to create bucket on put file

* uplaoder

* login policy logo

* set bucket prefix

* feat: avatar upload

* feat: avatar upload

* feat: use assets on command side

* feat: fix human avatar removed event

* feat: remove human avatar

* feat: mock asset storage

* feat: remove human avatar

* fix(operator): add configuration of asset storage to zitadel operator

* feat(console): private labeling policy (#1697)

* private labeling component, routing, preview

* font, colors, upload, i18n

* show logo

* fix: uniqueness (#1710)

* fix: uniqueconstraint to lower

* feat: change org

* feat: org change test

* feat: change org

* fix: tests

* fix: handle domain claims correctly

* feat: update org

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: handle domain claimed event correctly for service users (#1711)

* fix: handle domain claimed event correctly on user view

* fix: ignore domain claimed events for email notifications

* fix: change org

* handle org changed in read models correctly

* fix: change org in user grant handler

Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>

* fix: correct value (#1695)

* docs(api): correct link (#1712)

* upload service

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
Co-authored-by: Florian Forster <florian@caos.ch>

* feat: fix tests,

* feat: remove assets from label policy

* fix npm, set environment

* lint ts

* remove stylelinting

* fix(operator): add mapping for console with changed unit tests

* fix(operator): add secrets as env variables to pod

* feat: remove human avatar

* fix(operator): add secrets as env variables to pod

* feat: map label policy

* feat: labelpolicy, admin, mgmt, adv settings (#1715)

* fetch label policy, mgmt, admin service

* feat: advanced beh, links, add, update

* lint ts

* feat: watermark

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: remove human avatar

* feat: custom css

* css

* css

* css

* css

* css

* getobject

* feat: dynamic handler

* feat: varibale css

* content info

* css overwrite

* feat: variablen css

* feat: generate css file

* feat: dark mode

* feat: dark mode

* fix logo css

* feat: upload logos

* dark mode with cookie

* feat: handle images in login

* avatar css and begin font

* feat: avatar

* feat: user avatar

* caching of static assets in login

* add avatar.js to main.html

* feat: header dont show logo if no url

* feat: label policy colors

* feat: mock asset storage

* feat: mock asset storage

* feat: fix tests

* feat: user avatar

* feat: header logo

* avatar

* avatar

* make it compatible with go 1.15

* feat: remove unused logos

* fix handler

* fix: styling error handling

* fonts

* fix: download func

* switch to mux

* fix: change upload api to assets

* fix build

* fix: download avatar

* fix: download logos

* fix: my avatar

* font

* fix: remove error msg popup possibility

* fix: docs

* fix: svalidate colors

* rem msg popup from frontend

* fix: email with private labeling

* fix: tests

* fix: email templates

* fix: change migration version

* fix: fix duplicate imports

* fix(console): assets, service url, upload, policy current and preview  (#1781)

* upload endpoint, layout

* fetch current, preview, fix upload

* cleanup private labeling

* fix linting

* begin generated asset handler

* generate asset api in dockerfile

* features for label policy

* features for label policy

* features

* flag for asset generator

* change asset generator flag

* fix label policy view in grpc

* fix: layout, activate policy (#1786)

* theme switcher up on top

* change layout

* activate policy

* feat(console): label policy back color, layout (#1788)

* theme switcher up on top

* change layout

* activate policy

* fix overwrite value fc

* reset policy, reset service

* autosave policy, preview desc, layout impv

* layout, i18n

* background colors, inject material styles

* load images

* clean, lint

* fix layout

* set custom hex

* fix content size conversion

* remove font format in generated css

* fix features for assets

* fix(console): label policy colors, image downloads, preview (#1804)

* load images

* colors, images binding

* lint

* refresh emitter

* lint

* propagate font colors

* upload error handling

* label policy feature check

* add blob in csp for console

* log

* fix: feature edits for label policy, refresh state on upload (#1807)

* show error on load image, stop spinner

* fix merge

* fix migration versions

* fix assets

* fix csp

* fix background color

* scss

* fix build

* lint scss

* fix statik for console

* fix features check for label policy

* cleanup

* lint

* public links

* fix notifications

* public links

* feat: merge main

* feat: fix translation files

* fix migration

* set api domain

* fix logo in email

* font face in email

* font face in email

* validate assets on upload

* cleanup

* add missing translations

* add missing translations

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Stefan Benz <stefan@caos.ch>
Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
Fabi
2021-06-04 14:53:51 +02:00
committed by GitHub
parent c0d9d86b09
commit 73d37459bb
257 changed files with 18248 additions and 7178 deletions

View File

@@ -26,6 +26,7 @@ import { from, Observable } from 'rxjs';
import { OnboardingModule } from 'src/app/modules/onboarding/onboarding.module';
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe/regexp-pipe.module';
import { SubscriptionService } from 'src/app/services/subscription.service';
import { UploadService } from 'src/app/services/upload.service';
import { environment } from '../environments/environment';
import { AppRoutingModule } from './app-routing.module';
@@ -180,6 +181,7 @@ const authConfig: AuthConfig = {
AuthenticationService,
GrpcAuthService,
SubscriptionService,
UploadService,
{ provide: 'windowObject', useValue: window },
],
bootstrap: [AppComponent],

View File

@@ -0,0 +1,28 @@
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
@Directive({
selector: '[cnslDropzone]',
})
export class DropzoneDirective {
@Output() dropped: EventEmitter<FileList> = new EventEmitter<FileList>();
@Output() hovered: EventEmitter<boolean> = new EventEmitter<boolean>();
@HostListener('drop', ['$event'])
onDrop($event: DragEvent): void {
$event.preventDefault();
this.dropped.emit($event.dataTransfer?.files);
this.hovered.emit(false);
}
@HostListener('dragover', ['$event'])
onDragOver($event: any): void {
$event.preventDefault();
this.hovered.emit(true);
}
@HostListener('dragleave', ['$event'])
onDragLeave($event: any): void {
$event.preventDefault();
this.hovered.emit(false);
}
}

View File

@@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DropzoneDirective } from './dropzone.directive';
@NgModule({
declarations: [
DropzoneDirective,
],
imports: [
CommonModule,
],
exports: [
DropzoneDirective,
],
})
export class DropzoneModule { }

View File

@@ -120,9 +120,17 @@
</div>
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICY' | translate}}</span>
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYPRIVATELABEL' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicy"
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicyPrivateLabel"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.LABELPOLICYWATERMARK' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.labelPolicyWatermark"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>

View File

@@ -158,7 +158,8 @@ export class FeaturesComponent implements OnDestroy {
req.setLoginPolicyFactors(this.features.loginPolicyFactors);
req.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
req.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
req.setLabelPolicy(this.features.labelPolicy);
req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
req.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
req.setCustomDomain(this.features.customDomain);
this.adminService.setOrgFeatures(req).then(() => {
@@ -177,7 +178,8 @@ export class FeaturesComponent implements OnDestroy {
dreq.setLoginPolicyFactors(this.features.loginPolicyFactors);
dreq.setLoginPolicyPasswordless(this.features.loginPolicyPasswordless);
dreq.setPasswordComplexityPolicy(this.features.passwordComplexityPolicy);
dreq.setLabelPolicy(this.features.labelPolicy);
dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
dreq.setCustomDomain(this.features.customDomain);
this.adminService.setDefaultFeatures(dreq).then(() => {

View File

@@ -4,6 +4,7 @@
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
$foreground: map-get($theme, foreground);
.ng-untouched {
.cnsl-error {
@@ -29,6 +30,10 @@
vertical-align: middle;
}
input {
color: mat.get-color-from-palette($foreground, text);
}
// Wrapper for the hints and error messages.
.cnsl-form-field-subscript-wrapper {
position: absolute;

View File

@@ -8,17 +8,18 @@
}
.row {
width: 100%;
display: flex;
overflow-x: auto;
flex-wrap: wrap;
padding-bottom: .5rem;
margin: 0 -.5rem;
margin-bottom: 2rem;
.step {
min-width: 220px;
max-width: 280px;
padding: 1rem;
margin: 0 .5rem;
border: 1px solid var(--grey);
margin: 1rem .5rem;
border: 1px solid #8795a150;
border-radius: .5rem;
display: flex;
flex-direction: column;
@@ -26,6 +27,10 @@
box-sizing: border-box;
flex: 1;
@media only screen and (min-width: 899px) {
max-width: 280px;
}
h6 {
font-size: 1rem;
text-align: center;
@@ -46,14 +51,6 @@
display: block;
margin: auto;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
}

View File

@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LabelPolicyComponent } from './label-policy.component';
const routes: Routes = [
{
path: '',
component: LabelPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class LabelPolicyRoutingModule { }

View File

@@ -1,19 +0,0 @@
<app-detail-layout [backRouterLink]="['/iam/policies']" [title]="'POLICY.LABEL.TITLE' | translate"
[description]="'POLICY.LABEL.DESCRIPTION' | translate">
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
<div class="content" *ngIf="labelData">
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[(ngModel)]="labelData.hideLoginNameSuffix">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle>
</div>
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.SAVE' | translate
}}</button>
</div>
<cnsl-links *ngIf="nextLinks" [links]="nextLinks"></cnsl-links>
</app-detail-layout>

View File

@@ -1,23 +0,0 @@
.content {
padding-top: 1rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.default {
color: var(--color-main);
margin-top: 0;
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
padding: .5rem 4rem;
}
}

View File

@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LabelPolicyComponent } from './label-policy.component';
describe('LabelPolicyComponent', () => {
let component: LabelPolicyComponent;
let fixture: ComponentFixture<LabelPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LabelPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LabelPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,71 +0,0 @@
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { GetLabelPolicyResponse, UpdateLabelPolicyRequest } from 'src/app/proto/generated/zitadel/admin_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-label-policy',
templateUrl: './label-policy.component.html',
styleUrls: ['./label-policy.component.scss'],
})
export class LabelPolicyComponent implements OnDestroy {
public labelData!: LabelPolicy.AsObject;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: CnslLinks[] = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private adminService: AdminService,
) {
this.route.params.subscribe(() => {
this.getData().then(data => {
if (data?.policy) {
this.labelData = data.policy;
}
});
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData(): Promise<GetLabelPolicyResponse.AsObject> {
return this.adminService.getLabelPolicy();
}
public savePolicy(): void {
const req = new UpdateLabelPolicyRequest();
req.setPrimaryColor(this.labelData.primaryColor);
req.setSecondaryColor(this.labelData.secondaryColor);
req.setHideLoginNameSuffix(this.labelData.hideLoginNameSuffix);
this.adminService.updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
public get isDefault(): boolean {
if (this.labelData) {
return (this.labelData as LabelPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@@ -1,36 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
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 { InfoSectionModule } from '../../info-section/info-section.module';
import { LinksModule } from '../../links/links.module';
import { LabelPolicyRoutingModule } from './label-policy-routing.module';
import { LabelPolicyComponent } from './label-policy.component';
@NgModule({
declarations: [LabelPolicyComponent],
imports: [
LabelPolicyRoutingModule,
CommonModule,
FormsModule,
InputModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
LinksModule,
InfoSectionModule,
],
})
export class LabelPolicyModule { }

View File

@@ -22,10 +22,11 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_COMPLEXITY_LINK,
IAM_LABEL_LINK,
IAM_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_COMPLEXITY_LINK,
ORG_IAM_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
@@ -69,6 +70,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_IAM_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
@@ -80,7 +82,7 @@ export class LoginPolicyComponent implements OnDestroy {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LABEL_LINK,
IAM_PRIVATELABEL_LINK,
];
break;
}
@@ -96,7 +98,7 @@ export class LoginPolicyComponent implements OnDestroy {
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
this.disabled = this.isDefault ?? false;
this.disabled = ((this.loginData as LoginPolicy.AsObject)?.isDefault) ?? false;
}
});
this.getIdps().then(resp => {

View File

@@ -13,141 +13,143 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_COMPLEXITY_LINK,
IAM_LABEL_LINK,
IAM_LOGIN_POLICY_LINK,
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'],
selector: 'app-org-iam-policy',
templateUrl: './org-iam-policy.component.html',
styleUrls: ['./org-iam-policy.component.scss'],
})
export class OrgIamPolicyComponent implements OnDestroy {
private managementService!: ManagementService;
public serviceType!: PolicyComponentServiceType;
private managementService!: ManagementService;
public serviceType!: PolicyComponentServiceType;
public iamData!: OrgIAMPolicy.AsObject;
public iamData!: OrgIAMPolicy.AsObject;
private sub: Subscription = new Subscription();
private org!: Org.AsObject;
private sub: Subscription = new Subscription();
private org!: Org.AsObject;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: Array<CnslLinks> = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private sessionStorage: StorageService,
private injector: Injector,
private adminService: AdminService,
) {
const temporg = this.sessionStorage.getItem('organization') as Org.AsObject;
if (temporg) {
this.org = temporg;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public nextLinks: Array<CnslLinks> = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private sessionStorage: StorageService,
private injector: Injector,
private adminService: AdminService,
) {
const temporg = this.sessionStorage.getItem('organization') as Org.AsObject;
if (temporg) {
this.org = temporg;
}
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
} else {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
];
}
return this.route.params;
})).subscribe(_ => {
this.fetchData();
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public fetchData(): void {
this.getData().then(resp => {
if (resp?.policy) {
this.iamData = resp.policy;
}
});
}
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | undefined> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
case PolicyComponentServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.managementService = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_COMPLEXITY_LINK,
ORG_LOGIN_POLICY_LINK,
];
} else {
this.nextLinks = [
IAM_COMPLEXITY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_LABEL_LINK,
];
}
return this.route.params;
})).subscribe(_ => {
this.fetchData();
});
break;
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public fetchData(): void {
this.getData().then(resp => {
if (resp?.policy) {
this.iamData = resp.policy;
}
});
}
private async getData(): Promise<GetCustomOrgIAMPolicyResponse.AsObject | GetOrgIAMPolicyResponse.AsObject | undefined> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.managementService.getOrgIAMPolicy();
case PolicyComponentServiceType.ADMIN:
if (this.org?.id) {
return this.adminService.getCustomOrgIAMPolicy(this.org.id);
}
break;
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.iamData as OrgIAMPolicy.AsObject).isDefault) {
this.adminService.addCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
} else {
this.adminService.updateCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
// update Default org iam policy?
this.adminService.updateOrgIAMPolicy(
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.adminService.resetCustomOrgIAMPolicyToDefault(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.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.iamData as OrgIAMPolicy.AsObject).isDefault;
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.iamData as OrgIAMPolicy.AsObject).isDefault) {
this.adminService.addCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
} else {
return false;
this.adminService.updateCustomOrgIAMPolicy(
this.org.id,
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
case PolicyComponentServiceType.ADMIN:
// update Default org iam policy?
this.adminService.updateOrgIAMPolicy(
this.iamData.userLoginMustBeDomain,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public removePolicy(): void {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
this.adminService.resetCustomOrgIAMPolicyToDefault(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.iamData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.iamData as OrgIAMPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@@ -3,10 +3,10 @@ import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
GetPasswordComplexityPolicyResponse as AdminGetPasswordComplexityPolicyResponse,
GetPasswordComplexityPolicyResponse as AdminGetPasswordComplexityPolicyResponse,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
GetPasswordComplexityPolicyResponse as MgmtGetPasswordComplexityPolicyResponse,
GetPasswordComplexityPolicyResponse as MgmtGetPasswordComplexityPolicyResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
@@ -15,163 +15,165 @@ import { ToastService } from 'src/app/services/toast.service';
import { CnslLinks } from '../../links/links.component';
import {
IAM_LABEL_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_POLICY_LINK,
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
} from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'app-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
selector: 'app-password-policy',
templateUrl: './password-complexity-policy.component.html',
styleUrls: ['./password-complexity-policy.component.scss'],
})
export class PasswordComplexityPolicyComponent implements OnDestroy {
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public complexityData!: PasswordComplexityPolicy.AsObject;
public complexityData!: PasswordComplexityPolicy.AsObject;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [];
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.nextLinks = [
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_LABEL_LINK,
];
break;
}
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
this.nextLinks = [
ORG_IAM_POLICY_LINK,
ORG_LOGIN_POLICY_LINK,
ORG_PRIVATELABEL_LINK,
];
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
this.nextLinks = [
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
IAM_PRIVATELABEL_LINK,
];
break;
}
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
}
public fetchData(): void {
this.loading = true;
this.getData().then(data => {
if (data.policy) {
this.complexityData = data.policy;
this.loading = false;
}
});
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getData():
Promise<MgmtGetPasswordComplexityPolicyResponse.AsObject | AdminGetPasswordComplexityPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordComplexityPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordComplexityPolicy();
}
}
public fetchData(): void {
this.loading = true;
this.getData().then(data => {
if (data.policy) {
this.complexityData = data.policy;
this.loading = false;
}
});
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
public incrementLength(): void {
if (this.complexityData?.minLength !== undefined && this.complexityData?.minLength <= 72) {
this.complexityData.minLength++;
}
}
private async getData():
Promise<MgmtGetPasswordComplexityPolicyResponse.AsObject | AdminGetPasswordComplexityPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPasswordComplexityPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPasswordComplexityPolicy();
}
public decrementLength(): void {
if (this.complexityData?.minLength && this.complexityData?.minLength > 1) {
this.complexityData.minLength--;
}
}
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.complexityData as PasswordComplexityPolicy.AsObject).isDefault) {
(this.service as ManagementService).addCustomPasswordComplexityPolicy(
public incrementLength(): void {
if (this.complexityData?.minLength !== undefined && this.complexityData?.minLength <= 72) {
this.complexityData.minLength++;
}
}
public decrementLength(): void {
if (this.complexityData?.minLength && this.complexityData?.minLength > 1) {
this.complexityData.minLength--;
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.complexityData as PasswordComplexityPolicy.AsObject).isDefault) {
(this.service as ManagementService).addCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
(this.service as ManagementService).updateCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.complexityData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.complexityData as PasswordComplexityPolicy.AsObject).isDefault;
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
} else {
return false;
(this.service as ManagementService).updateCustomPasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
(this.service as AdminService).updatePasswordComplexityPolicy(
this.complexityData.hasLowercase,
this.complexityData.hasUppercase,
this.complexityData.hasNumber,
this.complexityData.hasSymbol,
this.complexityData.minLength,
).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.complexityData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.complexityData as PasswordComplexityPolicy.AsObject).isDefault;
} else {
return false;
}
}
}

View File

@@ -1,12 +1,12 @@
export enum PolicyComponentType {
LOCKOUT = 'lockout',
AGE = 'age',
COMPLEXITY = 'complexity',
IAM = 'iam',
LOGIN = 'login',
LABEL = 'label',
LOCKOUT = 'lockout',
AGE = 'age',
COMPLEXITY = 'complexity',
IAM = 'iam',
LOGIN = 'login',
PRIVATELABEL = 'privatelabel',
}
export enum PolicyComponentServiceType {
MGMT = 'mgmt',
ADMIN = 'admin',
MGMT = 'mgmt',
ADMIN = 'admin',
}

View File

@@ -0,0 +1,15 @@
<div class="form-row">
<cnsl-form-field class="formfield">
<cnsl-label>{{name}} (current {{color}})</cnsl-label>
<input cnslInput [(ngModel)]="previewColor" (keydown.enter)="name ? emitPreview(previewColor) : null" />
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.SET' | translate}}" (click)="emitPreview(previewColor)" mat-icon-button><mat-icon>check</mat-icon></button>
</div>
<div class="color-selector">
<div *ngFor="let c of colors" class="circle mat-elevation-z3"
[ngClass]="{'active': color == c.color || previewColor == c.color}"
(click)="emitPreview(c.color)" [ngStyle]="{'background-color': c.color}">
<span *ngIf="previewColor == c.color">P</span>
<span *ngIf="color == c.color">C</span>
</div>
</div>

View File

@@ -0,0 +1,52 @@
.title {
// font-size: 1rem;
display: block;
font-size: 18px;
&.border {
border-top: 1px solid var(--grey);
padding-top: 1.5rem;
}
}
.form-row {
display: flex;
align-items: flex-end;
.formfield {
flex: 1;
}
button {
margin-bottom: .9rem;
}
}
.color-selector {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0 -.25rem;
width: 100%;
padding-bottom: 1rem;
.circle {
height: 30px;
width: 30px;
background-color: #cd5c5c;
border-radius: 50%;
margin: .25rem;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 18px;
}
&.active {
border: 3px solid #ffffff90;
}
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ColorComponent } from './color.component';
describe('ColorComponent', () => {
let component: ColorComponent;
let fixture: ComponentFixture<ColorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ColorComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ColorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,118 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ColorType } from '../private-labeling-policy.component';
@Component({
selector: 'cnsl-color',
templateUrl: './color.component.html',
styleUrls: ['./color.component.scss'],
})
export class ColorComponent implements OnInit {
public PRIMARY: Array<{ name: string; color: string; }> = [
{ name: 'red', color: '#f44336' },
{ name: 'pink', color: '#e91e63' },
{ name: 'purple', color: '#9c27b0' },
{ name: 'deeppurple', color: '#673ab7' },
{ name: 'indigo', color: '#3f51b5' },
{ name: 'blue', color: '#2196f3' },
{ name: 'lightblue', color: '#03a9f4' },
{ name: 'cyan', color: '#00bcd4' },
{ name: 'teal', color: '#009688' },
{ name: 'green', color: '#4caf50' },
{ name: 'lightgreen', color: '#8bc34a' },
{ name: 'lime', color: '#cddc39' },
{ name: 'yellow', color: '#ffeb3b' },
{ name: 'amber', color: '#ffc107' },
{ name: 'orange', color: '#fb8c00' },
{ name: 'deeporange', color: '#ff5722' },
{ name: 'brown', color: '#795548' },
{ name: 'grey', color: '#9e9e9e' },
{ name: 'bluegrey', color: '#607d8b' },
{ name: 'white', color: '#ffffff' },
];
public WARN: Array<{ name: string; color: string; }> = [
{ name: 'red', color: '#f44336' },
{ name: 'pink', color: '#e91e63' },
{ name: 'purple', color: '#9c27b0' },
{ name: 'deeppurple', color: '#673ab7' },
];
public FONTLIGHT: Array<{ name: string; color: string; }> = [
{ name: 'gray-500', color: '#6b7280' },
{ name: 'gray-600', color: '#4b5563' },
{ name: 'gray-700', color: '#374151' },
{ name: 'gray-800', color: '#1f2937' },
{ name: 'gray-900', color: '#111827' },
{ name: 'black', color: '#000000' },
];
public FONTDARK: Array<{ name: string; color: string; }> = [
{ name: 'white', color: '#ffffff' },
{ name: 'gray-50', color: '#f9fafb' },
{ name: 'gray-100', color: '#f3f4f6' },
{ name: 'gray-200', color: '#e5e7eb' },
{ name: 'gray-300', color: '#d1d5db' },
{ name: 'gray-400', color: '#9ca3af' },
{ name: 'gray-500', color: '#6b7280' },
];
public BACKGROUNDLIGHT: Array<{ name: string; color: string; }> = [
{ name: 'white', color: '#ffffff' },
{ name: 'gray-50', color: '#f9fafb' },
{ name: 'gray-100', color: '#f3f4f6' },
{ name: 'gray-200', color: '#e5e7eb' },
{ name: 'gray-300', color: '#d1d5db' },
{ name: 'gray-400', color: '#9ca3af' },
{ name: 'gray-500', color: '#6b7280' },
];
public BACKGROUNDDARK: Array<{ name: string; color: string; }> = [
{ name: 'gray-500', color: '#6b7280' },
{ name: 'gray-600', color: '#4b5563' },
{ name: 'gray-700', color: '#374151' },
{ name: 'gray-800', color: '#1f2937' },
{ name: 'gray-900', color: '#111827' },
{ name: 'black', color: '#000000' },
];
public colors: Array<{ name: string; color: string; }> = this.PRIMARY;
@Input() colorType: ColorType = ColorType.PRIMARY;
@Input() color: string = '';
@Input() previewColor: string = '';
@Input() name: string = '';
@Output() previewChanged: EventEmitter<string> = new EventEmitter();
public emitPreview(color: string): void {
this.previewColor = color;
this.previewChanged.emit(this.previewColor);
}
ngOnInit(): void {
switch (this.colorType) {
case ColorType.PRIMARY:
this.colors = this.PRIMARY;
break;
case ColorType.WARN:
this.colors = this.WARN;
break;
case ColorType.FONTDARK:
this.colors = this.FONTDARK;
break;
case ColorType.FONTLIGHT:
this.colors = this.FONTLIGHT;
break;
case ColorType.BACKGROUNDDARK:
this.colors = this.BACKGROUNDDARK;
break;
case ColorType.BACKGROUNDLIGHT:
this.colors = this.BACKGROUNDLIGHT;
break;
default:
this.colors = this.PRIMARY;
break;
}
}
}

View File

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

View File

@@ -0,0 +1,92 @@
.preview {
position: absolute;
left: 0;
width: 100%;
pointer-events: none;
margin-bottom: 2rem;
* {
pointer-events: none;
}
.label {
color: var(--grey);
font-size: 12px;
position: absolute;
right: 1rem;
top: 1rem;
}
.dashed {
border: 2px dashed rgba(#697386, .5);
border-radius: 1rem;
padding: 100px 20px;
.login-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 360px;
margin: auto;
@media only screen and (min-width: 1000px) {
width: 360px;
}
img {
max-width: 160px;
max-height: 150px;
margin-bottom: 1rem;
}
h1 {
font-size: 20px;
}
.formfield {
width: 100%;
}
.btn-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
}
.error-msg {
align-self: flex-start;
display: flex;
flex-direction: row;
align-items: center;
outline: none;
justify-content: flex-start;
i {
margin-right: .5rem;
}
span {
font-size: 14px;
margin: 1rem 0;
}
}
}
&.light {
background: #fff;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #000;
}
}
&.dark {
background: #212224;
.login-wrapper *:not(.cnsl-label):not(.mat-button-wrapper) {
color: #fff;
}
}
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PreviewComponent } from './preview.component';
describe('PreviewComponent', () => {
let component: PreviewComponent;
let fixture: ComponentFixture<PreviewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PreviewComponent],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PreviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,35 @@
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Observable, of, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { Preview, Theme } from '../private-labeling-policy.component';
@Component({
selector: 'cnsl-preview',
templateUrl: './preview.component.html',
styleUrls: ['./preview.component.scss'],
})
export class PreviewComponent implements OnInit, OnDestroy {
@Input() preview: Preview = Preview.PREVIEW;
@Input() policy!: LabelPolicy.AsObject;
@Input() label: string = 'PREVIEW';
@Input() images: { [imagekey: string]: any; } = {};
@Input() theme: Theme = Theme.DARK;
@Input() refresh: Observable<void> = of();
private destroyed$: Subject<void> = new Subject();
public Theme: any = Theme;
public Preview: any = Preview;
constructor(private chd: ChangeDetectorRef) { }
public ngOnInit(): void {
this.refresh.pipe(takeUntil(this.destroyed$)).subscribe(() => {
this.chd.detectChanges();
});
}
public ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
}

View File

@@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
const routes: Routes = [
{
path: '',
component: PrivateLabelingPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PrivateLabelingPolicyRoutingModule { }

View File

@@ -0,0 +1,254 @@
<div class="policy enlarged-container">
<div class="header">
<a [routerLink]="[ serviceType === PolicyComponentServiceType.ADMIN ? '/iam/policies' : '/org']" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
<div class="col">
<h1>{{'POLICY.PRIVATELABELING.TITLE' | translate}}</h1>
<p>{{'POLICY.PRIVATELABELING.DESCRIPTION' | translate}}</p>
</div>
</div>
<p class="desc">{{'POLICY.PRIVATELABELING.PREVIEW_DESCRIPTION' | translate}}</p>
<p class="default" *ngIf="isDefault"> {{'POLICY.DEFAULTLABEL' | translate}}</p>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<ng-template appHasRole [appHasRole]="['policy.delete']">
<button *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{'POLICY.RESET' | translate}}" color="warn" (click)="removePolicy()" mat-stroked-button>
{{'POLICY.RESET' | translate}}
</button>
</ng-template>
<div class="top-row">
<div>
<p>{{'POLICY.PRIVATELABELING.THEME' | translate}}</p>
<div class="theme-changer">
<button (click)="theme = Theme.LIGHT" matTooltip="{{'POLICY.PRIVATELABELING.LIGHT' | translate}}" class="light" [ngClass]="{'active': theme === Theme.LIGHT}" >
<i class="icon las la-edit"></i>
</button>
<button (click)="theme = Theme.DARK" matTooltip="{{'POLICY.PRIVATELABELING.DARK' | translate}}" class="dark" [ngClass]="{'active': theme === Theme.DARK}" >
<i class="icon las la-edit"></i>
</button>
</div>
</div>
<button class="activate-button" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button color="primary" (click)="activatePolicy()">
{{'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate}}
</button>
</div>
<div *ngIf="previewData && data" class="lab-policy-content">
<mat-accordion class="settings">
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-image"></i>
Logo</div>
</mat-panel-title>
<mat-panel-description>
</mat-panel-description>
</mat-expansion-panel-header>
<p class="description">Your Logo will be used in the Login itself, while the icon is used for smaller UI elements like in the organisation switcher in console</p>
<!-- <span class="title">{{ theme === Theme.DARK ? ('POLICY.PRIVATELABELING.DARK' | translate) : ('POLICY.PRIVATELABELING.LIGHT' | translate)}}</span> -->
<div class="logo-setup-wrapper">
<div class="part">
<span class="label">Logo</span>
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkLogo']" [src]="images['previewDarkLogo']" alt="dark logo preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkLogo']" [src]="images['darkLogo']" alt="dark logo"/>
</div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewLogo']" [src]="images['previewLogo']" alt="logo preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['logo']" [src]="images['logo']" alt="logo"/>
</div>
</container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverLogo(theme, $event)"
(dropped)="onDropLogo(theme, $event)"
[class.hovering]="isHoveringOverDarkLogo">
<label class="file-label">
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDropLogo(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFile.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkLogo ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
<div class="part">
<span class="label">Icon</span>
<mat-spinner class="spinner" color="primary" diameter="25" *ngIf="loadingImages"></mat-spinner>
<container [ngSwitch]="theme">
<div class="logo-view" *ngSwitchCase="Theme.DARK">
<img matTooltip="Preview" class="prev" *ngIf="images['previewDarkIcon']" [src]="images['previewDarkIcon']" alt="dark icon preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['darkIcon']" [src]="images['darkIcon']" alt="dark icon"/>
</div>
<div class="logo-view" *ngSwitchCase="Theme.LIGHT">
<img matTooltip="Preview" class="prev" *ngIf="images['previewIcon']" [src]="images['previewIcon']" alt="icon preview"/>
<span class="fill-space"></span>
<img matTooltip="Current" class="curr" *ngIf="images['icon']" [src]="images['icon']" alt="icon"/>
</div>
</container>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverIcon(theme, $event)"
(dropped)="onDropIcon(theme, $event)"
[class.hovering]="isHoveringOverDarkIcon">
<label class="file-label">
<input #selectedFileIcon style="display: none;" class="file-input" type="file" (change)="onDropIcon(theme, $event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFileIcon.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span>{{isHoveringOverDarkIcon ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel class="expansion" [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-palette"></i>
{{'POLICY.PRIVATELABELING.COLORS' | translate}}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<!-- <span class="title">{{ theme === Theme.DARK ? ('POLICY.PRIVATELABELING.DARK' | translate) : ('POLICY.PRIVATELABELING.LIGHT' | translate)}}</span> -->
<ng-container *ngIf="theme==Theme.DARK">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK"(previewChanged)="previewData.backgroundColorDark = $event; savePolicy()" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event; savePolicy()" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event; savePolicy()" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event; savePolicy()" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
</div>
</div>
</ng-container>
<ng-container *ngIf="theme==Theme.LIGHT">
<div class="colors" *ngIf="data && previewData">
<div class="color">
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event; savePolicy()" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event; savePolicy()" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.WARN" name="Preview Warn Color Light" (previewChanged)="previewData.warnColor= $event; savePolicy()" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
</div>
<div class="color">
<cnsl-color [colorType]="ColorType.FONTLIGHT" (previewChanged)="previewData.fontColor = $event; savePolicy()" name="Font Color" [color]="data.fontColor" [previewColor]="previewData.fontColor"></cnsl-color>
</div>
</div>
</ng-container>
</mat-expansion-panel>
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header class="header">
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-font"></i>
{{'POLICY.PRIVATELABELING.FONT' | translate}}</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="fonts">
<div class="font-preview">
<mat-icon>text_fields</mat-icon>
<span>ABC • abc • 123</span>
<span>_</span>
<span>Upload your favorite font for the UI</span>
</div>
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
(dropped)="onDropFont($event)"
[class.hovering]="isHoveringOverFont">
<label class="file-label">
<input #selectedFontFile style="display: none;" class="file-input" type="file" (change)="onDropFont($event.target.files)">
<input class="btn" mat-raised-button type="button" [value]="'POLICY.PRIVATELABELING.BTN' | translate" (click)="selectedFontFile.click();" />
<i class="icon las la-cloud-upload-alt"></i>
<span >{{isHoveringOverFont ? 'Release': 'Drop your Logo here'}}</span>
</label>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel class="expansion">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="panel-title">
<i class="icon las la-universal-access"></i>
{{'POLICY.PRIVATELABELING.ADVANCEDBEHAVIOR' | translate}}
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="adv-container" *ngIf="previewData">
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false">
<cnsl-info-section class="info" type="WARN">{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy.private_label'})}}
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl
[(ngModel)]="previewData.hideLoginNameSuffix" (change)="savePolicy()">
{{'POLICY.DATA.HIDELOGINNAMESUFFIX' | translate}}
</mat-slide-toggle>
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false">
<cnsl-info-section class="info" type="WARN">{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy.watermark'})}}
</cnsl-info-section>
</ng-container>
<mat-slide-toggle class="toggle" color="primary" ngDefaultControl [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.watermark'] | hasFeature | async) == false"
[(ngModel)]="previewData.disableWatermark" (change)="saveWatermark()">
{{'POLICY.DATA.DISABLEWATERMARK' | translate}}
</mat-slide-toggle>
</div>
</mat-expansion-panel>
</mat-accordion>
<div class="preview-wrapper">
<!-- <cnsl-preview class="prev" label="CURRENT CONFIG" [policy]="data"></cnsl-preview> -->
<div class="col">
<button color="primary" mat-raised-button class="preview-changer" (click)="preview = preview == Preview.PREVIEW ? Preview.CURRENT : Preview.PREVIEW" matTooltip="{{'POLICY.PRIVATELABELING.CHANGEVIEW' | translate}}" [ngClass]="{'active': preview === Preview.PREVIEW}" >
<span><span [ngClass]="{'strong': preview == Preview.PREVIEW}">P</span> / <span [ngClass]="{'strong': preview == Preview.CURRENT}">C</span></span>
</button>
<cnsl-preview *ngIf="preview === Preview.CURRENT" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="CURRENT" [policy]="data"></cnsl-preview>
<cnsl-preview *ngIf="preview === Preview.PREVIEW" [refresh]="refreshPreview" [images]="images" [preview]="preview" [theme]="theme" class="prev" label="PREVIEW" [policy]="previewData"></cnsl-preview>
</div>
</div>
</div>
<cnsl-links *ngIf="nextLinks" [links]="nextLinks"></cnsl-links>
</div>

View File

@@ -0,0 +1,398 @@
@use '~@angular/material' as mat;
@import './preview/preview.component.scss';
@mixin private-label-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$is-dark-theme: map-get($theme, is-dark);
.policy {
.header {
display: flex;
a {
margin: 0 1rem;
}
.col {
display: flex;
flex-direction: column;
margin-left: 1rem;
h1 {
margin: 0;
}
}
}
.default {
color: var(--color-main);
margin-top: 0;
}
.desc {
font-size: 14px;
color: var(--grey);
max-width: 800px;
margin-bottom: 2rem;
}
.spinner-wr {
margin: .5rem 0;
}
.theme-changer {
display: flex;
margin: 1rem -.5rem;
button {
height: 40px;
width: 40px;
border-radius: 50%;
border: 1px solid #81868a50;
margin: 0 .5rem;
.icon {
visibility: hidden;
}
}
.dark {
background-color: black;
&.active {
.icon {
visibility: visible;
color: white;
}
}
}
.light {
background-color: white;
&.active {
.icon {
visibility: visible;
color: black;
}
}
}
}
.top-row {
display: flex;
justify-content: space-between;
.activate-button {
border-radius: 50%;
align-self: flex-end;
}
.theme-changer {
display: flex;
margin: 0 -.25rem;
button {
height: 40px;
width: 40px;
border-radius: 50%;
border: 1px solid #81868a50;
margin: 0 .25rem;
display: flex;
align-items: center;
justify-content: center;
.icon {
visibility: hidden;
}
}
.dark {
background-color: black;
&.active {
transform: scale(1.1);
.icon {
visibility: visible;
color: white;
}
}
}
.light {
background-color: white;
&.active {
transform: scale(1.1);
.icon {
visibility: visible;
color: black;
}
}
}
}
}
.lab-policy-content {
padding-top: 1rem;
display: flex;
flex-direction: column;
margin: 0 -1rem;
.settings {
flex: 1;
margin: 0 1rem;
margin-bottom: 2rem;
.expansion {
background: if($is-dark-theme, #2d2e30, #fff) !important;
.header {
justify-content: flex-start;
}
.panel-title {
display: flex;
align-items: center;
.icon {
margin-right: .5rem;
}
}
}
.description {
width: 100%;
}
.title {
margin-top: 2rem;
margin-bottom: .5rem;
display: block;
font-size: 18px;
}
.dropzone {
outline: none;
height: 150px;
width: 100%;
border-radius: 16px;
background: if($is-dark-theme, #2d2e30, #fff);
border: 1px solid if($is-dark-theme, #4a4b4b, #ddd);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: all .2s ease-in-out;
.file-label {
display: flex;
flex-direction: column;
align-items: center;
.btn {
cursor: pointer;
border-radius: 6px;
padding: .5rem 1rem;
background-color: inherit;
border: 1px solid if($is-dark-theme, #ffffff20, #000);
color: if($is-dark-theme, white, #000);
margin-bottom: .5rem;
}
}
.icon {
font-size: 1.5rem;
color: var(--grey);
}
.desc {
font-size: 14px;
color: var(--grey);
}
&.hovering {
border-radius: 16px;
box-shadow:
if(
$is-dark-theme,
(inset 26px 26px 52px #252628, inset -26px -26px 52px #353638),
(inset 26px 26px 52px #d4d4d4, inset -26px -26px 52px #fff)
);
.desc {
color: if($is-dark-theme, white, black);
}
.icon {
color: if($is-dark-theme, white, black);
}
}
}
.logo-setup-wrapper {
display: flex;
flex-direction: column;
.part {
padding-bottom: 1rem;
.spinner {
margin-bottom: 1rem;
}
.label {
font-size: 14px;
color: var(--grey);
margin-bottom: 1rem;
display: block;
}
.logo-view {
width: 100%;
display: flex;
margin-bottom: 1rem;
.prev,
.curr {
height: 50px;
object-fit: contain;
border-radius: .5rem;
}
.fill-space {
flex: 1;
}
}
}
}
.colors {
display: flex;
flex-direction: column;
.color {
padding-bottom: 1rem;
}
}
.fonts {
.title {
display: block;
font-size: 14px;
}
.font-preview {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 50px;
text-align: center;
}
.font-selector {
display: flex;
align-items: center;
flex-wrap: wrap;
margin: 0 -.25rem;
width: 100%;
padding-bottom: 1rem;
.font {
height: 50px;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
border-radius: .5rem;
margin: .25rem;
box-sizing: border-box;
border: 2px solid if($is-dark-theme, #ffffff30, #00000030);
font-size: 1.5rem;
background-color: inherit;
color: inherit;
cursor: pointer;
&.active {
border: 2px solid if($is-dark-theme, #fff, #000);
}
}
}
}
.adv-container {
display: flex;
flex-direction: column;
padding-bottom: 50px;
.info {
margin-bottom: .5rem;
}
.toggle {
margin-bottom: 1rem;
}
}
}
.preview-wrapper {
margin: 0 1rem;
flex: 1;
.col {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 2rem;
position: relative;
min-height: 600px;
.preview-changer {
position: absolute;
top: .5rem;
left: .5rem;
border-radius: 10px !important;
z-index: 1;
span {
color: if($is-dark-theme, #ffffff50, #00000050);
}
.strong {
color: if($is-dark-theme, #fff, #000);
font-weight: bold;
font-size: 18px;
}
}
}
}
@media only screen and (min-width: 1000px) {
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
.preview-wrapper {
.col {
min-width: 400px;
}
}
}
}
.btn-container {
display: flex;
justify-content: flex-end;
width: 100%;
button {
margin-top: 3rem;
display: block;
}
}
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
describe('PrivateLabelingPolicyComponent', () => {
let component: PrivateLabelingPolicyComponent;
let fixture: ComponentFixture<PrivateLabelingPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PrivateLabelingPolicyComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PrivateLabelingPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,511 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, Injector, OnDestroy, Type } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import {
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
UpdateLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddCustomLabelPolicyRequest,
GetLabelPolicyResponse as MgmtGetLabelPolicyResponse,
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
UpdateCustomLabelPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { DownloadEndpoint, UploadEndpoint, UploadService } from 'src/app/services/upload.service';
import { CnslLinks } from '../../links/links.component';
import { IAM_COMPLEXITY_LINK, IAM_LOGIN_POLICY_LINK, IAM_POLICY_LINK } from '../../policy-grid/policy-links';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
export enum Theme {
DARK,
LIGHT,
}
export enum Preview {
CURRENT,
PREVIEW,
}
export enum ColorType {
BACKGROUND,
PRIMARY,
WARN,
FONTDARK,
FONTLIGHT,
BACKGROUNDDARK,
BACKGROUNDLIGHT,
}
@Component({
selector: 'app-private-labeling-policy',
templateUrl: './private-labeling-policy.component.html',
styleUrls: ['./private-labeling-policy.component.scss'],
})
export class PrivateLabelingPolicyComponent implements OnDestroy {
public theme: Theme = Theme.LIGHT;
public preview: Preview = Preview.PREVIEW;
public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public previewData!: LabelPolicy.AsObject;
public data!: LabelPolicy.AsObject;
public images: { [key: string]: any; } = {};
public panelOpenState: boolean = false;
public isHoveringOverDarkLogo: boolean = false;
public isHoveringOverDarkIcon: boolean = false;
public isHoveringOverLightLogo: boolean = false;
public isHoveringOverLightIcon: boolean = false;
public isHoveringOverFont: boolean = false;
private sub: Subscription = new Subscription();
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public loading: boolean = false;
public nextLinks: CnslLinks[] = [
IAM_COMPLEXITY_LINK,
IAM_POLICY_LINK,
IAM_LOGIN_POLICY_LINK,
];
public Theme: any = Theme;
public Preview: any = Preview;
public ColorType: any = ColorType;
public refreshPreview: EventEmitter<void> = new EventEmitter();
public loadingImages: boolean = false;
constructor(
private route: ActivatedRoute,
private toast: ToastService,
private injector: Injector,
private uploadService: UploadService,
private sanitizer: DomSanitizer,
) {
this.sub = this.route.data.pipe(switchMap(data => {
this.serviceType = data.serviceType;
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
return this.route.params;
})).subscribe(() => {
this.fetchData();
});
}
public toggleHoverLogo(theme: Theme, isHovering: boolean): void {
if (theme === Theme.DARK) {
this.isHoveringOverDarkLogo = isHovering;
}
if (theme === Theme.LIGHT) {
this.isHoveringOverLightLogo = isHovering;
}
}
public toggleHoverFont(isHovering: boolean): void {
this.isHoveringOverFont = isHovering;
}
public onDropLogo(theme: Theme, filelist: FileList): Promise<any> | void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKLOGO, formData));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKLOGO, formData));
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTLOGO, formData));
case PolicyComponentServiceType.ADMIN:
return this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTLOGO, formData));
}
}
}
}
public onDropFont(filelist: FileList): Promise<any> | void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return this.uploadService.upload(UploadEndpoint.MGMTFONT, formData);
case PolicyComponentServiceType.ADMIN:
return this.uploadService.upload(UploadEndpoint.IAMFONT, formData);
}
}
}
public toggleHoverIcon(theme: Theme, isHovering: boolean): void {
if (theme === Theme.DARK) {
this.isHoveringOverDarkIcon = isHovering;
}
if (theme === Theme.LIGHT) {
this.isHoveringOverLightIcon = isHovering;
}
}
public onDropIcon(theme: Theme, filelist: FileList): void {
const file = filelist.item(0);
if (file) {
const formData = new FormData();
formData.append('file', file);
if (theme === Theme.DARK) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTDARKICON, formData));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMDARKICON, formData));
break;
}
}
if (theme === Theme.LIGHT) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.MGMTLIGHTICON, formData));
break;
case PolicyComponentServiceType.ADMIN:
this.handleUploadPromise(this.uploadService.upload(UploadEndpoint.IAMLIGHTICON, formData));
break;
}
}
}
}
private handleUploadPromise(task: Promise<any>): Promise<any> {
return task.then(() => {
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
setTimeout(() => {
this.loadingImages = true;
this.getPreviewData().then(data => {
if (data.policy) {
this.previewData = data.policy;
this.loadPreviewImages();
}
});
}, 1000);
}).catch(error => this.toast.showError(error));
}
public fetchData(): void {
this.loading = true;
this.getPreviewData().then(data => {
console.log('preview', data);
this.loadingImages = true;
if (data.policy) {
this.previewData = data.policy;
this.loading = false;
this.loadPreviewImages();
}
}).catch(error => {
this.toast.showError(error);
});
this.getData().then(data => {
console.log('data', data);
if (data.policy) {
this.data = data.policy;
this.loading = false;
this.loadImages();
}
}).catch(error => {
this.toast.showError(error);
});
}
private loadImages(): void {
if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW);
}
if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.IAMDARKICONPREVIEW);
}
if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.IAMLOGOPREVIEW);
}
if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.IAMICONPREVIEW);
}
} else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.data.logoUrlDark) {
this.loadAsset('darkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW);
}
if (this.data.iconUrlDark) {
this.loadAsset('darkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW);
}
if (this.data.logoUrl) {
this.loadAsset('logo', DownloadEndpoint.MGMTLOGOPREVIEW);
}
if (this.data.iconUrl) {
this.loadAsset('icon', DownloadEndpoint.MGMTICONPREVIEW);
}
}
}
private loadPreviewImages(): void {
if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.IAMDARKLOGOPREVIEW);
}
if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.IAMDARKICONPREVIEW);
}
if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.IAMLOGOPREVIEW);
}
if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.IAMICONPREVIEW);
}
} else if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.previewData.logoUrlDark) {
this.loadAsset('previewDarkLogo', DownloadEndpoint.MGMTDARKLOGOPREVIEW);
}
if (this.previewData.iconUrlDark) {
this.loadAsset('previewDarkIcon', DownloadEndpoint.MGMTDARKICONPREVIEW);
}
if (this.previewData.logoUrl) {
this.loadAsset('previewLogo', DownloadEndpoint.MGMTLOGOPREVIEW);
}
if (this.previewData.iconUrl) {
this.loadAsset('previewIcon', DownloadEndpoint.MGMTICONPREVIEW);
}
}
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
private async getPreviewData():
Promise<MgmtGetPreviewLabelPolicyResponse.AsObject |
AdminGetPreviewLabelPolicyResponse.AsObject |
MgmtGetLabelPolicyResponse.AsObject |
AdminGetLabelPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getPreviewLabelPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getPreviewLabelPolicy();
}
}
private async getData():
Promise<MgmtGetPreviewLabelPolicyResponse.AsObject |
AdminGetPreviewLabelPolicyResponse.AsObject |
MgmtGetLabelPolicyResponse.AsObject |
AdminGetLabelPolicyResponse.AsObject> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getLabelPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getLabelPolicy();
}
}
private loadAsset(imagekey: string, url: string): Promise<any> {
return this.uploadService.load(`${url}`).then(data => {
const objectURL = URL.createObjectURL(data);
this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL);
this.refreshPreview.emit();
this.loadingImages = false;
}).catch(error => {
this.toast.showError(error);
this.loadingImages = false;
});
}
public removePolicy(): void {
if (this.service instanceof ManagementService) {
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
setTimeout(() => {
this.fetchData();
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public savePolicy(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.previewData as LabelPolicy.AsObject).isDefault) {
const req0 = new AddCustomLabelPolicyRequest();
this.overwriteValues(req0);
(this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch((error: HttpErrorResponse) => {
this.toast.showError(error);
});
} else {
const req1 = new UpdateCustomLabelPolicyRequest();
this.overwriteValues(req1);
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
const req = new UpdateLabelPolicyRequest();
this.overwriteValues(req);
(this.service as AdminService).updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public saveWatermark(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.previewData as LabelPolicy.AsObject).isDefault) {
const req0 = new AddCustomLabelPolicyRequest();
req0.setDisableWatermark(this.previewData.disableWatermark);
(this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch((error: HttpErrorResponse) => {
this.toast.showError(error);
});
} else {
const req1 = new UpdateCustomLabelPolicyRequest();
req1.setDisableWatermark(this.previewData.disableWatermark);
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
const req = new UpdateLabelPolicyRequest();
req.setDisableWatermark(this.data.disableWatermark);
(this.service as AdminService).updateLabelPolicy(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public get isDefault(): boolean {
if (this.previewData && this.serviceType === PolicyComponentServiceType.MGMT) {
return (this.previewData as LabelPolicy.AsObject).isDefault;
} else {
return false;
}
}
public overwriteValues(req: AddCustomLabelPolicyRequest | UpdateCustomLabelPolicyRequest): void {
req.setBackgroundColorDark(this.previewData.backgroundColorDark);
req.setBackgroundColor(this.previewData.backgroundColor);
req.setFontColorDark(this.previewData.fontColorDark);
req.setFontColor(this.previewData.fontColor);
req.setPrimaryColorDark(this.previewData.primaryColorDark);
req.setPrimaryColor(this.previewData.primaryColor);
req.setWarnColorDark(this.previewData.warnColorDark);
req.setWarnColor(this.previewData.warnColor);
req.setDisableWatermark(this.previewData.disableWatermark);
req.setHideLoginNameSuffix(this.previewData.hideLoginNameSuffix);
}
public activatePolicy(): Promise<any> {
// dialog warning
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).activateCustomLabelPolicy().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.ACTIVATED', true);
setTimeout(() => {
this.loadingImages = true;
this.getData().then(data => {
if (data.policy) {
this.data = data.policy;
this.loadImages();
}
});
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).activateLabelPolicy().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.ACTIVATED', true);
setTimeout(() => {
this.loadingImages = true;
this.getData().then(data => {
if (data.policy) {
this.data = data.policy;
this.loadImages();
}
});
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
public resetPolicy(): Promise<any> {
return (this.service as ManagementService).resetLabelPolicyToDefault().then(() => {
this.toast.showInfo('POLICY.PRIVATELABELING.RESET', true);
setTimeout(() => {
this.fetchData();
});
}).catch(error => {
this.toast.showError(error);
});
}
}

View File

@@ -0,0 +1,50 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
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';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { InputModule } from '../../input/input.module';
import { LinksModule } from '../../links/links.module';
import { ColorComponent } from './color/color.component';
import { PreviewComponent } from './preview/preview.component';
import { PrivateLabelingPolicyRoutingModule } from './private-labeling-policy-routing.module';
import { PrivateLabelingPolicyComponent } from './private-labeling-policy.component';
@NgModule({
declarations: [
PrivateLabelingPolicyComponent,
PreviewComponent,
ColorComponent,
],
imports: [
PrivateLabelingPolicyRoutingModule,
CommonModule,
FormsModule,
InputModule,
MatButtonModule,
MatSlideToggleModule,
MatIconModule,
HasRoleModule,
MatTooltipModule,
TranslateModule,
DetailLayoutModule,
DropzoneModule,
MatProgressSpinnerModule,
LinksModule,
MatExpansionModule,
InfoSectionModule,
HasFeaturePipeModule,
],
})
export class PrivateLabelingPolicyModule { }

View File

@@ -75,6 +75,38 @@
</div>
</ng-template>
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
<div class="p-item card">
<div class="avatar">
<i class="icon las la-gem"></i>
</div>
<div class="title">
<span>{{'POLICY.PRIVATELABELING_POLICY.TITLE' | translate}}</span>
<button mat-icon-button disabled>
<i class="icon las la-check-circle"></i>
</button>
</div>
<p class="desc">
{{'POLICY.PRIVATELABELING_POLICY.DESCRIPTION' | translate}}</p>
<!-- <cnsl-info-section class="warn"
*ngIf="type == PolicyGridType.ORG && (['password_complexity_policy'] | hasFeature | async) == false"
type="WARN">
{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'password_complexity_policy'})}}
</cnsl-info-section> -->
<span class="fill-space"></span>
<div class="btn-wrapper">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button
[routerLink]="[type == PolicyGridType.IAM ? '/iam' : type == PolicyGridType.ORG ? '/org' : '','policy', PolicyComponentType.PRIVATELABEL ]"
mat-stroked-button>{{'POLICY.BTN_EDIT' | translate}}</button>
</ng-template>
</div>
</div>
</ng-template>
<ng-template appHasRole
[appHasRole]="type == PolicyGridType.IAM ? ['iam.policy.read'] : type == PolicyGridType.ORG ? ['policy.read'] : []">
<div class="p-item card">
@@ -101,40 +133,4 @@
</div>
</div>
</ng-template>
<ng-container *ngIf="type === PolicyGridType.IAM">
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
<div class="p-item card">
<div class="avatar">
<i class="icon las la-envelope"></i>
</div>
<div class="title">
<span>{{'POLICY.LABEL.TITLE' | translate}}</span>
<button mat-icon-button disabled>
<i class="icon las la-check-circle"></i>
</button>
</div>
<p class="desc">
{{'POLICY.LABEL.DESCRIPTION' | translate}}</p>
<cnsl-info-section class="warn"
*ngIf="type == PolicyGridType.ORG && (['label_policy'] | hasFeature | async) == false" type="WARN">
{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'label_policy'})}}
</cnsl-info-section>
<span class="fill-space"></span>
<div class="btn-wrapper">
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button
[routerLink]="[type == PolicyGridType.IAM ? '/iam' : type == PolicyGridType.ORG ? '/org' : '','policy', PolicyComponentType.LABEL ]"
mat-stroked-button
[disabled]="type == PolicyGridType.ORG && (['label_policy'] | hasFeature | async) == false">
{{'POLICY.BTN_EDIT' | translate}}</button>
</ng-template>
</div>
</div>
</ng-template>
</ng-container>
</div>

View File

@@ -1,51 +1,58 @@
import { PolicyComponentType } from '../policies/policy-component-types.enum';
export const IAM_COMPLEXITY_LINK = {
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['iam.policy.read'],
};
export const IAM_POLICY_LINK = {
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
};
export const IAM_LOGIN_POLICY_LINK = {
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LOGIN],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LOGIN],
withRole: ['iam.policy.read'],
};
export const IAM_LABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.LABEL],
withRole: ['iam.policy.read'],
export const IAM_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/iam', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['iam.policy.read'],
};
export const ORG_COMPLEXITY_LINK = {
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['policy.read'],
i18nTitle: 'POLICY.PWD_COMPLEXITY.TITLE',
i18nDesc: 'POLICY.PWD_COMPLEXITY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.COMPLEXITY],
withRole: ['policy.read'],
};
export const ORG_IAM_POLICY_LINK = {
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
i18nTitle: 'POLICY.IAM_POLICY.TITLE',
i18nDesc: 'POLICY.IAM_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.IAM],
withRole: ['iam.policy.read'],
};
export const ORG_LOGIN_POLICY_LINK = {
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.LOGIN],
withRole: ['policy.read'],
i18nTitle: 'POLICY.LOGIN_POLICY.TITLE',
i18nDesc: 'POLICY.LOGIN_POLICY.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.LOGIN],
withRole: ['policy.read'],
};
export const ORG_PRIVATELABEL_LINK = {
i18nTitle: 'POLICY.LABEL.TITLE',
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
routerLink: ['/org', 'policy', PolicyComponentType.PRIVATELABEL],
withRole: ['policy.read'],
};

View File

@@ -9,119 +9,119 @@ import { EventstoreComponent } from './eventstore/eventstore.component';
import { IamComponent } from './iam.component';
const routes: Routes = [
{
path: 'policies',
component: IamComponent,
{
path: 'policies',
component: IamComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'eventstore',
component: EventstoreComponent,
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
},
},
{
path: 'members',
loadChildren: () => import('./iam-members/iam-members.module').then(m => m.IamMembersModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.member.read'],
},
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
// canActivate: [RoleGuard],
data: {
roles: ['iam.features.read'],
serviceType: FeatureServiceType.ADMIN,
},
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
roles: ['iam.idp.write'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'eventstore',
component: EventstoreComponent,
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.read'],
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'members',
loadChildren: () => import('./iam-members/iam-members.module').then(m => m.IamMembersModule),
canActivate: [AuthGuard, RoleGuard],
},
],
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
roles: ['iam.member.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
// canActivate: [RoleGuard],
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
roles: ['iam.features.read'],
serviceType: FeatureServiceType.ADMIN,
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.idp.write'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.ADMIN,
},
},
],
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
{
path: PolicyComponentType.LABEL,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/label-policy/label-policy.module')
.then(m => m.LabelPolicyModule),
},
],
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.PRIVATELABEL,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/private-labeling-policy/private-labeling-policy.module')
.then(m => m.PrivateLabelingPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.ADMIN,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class IamRoutingModule { }

View File

@@ -73,7 +73,7 @@ h2 {
.new-desc {
font-size: 14px;
color: #818a8a;
color: var(--grey);
}
.custom-domain-deactivated {

View File

@@ -8,108 +8,116 @@ import { OrgCreateComponent } from './org-create/org-create.component';
import { OrgDetailComponent } from './org-detail/org-detail.component';
const routes: Routes = [
{
{
path: 'create',
component: OrgCreateComponent,
canActivate: [RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
},
loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule),
},
{
path: 'idp',
children: [
{
path: 'create',
component: OrgCreateComponent,
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [RoleGuard],
data: {
roles: ['(org.create)?(iam.write)?'],
roles: ['org.idp.write'],
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule),
},
{
path: 'idp',
children: [
{
path: 'create',
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then(m => m.IdpCreateModule),
canActivate: [RoleGuard],
data: {
roles: ['org.idp.write'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [RoleGuard],
data: {
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
],
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
},
{
path: ':id',
loadChildren: () => import('src/app/modules/idp/idp.module').then(m => m.IdpModule),
canActivate: [RoleGuard],
data: {
roles: ['features.read'],
serviceType: FeatureServiceType.MGMT,
roles: ['iam.idp.read'],
serviceType: PolicyComponentServiceType.MGMT,
},
},
],
},
{
path: 'features',
loadChildren: () => import('src/app/modules/features/features.module').then(m => m.FeaturesModule),
canActivate: [RoleGuard],
data: {
roles: ['features.read'],
serviceType: FeatureServiceType.MGMT,
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
{
path: 'members',
loadChildren: () => import('./org-members/org-members.module').then(m => m.OrgMembersModule),
},
{
path: '',
component: OrgDetailComponent,
},
{
path: 'overview',
loadChildren: () => import('./org-list/org-list.module').then(m => m.OrgListModule),
},
},
{
path: 'policy',
children: [
{
path: PolicyComponentType.AGE,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-age-policy/password-age-policy.module')
.then(m => m.PasswordAgePolicyModule),
},
{
path: PolicyComponentType.LOCKOUT,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-lockout-policy/password-lockout-policy.module')
.then(m => m.PasswordLockoutPolicyModule),
},
{
path: PolicyComponentType.PRIVATELABEL,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/private-labeling-policy/private-labeling-policy.module')
.then(m => m.PrivateLabelingPolicyModule),
},
{
path: PolicyComponentType.COMPLEXITY,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/password-complexity-policy/password-complexity-policy.module')
.then(m => m.PasswordComplexityPolicyModule),
},
{
path: PolicyComponentType.IAM,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/org-iam-policy/org-iam-policy.module')
.then(m => m.OrgIamPolicyModule),
},
{
path: PolicyComponentType.LOGIN,
data: {
serviceType: PolicyComponentServiceType.MGMT,
},
loadChildren: () => import('src/app/modules/policies/login-policy/login-policy.module')
.then(m => m.LoginPolicyModule),
},
],
},
{
path: 'members',
loadChildren: () => import('./org-members/org-members.module').then(m => m.OrgMembersModule),
},
{
path: '',
component: OrgDetailComponent,
},
{
path: 'overview',
loadChildren: () => import('./org-list/org-list.module').then(m => m.OrgListModule),
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class OrgsRoutingModule { }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Org } from '../proto/generated/zitadel/org_pb';
import { StorageService } from './storage.service';
const ORG_STORAGE_KEY = 'organization';
const authorizationKey = 'Authorization';
const orgKey = 'x-zitadel-orgid';
const bearerPrefix = 'Bearer';
const accessTokenStorageKey = 'access_token';
export enum UploadEndpoint {
IAMFONT = 'iam/policy/label/font',
MGMTFONT = 'org/policy/label/font',
IAMDARKLOGO = 'iam/policy/label/logo/dark',
IAMLIGHTLOGO = 'iam/policy/label/logo',
IAMDARKICON = 'iam/policy/label/icon/dark',
IAMLIGHTICON = 'iam/policy/label/icon',
MGMTDARKLOGO = 'org/policy/label/logo/dark',
MGMTLIGHTLOGO = 'org/policy/label/logo',
MGMTDARKICON = 'org/policy/label/icon/dark',
MGMTLIGHTICON = 'org/policy/label/icon',
}
export enum DownloadEndpoint {
IAMFONT = 'iam/policy/label/font',
MGMTFONT = 'org/policy/label/font',
IAMDARKLOGO = 'iam/policy/label/logo/dark',
IAMLOGO = 'iam/policy/label/logo',
IAMDARKICON = 'iam/policy/label/icon/dark',
IAMICON = 'iam/policy/label/icon',
MGMTDARKLOGO = 'org/policy/label/logo/dark',
MGMTLOGO = 'org/policy/label/logo',
MGMTDARKICON = 'org/policy/label/icon/dark',
MGMTICON = 'org/policy/label/icon',
IAMDARKLOGOPREVIEW = 'iam/policy/label/logo/dark/_preview',
IAMLOGOPREVIEW = 'iam/policy/label/logo/_preview',
IAMDARKICONPREVIEW = 'iam/policy/label/icon/dark/_preview',
IAMICONPREVIEW = 'iam/policy/label/icon/_preview',
MGMTDARKLOGOPREVIEW = 'org/policy/label/logo/dark/_preview',
MGMTLOGOPREVIEW = 'org/policy/label/logo/_preview',
MGMTDARKICONPREVIEW = 'org/policy/label/icon/dark/_preview',
MGMTICONPREVIEW = 'org/policy/label/icon/_preview',
}
@Injectable({
providedIn: 'root',
})
export class UploadService {
private serviceUrl: string = '';
private accessToken: string = '';
private org!: Org.AsObject;
constructor(private http: HttpClient, private storageService: StorageService) {
http.get('./assets/environment.json')
.toPromise().then((data: any) => {
if (data && data.uploadServiceUrl) {
this.serviceUrl = data.uploadServiceUrl;
const aT = this.storageService.getItem(accessTokenStorageKey);
if (aT) {
this.accessToken = aT;
}
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
if (org) {
this.org = org;
}
}
}).catch(error => {
console.error(error);
});
}
public upload(endpoint: UploadEndpoint, body: any): Promise<any> {
return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`,
body,
{
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
public load(endpoint: string): Promise<any> {
return this.http.get(`${this.serviceUrl}/assets/v1/${endpoint}`,
{
responseType: 'blob',
headers: {
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
[orgKey]: `${this.org.id}`,
},
}).toPromise();
}
}

View File

@@ -1,8 +1,9 @@
{
"authServiceUrl": "https://api.zitadel.dev",
"mgmtServiceUrl": "https://api.zitadel.dev",
"adminServiceUrl":"https://api.zitadel.dev",
"subscriptionServiceUrl":"https://sub.zitadel.dev",
"issuer": "https://issuer.zitadel.dev",
"clientid": "70669160379706195@zitadel"
"authServiceUrl": "https://api.zitadel.io",
"mgmtServiceUrl": "https://api.zitadel.io",
"adminServiceUrl":"https://api.zitadel.io",
"subscriptionServiceUrl":"https://sub.zitadel.io",
"uploadServiceUrl":"https://api.zitadel.io",
"issuer": "https://issuer.zitadel.io",
"clientid": "69234247558357051@zitadel"
}

View File

@@ -104,8 +104,9 @@
}
},
"ACTIONS": {
"SHOW":"Aufklappen",
"HIDE":"Zuklappen",
"SET":"Übernehmen",
"SHOW":"Aufklappen",
"HIDE":"Zuklappen",
"SAVE": "Speichern",
"SAVENOW": "Speichern",
"NEW": "Neu",
@@ -613,7 +614,8 @@
"LOGINPOLICYFACTORS": "Login Richtlinie: Mltifaktoren - benutzerdefiniert",
"LOGINPOLICYPASSWORDLESS": "Login Richtlinie: Passwortlose Authentifizierung - benutzerdefiniert",
"LOGINPOLICYCOMPLEXITYPOLICY": "Passwortkomplexitäts Richtlinie - benutzerdefiniert",
"LABELPOLICY": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domänen Verifikation - verfügbar"
},
"TIERSTATES": {
@@ -636,6 +638,28 @@
"NUMBERERROR": "Das Password muss eine Ziffer beinhalten.",
"PATTERNERROR": "Das Passwort erfüllt nicht die vorgeschriebene Richtlinie."
},
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Verleihe dem Login deinen benutzerdefinierten Style und passe das Verhalten an.",
"PREVIEW_DESCRIPTION":"Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar. Um die Preview zu Testen muss dem login flow der scope 'x-preview' mitgegeben werden.",
"BTN":"Datei auswählen",
"ACTIVATEPREVIEW":"Konfiguration übernehmen",
"DARK":"Dunkler Modus",
"LIGHT":"Heller Modus",
"CHANGEVIEW":"Ansicht wechseln",
"ACTIVATED":"Richtlinie wurde LIVE geschaltet",
"THEME":"Modus",
"COLORS":"Farben",
"FONT":"Schrift",
"ADVANCEDBEHAVIOR":"Erweitertes Verhalten",
"PREVIEW": {
"TITLE":"Anmeldung",
"SECOND":"mit ZITADEL-Konto anmelden.",
"ERROR":"Benutzer konnte nicht gefunden werden!",
"PRIMARYBUTTON":"weiter",
"SECONDARYBUTTON":"registrieren"
}
},
"PWD_AGE": {
"TITLE": "Gültigkeitsdauer für Passwörter",
"DESCRIPTION": "Du kannst eine Richtlinie für die maximale Gültigkeitsdauer von Passwörtern festlegen. Diese Richtlinie löst eine Warnung nach Ablauf einer festgelegten Gültigkeitsdauer aus."
@@ -648,6 +672,10 @@
"TITLE": "Zugangseinstellungen IAM",
"DESCRIPTION": "Definiere die Zugangseistellungen für Benutzer."
},
"PRIVATELABELING_POLICY": {
"TITLE": "Private Labeling",
"DESCRIPTION": "Definiere das Erscheinungsbild des Logins."
},
"LOGIN_POLICY": {
"TITLE": "Login Richtlinien",
"DESCRIPTION": "Definiere die Loginmethoden für Benutzer",
@@ -655,13 +683,6 @@
"DESCRIPTIONCREATEMGMT": "Nutzer können sich mit den verfügbaren Idps authentifizieren. Achtung: Es kann zwischen System- und organisationsspezifischen Providern gewählt werden.",
"SAVED": "Erfolgreich gespeichert."
},
"LABEL": {
"TITLE": "Email Labelling Einstellungen",
"DESCRIPTION": "Definieren Sie das Erscheinungsbild Ihrer Benachrichtigungs-Mails",
"PRIMARYCOLOR": "Hintergrundfarbe",
"SECONDARYCOLOR": "Schriftfarbe",
"SAVED": "Erfolgreich gespeichert."
},
"DEFAULTLABEL": "Die aktuelle Richtlinie entspricht der IAM-Standard Einstellung.",
"BTN_INSTALL": "Installieren",
"BTN_EDIT": "Modifizieren",
@@ -685,14 +706,19 @@
"ALLOWREGISTER_DESC": "Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
"FORCEMFA": "Mfa erzwingen",
"FORCEMFA_DESC": "Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden.",
"HIDEPASSWORDRESET": "Passwort vergessen, nicht anzeigen",
"FORCEMFA_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link."
"HIDEPASSWORDRESET": "Passwort vergessen ausblenden",
"HIDEPASSWORDRESET_DESC": "Ist die Option gewählt, ist es nicht möglich im Login das Passwort zurück zusetzen via Passwort vergessen Link.",
"HIDELOGINNAMESUFFIX":"Loginname Suffix ausblenden",
"ERRORMSGPOPUP":"Fehler als Dialog Fenster",
"DISABLEWATERMARK":"Wasserzeichen ausblenden"
},
"RESET": "Richtlinie zurücksetzen",
"CREATECUSTOM": "Benutzerdefinierte Richtlinie erstellen",
"TOAST": {
"SET": "Richtline erfolgreich gesetzt!",
"RESETSUCCESS": "Richtline zurückgesetzt!"
"RESETSUCCESS": "Richtline zurückgesetzt!",
"UPLOADSUCCESS": "Upload erfolgreich",
"UPLOADFAILED":"Upload fehlgeschlagen!"
}
},
"ORG_DETAIL": {

View File

@@ -104,6 +104,7 @@
}
},
"ACTIONS": {
"SET":"Set",
"SHOW":"Show",
"HIDE":"Hide",
"SAVE": "Save",
@@ -613,7 +614,8 @@
"LOGINPOLICYFACTORS": "Login Policy: Multifactors - custom",
"LOGINPOLICYPASSWORDLESS": "Login Policy: Passwordless Authentication - custom",
"LOGINPOLICYCOMPLEXITYPOLICY": "Password Complexity Policy - custom",
"LABELPOLICY": "Labeling Policy - custom",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domain Verification - available"
},
"TIERSTATES": {
@@ -636,6 +638,28 @@
"NUMBERERROR": "The password must include a digit.",
"PATTERNERROR": "The password does not meet the required pattern."
},
"PRIVATELABELING": {
"TITLE":"Private Labeling",
"DESCRIPTION":"Give the login your personalized style and modify its behavior.",
"PREVIEW_DESCRIPTION":"Changes of the policy will automatically deployed to preview environment. To view those changes, a 'x-preview' scope will have to be added to your login scopes.",
"BTN":"Select File",
"ACTIVATEPREVIEW":"Set preview as current configuration",
"DARK":"Dark Mode",
"LIGHT":"Lighg Mode",
"CHANGEVIEW":"Change View",
"ACTIVATED":"Policy changes are now LIVE",
"THEME":"Theme",
"COLORS":"Colors",
"FONT":"Font",
"ADVANCEDBEHAVIOR":"Advanced Behavior",
"PREVIEW": {
"TITLE":"Login",
"SECOND":"login with your ZITADEL-Account.",
"ERROR":"User could not be found!",
"PRIMARYBUTTON":"next",
"SECONDARYBUTTON":"register"
}
},
"PWD_AGE": {
"TITLE": "Password Aging",
"DESCRIPTION": "You can set a policy for the aging of passwords. This policy emits a warning after the specific aging time has elapsed."
@@ -648,6 +672,12 @@
"TITLE": "IAM Access Preferences",
"DESCRIPTION": "Define access properties of your users."
},
"PRIVATELABELING_POLICY": {
"TITLE": "Private Labeling",
"BTN":"Select File",
"DESCRIPTION": "Customize the appearance of the Login",
"ACTIVATEPREVIEW":"Activate Configuration"
},
"LOGIN_POLICY": {
"TITLE": "Login Policy",
"DESCRIPTION": "Define how Users can be authenticated and configure Identity Providers",
@@ -655,13 +685,6 @@
"DESCRIPTIONCREATEMGMT": "Users can choose from the available identity providers below. Note: You can use System-set providers as well as providers set for your organisation only.",
"SAVED": "Saved successfully!"
},
"LABEL": {
"TITLE": "Email Labelling Settings",
"DESCRIPTION": "Change the look of your emails.",
"PRIMARYCOLOR": "Background color",
"SECONDARYCOLOR": "Font color",
"SAVED": "Saved successfully"
},
"DEFAULTLABEL": "The currently set guideline corresponds to the standard setting set by the IAM Administrator.",
"BTN_INSTALL": "Setup",
"BTN_EDIT": "Modify",
@@ -686,13 +709,18 @@
"FORCEMFA": "Force MFA",
"FORCEMFA_DESC": "If the option is selected, users have to configure a second factor for login.",
"HIDEPASSWORDRESET": "Hide Password reset",
"FORCEMFA_DESC": "If the option is selected, the user can't reset his password in the login process."
"HIDEPASSWORDRESET_DESC": "If the option is selected, the user can't reset his password in the login process.",
"HIDELOGINNAMESUFFIX":"Hide Loginname suffix",
"ERRORMSGPOPUP":"Show Error in Dialog",
"DISABLEWATERMARK":"Disable Watermark"
},
"RESET": "Reset Policy",
"CREATECUSTOM": "Create Custom Policy",
"TOAST": {
"SET": "Policy set successfully!",
"RESETSUCCESS": "Policy reset successfully!"
"RESETSUCCESS": "Policy reset successfully!",
"UPLOADSUCCESS": "Uploaded successfully!",
"UPLOADFAILED":"Upload failed!"
}
},
"ORG_DETAIL": {

View File

@@ -19,6 +19,7 @@
@import 'src/app/modules/meta-layout/meta.scss';
@import 'src/app/modules/zitadel-tier/zitadel-tier.component.scss';
@import 'src/app/modules/onboarding/onboarding.component.scss';
@import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss';
@mixin component-themes($theme) {
@include avatar-theme($theme);
@@ -42,4 +43,5 @@
@include info-section-theme($theme);
@include onboarding-theme($theme);
@include tier-theme($theme);
@include private-label-theme($theme);
}

View File

@@ -146,7 +146,6 @@ $dark-accent: mat.define-palette(mat.$pink-palette);
$dark-warn: mat.define-palette(mat.$red-palette);
$light-theme: mat.define-light-theme($light-primary, $light-accent, $light-warn);
$dark-theme: mat.define-dark-theme($dark-primary, $dark-accent, $dark-warn);
$custom-typography: mat.define-typography-config($font-family: 'Lato');