mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 00:27:24 +00:00
feat(console): update deps, alternative hash function with fixed colors, use preferrenLoginName for hashing, fix iam write permissions, user img upload (#1846)
* chore(deps-dev): bump @types/jasmine from 3.6.9 to 3.7.7 in /console (#1824) Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.6.9 to 3.7.7. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump google-protobuf from 3.15.8 to 3.17.2 in /console (#1823) Bumps [google-protobuf](https://github.com/protocolbuffers/protobuf) from 3.15.8 to 3.17.2. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.8...v3.17.2) --- updated-dependencies: - dependency-name: google-protobuf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @angular/animations from 12.0.0 to 12.0.3 in /console (#1821) Bumps [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/12.0.3/packages/animations) --- updated-dependencies: - dependency-name: "@angular/animations" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @angular/material from 12.0.0 to 12.0.3 in /console (#1819) Bumps [@angular/material](https://github.com/angular/components) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/12.0.3/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/12.0.0...12.0.3) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump prettier from 2.2.1 to 2.3.1 in /console (#1818) Bumps [prettier](https://github.com/prettier/prettier) from 2.2.1 to 2.3.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.2.1...2.3.1) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @angular/platform-browser-dynamic in /console (#1817) Bumps [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/12.0.3/packages/platform-browser-dynamic) --- updated-dependencies: - dependency-name: "@angular/platform-browser-dynamic" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @types/node from 14.14.37 to 15.12.1 in /console (#1815) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.37 to 15.12.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @angular/common from 12.0.0 to 12.0.3 in /console (#1814) Bumps [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/12.0.3/packages/common) --- updated-dependencies: - dependency-name: "@angular/common" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @angular/material-moment-adapter from 12.0.0 to 12.0.3 in /console (#1816) * chore(deps): bump @angular/material-moment-adapter in /console Bumps [@angular/material-moment-adapter](https://github.com/angular/components) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/12.0.3/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/12.0.0...12.0.3) --- updated-dependencies: - dependency-name: "@angular/material-moment-adapter" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * sort Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * chore(deps-dev): bump @angular/language-service in /console (#1822) Bumps [@angular/language-service](https://github.com/angular/angular/tree/HEAD/packages/language-service) from 12.0.0 to 12.0.3. - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/12.0.3/packages/language-service) --- updated-dependencies: - dependency-name: "@angular/language-service" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Max Peintner <max@caos.ch> * image cropper * fix: avatar bg colors, preferred username, login script * lint * membership color * rem logs * profile picture component * pic comp * dialog tirgger btn * trigger dialog, styles * lock * interceptor for org, upload, remove * tooltip * lint * stylelint * generate same credentials of username as in login * deletepic * fix disable privatelabeling on missing feature, i18n * lint * stylelint * block loading images if no feature * lint * optimize feature check Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
2502f379d9
commit
1e77b8aeae
16205
console/package-lock.json
generated
16205
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,16 +10,16 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~12.0.0",
|
||||
"@angular/animations": "~12.0.3",
|
||||
"@angular/cdk": "~12.0.0",
|
||||
"@angular/common": "~12.0.0",
|
||||
"@angular/common": "~12.0.3",
|
||||
"@angular/compiler": "~12.0.0",
|
||||
"@angular/core": "~12.0.0",
|
||||
"@angular/forms": "~12.0.0",
|
||||
"@angular/material": "^12.0.0",
|
||||
"@angular/material-moment-adapter": "^12.0.0",
|
||||
"@angular/material": "^12.0.3",
|
||||
"@angular/material-moment-adapter": "^12.0.3",
|
||||
"@angular/platform-browser": "~12.0.0",
|
||||
"@angular/platform-browser-dynamic": "~12.0.0",
|
||||
"@angular/platform-browser-dynamic": "~12.0.3",
|
||||
"@angular/router": "~12.0.0",
|
||||
"@angular/service-worker": "~12.0.0",
|
||||
"@grpc/grpc-js": "^1.3.2",
|
||||
@ -33,10 +33,11 @@
|
||||
"cors": "^2.8.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"google-proto-files": "^2.4.0",
|
||||
"google-protobuf": "^3.15.8",
|
||||
"google-protobuf": "^3.17.2",
|
||||
"grpc-web": "^1.2.1",
|
||||
"libphonenumber-js": "^1.9.16",
|
||||
"moment": "^2.29.1",
|
||||
"ngx-image-cropper": "^3.3.5",
|
||||
"ngx-color": "^7.0.0",
|
||||
"ngx-quicklink": "^0.2.6",
|
||||
"rxjs": "~6.6.7",
|
||||
@ -49,10 +50,10 @@
|
||||
"@angular-devkit/build-angular": "~12.0.0",
|
||||
"@angular/cli": "~12.0.0",
|
||||
"@angular/compiler-cli": "~12.0.0",
|
||||
"@angular/language-service": "~12.0.0",
|
||||
"@types/jasmine": "~3.6.9",
|
||||
"@angular/language-service": "~12.0.3",
|
||||
"@types/jasmine": "~3.7.7",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/node": "^15.12.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"jasmine-core": "~3.7.1",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
@ -61,7 +62,7 @@
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "^2.3.1",
|
||||
"protractor": "~7.0.0",
|
||||
"stylelint": "^13.10.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
|
@ -60,7 +60,7 @@
|
||||
<div (clickOutside)="closeAccountCard()" class="icon-container">
|
||||
<app-avatar
|
||||
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
|
||||
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount"
|
||||
class="avatar dontcloseonclick" (click)="showAccount = !showAccount" [active]="showAccount" [forColor]="user?.preferredLoginName"
|
||||
[name]="user.human.profile.displayName ? user.human.profile.displayName : (user.human.profile.firstName + ' '+ user.human.profile.lastName)"
|
||||
[size]="38">
|
||||
</app-avatar>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<app-avatar
|
||||
*ngIf="user.human?.profile && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
|
||||
class="avatar"
|
||||
[forColor]="user.preferredLoginName"
|
||||
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
|
||||
[size]="80">
|
||||
</app-avatar>
|
||||
@ -14,8 +15,7 @@
|
||||
<div class="l-accounts">
|
||||
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
<a class="row" *ngFor="let session of sessions" (click)="selectAccount(session.loginName)">
|
||||
<app-avatar *ngIf="session && session.displayName" class="small-avatar"
|
||||
[name]="session.displayName ? session.displayName : ''" [size]="32">
|
||||
<app-avatar *ngIf="session && session.displayName" class="small-avatar" [forColor]="session.loginName" [size]="32">
|
||||
</app-avatar>
|
||||
|
||||
<div class="col">
|
||||
|
@ -20,6 +20,7 @@ export class AccountsCardComponent implements OnInit {
|
||||
constructor(public authService: AuthenticationService, private router: Router, private userService: GrpcAuthService) {
|
||||
this.userService.listMyUserSessions().then(sessions => {
|
||||
this.sessions = sessions.resultList;
|
||||
console.log(sessions.resultList);
|
||||
const index = this.sessions.findIndex(user => user.loginName === this.user.preferredLoginName);
|
||||
if (index > -1) {
|
||||
this.sessions.splice(index, 1);
|
||||
|
@ -12,14 +12,14 @@ export class AvatarComponent implements OnInit {
|
||||
@Input() fontSize: number = 14;
|
||||
@Input() active: boolean = false;
|
||||
@Input() color: string = '';
|
||||
@Input() forColor: string = '';
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.credentials) {
|
||||
const split: string[] = this.name.split(' ');
|
||||
this.credentials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
|
||||
if (!this.credentials && this.forColor) {
|
||||
this.credentials = this.getInitials(this.forColor);
|
||||
if (!this.color) {
|
||||
this.color = this.getColor(this.name);
|
||||
this.color = this.getColor(this.forColor || '');
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,20 @@ export class AvatarComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
getInitials(fromName: string): string {
|
||||
const username = fromName.split('@')[0];
|
||||
let separator = '_';
|
||||
if (username.includes('-')) {
|
||||
separator = '-';
|
||||
}
|
||||
if (username.includes('.')) {
|
||||
separator = '.';
|
||||
}
|
||||
const split = username.split(separator);
|
||||
const initials = split[0].charAt(0) + (split[1] ? split[1].charAt(0) : '');
|
||||
return initials;
|
||||
}
|
||||
|
||||
getColor(userName: string): string {
|
||||
const colors = [
|
||||
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
|
||||
@ -52,13 +66,22 @@ export class AvatarComponent implements OnInit {
|
||||
if (userName.length === 0) {
|
||||
return colors[hash];
|
||||
}
|
||||
for (let i = 0; i < userName.length; i++) {
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
hash = userName.charCodeAt(i) + ((hash << 5) - hash);
|
||||
// tslint:disable-next-line: no-bitwise
|
||||
hash = hash & hash;
|
||||
|
||||
hash = this.hashCode(userName);
|
||||
return colors[hash % colors.length];
|
||||
}
|
||||
hash = ((hash % colors.length) + colors.length) % colors.length;
|
||||
return colors[hash];
|
||||
|
||||
// tslint:disable
|
||||
private hashCode(str: string, seed: number = 0): number {
|
||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
// tslint:enable
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="row">
|
||||
<app-avatar matTooltip="{{ dayelement.editorDisplayName }}"
|
||||
*ngIf="dayelement.editorDisplayName; else spacer" class="avatar"
|
||||
[name]="dayelement.editorDisplayName" [size]="32">
|
||||
[name]="dayelement.editorDisplayName" [size]="32" [forColor]="dayelement?.preferredLoginName">
|
||||
</app-avatar>
|
||||
<ng-template #spacer>
|
||||
<div class="spacer"></div>
|
||||
|
@ -248,12 +248,14 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Order by ascending property value
|
||||
// tslint:disable
|
||||
valueAscOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
|
||||
return a.value.localeCompare(b.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Order by descending property key
|
||||
keyDescOrder = (a: KeyValue<number, string>, b: KeyValue<number, string>): number => {
|
||||
return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0);
|
||||
}
|
||||
};
|
||||
// tslint:enable
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
[ngStyle]="{'z-index': 100 - i}">
|
||||
<app-avatar
|
||||
*ngIf="member && member.displayName && member.firstName && member.lastName; else cog"
|
||||
class="avatar dontcloseonclick"
|
||||
class="avatar dontcloseonclick" [forColor]="member?.userName"[forColor]="member?.preferredLoginName"
|
||||
[name]="member.displayName ? member.displayName : (member.firstName + ' '+ member.lastName)"
|
||||
[size]="32">
|
||||
</app-avatar>
|
||||
|
@ -22,7 +22,7 @@
|
||||
<mat-checkbox [disabled]="!canWrite" color="primary" (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
<app-avatar *ngIf="row?.displayName && row.firstName && row.lastName; else cog" class="avatar"
|
||||
[name]="row.displayName" [size]="32">
|
||||
[name]="row.displayName" [forColor]="row?.preferredLoginName" [size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon">
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<div class="content" *ngIf="loginData">
|
||||
<div class="row">
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.username_login'] | hasFeature | async) == false" ngDefaultControl
|
||||
[(ngModel)]="loginData.allowUsernamePassword">
|
||||
{{'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate}}
|
||||
</mat-slide-toggle>
|
||||
@ -62,7 +62,7 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false" ngDefaultControl
|
||||
[(ngModel)]="loginData.allowExternalIdp">
|
||||
{{'POLICY.DATA.ALLOWEXTERNALIDP' | translate}}
|
||||
</mat-slide-toggle>
|
||||
@ -79,7 +79,7 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.factors'] | hasFeature | async) == false" ngDefaultControl
|
||||
[(ngModel)]="loginData.forceMfa">
|
||||
{{'POLICY.DATA.FORCEMFA' | translate}}
|
||||
</mat-slide-toggle>
|
||||
@ -96,7 +96,7 @@
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled || serviceType == PolicyComponentServiceType.MGMT && (['login_policy.password_reset'] | hasFeature | async) == false" ngDefaultControl
|
||||
[(ngModel)]="loginData.hidePasswordReset">
|
||||
{{'POLICY.DATA.HIDEPASSWORDRESET' | translate}}
|
||||
</mat-slide-toggle>
|
||||
|
@ -98,7 +98,7 @@ export class LoginPolicyComponent implements OnDestroy {
|
||||
if (resp.policy) {
|
||||
this.loginData = resp.policy;
|
||||
this.loading = false;
|
||||
this.disabled = ((this.loginData as LoginPolicy.AsObject)?.isDefault) ?? false;
|
||||
this.disabled = this.isDefault;
|
||||
}
|
||||
});
|
||||
this.getIdps().then(resp => {
|
||||
|
@ -72,7 +72,6 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="img-wrapper" *ngIf="images['darkLogo']">
|
||||
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
|
||||
<img matTooltip="Current" class="curr" [src]="images['darkLogo']" alt="dark logo"/>
|
||||
</div>
|
||||
</div>
|
||||
@ -83,7 +82,6 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="img-wrapper" *ngIf="images['logo']">
|
||||
<!-- <mat-icon matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" class="dl-btn" (click)="deleteAsset(AssetType.LOGO, theme, Preview.PREVIEW)">remove_circle</mat-icon> -->
|
||||
<img matTooltip="Current" class="curr" [src]="images['logo']" alt="logo"/>
|
||||
</div>
|
||||
</div>
|
||||
@ -93,10 +91,10 @@
|
||||
[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();" />
|
||||
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
|
||||
|
||||
<i class="icon las la-cloud-upload-alt"></i>
|
||||
<span>{{isHoveringOverDarkLogo ? 'Release': 'Drop your Logo here'}}</span>
|
||||
<span>{{isHoveringOverDarkLogo ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -133,10 +131,10 @@
|
||||
[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();" />
|
||||
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFileIcon.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
|
||||
|
||||
<i class="icon las la-cloud-upload-alt"></i>
|
||||
<span>{{isHoveringOverDarkIcon ? 'Release': 'Drop your Logo here'}}</span>
|
||||
<span>{{isHoveringOverDarkIcon ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -158,19 +156,19 @@
|
||||
<ng-container *ngIf="theme==Theme.DARK">
|
||||
<div class="colors" *ngIf="data && previewData">
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color Dark" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.BACKGROUNDDARK" (previewChanged)="previewData.backgroundColorDark = $event" name="Background Color" [color]="data.backgroundColorDark" [previewColor]="previewData.backgroundColorDark"></cnsl-color>
|
||||
</div>
|
||||
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event" name="Preview Primary Color Dark" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.PRIMARY"(previewChanged)="previewData.primaryColorDark = $event" name="Primary Color" [color]="data.primaryColorDark" [previewColor]="previewData.primaryColorDark"></cnsl-color>
|
||||
</div>
|
||||
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event" name="Preview Warn Color Dark" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.WARN" (previewChanged)="previewData.warnColorDark = $event" name="Warn Color" [color]="data.warnColorDark" [previewColor]="previewData.warnColorDark"></cnsl-color>
|
||||
</div>
|
||||
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event" name="Font Color Dark" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.FONTDARK"(previewChanged)="previewData.fontColorDark = $event" name="Font Color" [color]="data.fontColorDark" [previewColor]="previewData.fontColorDark"></cnsl-color>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@ -178,15 +176,15 @@
|
||||
<ng-container *ngIf="theme==Theme.LIGHT">
|
||||
<div class="colors" *ngIf="data && previewData">
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color Light" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.BACKGROUNDLIGHT" (previewChanged)="previewData.backgroundColor = $event" name="Background Color" [color]="data.backgroundColor" [previewColor]="previewData.backgroundColor"></cnsl-color>
|
||||
</div>
|
||||
|
||||
<div class="color">
|
||||
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event" name="Preview Primary Color Light" [color]="data.primaryColor" [previewColor]="previewData.primaryColor"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.PRIMARY" (previewChanged)="previewData.primaryColor = $event" name="Primary Color" [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" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
|
||||
<cnsl-color [colorType]="ColorType.WARN" name="Warn Color" (previewChanged)="previewData.warnColor= $event" [color]="data.warnColor" [previewColor]="previewData.warnColor"></cnsl-color>
|
||||
</div>
|
||||
|
||||
<div class="color">
|
||||
@ -214,7 +212,7 @@
|
||||
<span>ABC • abc • 123</span>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
|
||||
<button [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" matTooltip="{{'ACTIONS.REMOVE' | translate}}" mat-icon-button color="warn" (click)="deleteFont()"><mat-icon>remove_circle</mat-icon></button>
|
||||
</div>
|
||||
|
||||
<div class="dropzone" cnslDropzone (hovered)="toggleHoverFont($event)"
|
||||
@ -222,10 +220,10 @@
|
||||
[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();" />
|
||||
<button mat-stroked-button class="btn" [disabled]="serviceType == PolicyComponentServiceType.MGMT && (['label_policy.private_label'] | hasFeature | async) == false" mat-raised-button type="button" (click)="selectedFontFile.click();">{{'POLICY.PRIVATELABELING.BTN' | translate}}</button>
|
||||
|
||||
<i class="icon las la-cloud-upload-alt"></i>
|
||||
<span >{{isHoveringOverFont ? 'Release': 'Drop your Logo here'}}</span>
|
||||
<span >{{isHoveringOverFont ? ('POLICY.PRIVATELABELING.RELEASE' | translate): ('POLICY.PRIVATELABELING.DROP' | translate)}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -203,14 +203,20 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.btn:not[disabled] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--grey);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
@ -274,6 +280,7 @@
|
||||
max-width: 120px;
|
||||
|
||||
.dl-btn {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -12px;
|
||||
|
@ -3,7 +3,7 @@ import { Component, EventEmitter, Injector, OnDestroy, Type } from '@angular/cor
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { switchMap, take } from 'rxjs/operators';
|
||||
import {
|
||||
GetLabelPolicyResponse as AdminGetLabelPolicyResponse,
|
||||
GetPreviewLabelPolicyResponse as AdminGetPreviewLabelPolicyResponse,
|
||||
@ -15,10 +15,13 @@ import {
|
||||
GetPreviewLabelPolicyResponse as MgmtGetPreviewLabelPolicyResponse,
|
||||
UpdateCustomLabelPolicyRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { LabelPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { AssetEndpoint, AssetService, AssetType } from 'src/app/services/asset.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
import { StorageService } from 'src/app/services/storage.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { CnslLinks } from '../../links/links.component';
|
||||
@ -45,6 +48,8 @@ export enum ColorType {
|
||||
BACKGROUNDLIGHT,
|
||||
}
|
||||
|
||||
const ORG_STORAGE_KEY = 'organization';
|
||||
|
||||
@Component({
|
||||
selector: 'app-private-labeling-policy',
|
||||
templateUrl: './private-labeling-policy.component.html',
|
||||
@ -86,14 +91,23 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
|
||||
public refreshPreview: EventEmitter<void> = new EventEmitter();
|
||||
public loadingImages: boolean = false;
|
||||
private org!: Org.AsObject;
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private route: ActivatedRoute,
|
||||
private toast: ToastService,
|
||||
private injector: Injector,
|
||||
private assetService: AssetService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private storageService: StorageService,
|
||||
) {
|
||||
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
|
||||
|
||||
if (org) {
|
||||
this.org = org;
|
||||
}
|
||||
|
||||
this.sub = this.route.data.pipe(switchMap(data => {
|
||||
this.serviceType = data.serviceType;
|
||||
|
||||
@ -134,17 +148,17 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
if (theme === Theme.DARK) {
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKLOGO, formData));
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKLOGO, formData, this.org.id));
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKLOGO, formData));
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKLOGO, formData, this.org.id));
|
||||
}
|
||||
}
|
||||
if (theme === Theme.LIGHT) {
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTLOGO, formData));
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTLOGO, formData, this.org.id));
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMLOGO, formData));
|
||||
return this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMLOGO, formData, this.org.id));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,9 +172,9 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
formData.append('file', file);
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData));
|
||||
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.MGMTFONT, formData, this.org.id));
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData));
|
||||
return this.handleFontUploadPromise(this.assetService.upload(AssetEndpoint.IAMFONT, formData, this.org.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -257,20 +271,20 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
if (theme === Theme.DARK) {
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKICON, formData));
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTDARKICON, formData, this.org.id));
|
||||
break;
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKICON, formData));
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMDARKICON, formData, this.org.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (theme === Theme.LIGHT) {
|
||||
switch (this.serviceType) {
|
||||
case PolicyComponentServiceType.MGMT:
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTICON, formData));
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.MGMTICON, formData, this.org.id));
|
||||
break;
|
||||
case PolicyComponentServiceType.ADMIN:
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMICON, formData));
|
||||
this.handleUploadPromise(this.assetService.upload(AssetEndpoint.IAMICON, formData, this.org.id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -291,7 +305,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
private handleUploadPromise(task: Promise<any>): Promise<any> {
|
||||
return task.then(() => {
|
||||
const enhTask = task.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
|
||||
setTimeout(() => {
|
||||
this.loadingImages = true;
|
||||
@ -304,21 +318,31 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
});
|
||||
}, 1000);
|
||||
}).catch(error => this.toast.showError(error));
|
||||
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT && ((this.previewData as LabelPolicy.AsObject).isDefault)) {
|
||||
return this.savePolicy().then(() => enhTask);
|
||||
} else {
|
||||
return enhTask;
|
||||
}
|
||||
}
|
||||
|
||||
public fetchData(): void {
|
||||
this.loading = true;
|
||||
|
||||
this.authService.canUseFeature(['label_policy.private_label']).pipe(take(1)).subscribe((canUse) => {
|
||||
this.getPreviewData().then(data => {
|
||||
console.log('preview', data);
|
||||
this.loadingImages = true;
|
||||
|
||||
if (data.policy) {
|
||||
this.previewData = data.policy;
|
||||
this.loading = false;
|
||||
|
||||
if ((canUse === true && this.serviceType === PolicyComponentServiceType.MGMT) ||
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN) {
|
||||
this.loadingImages = true;
|
||||
this.loadPreviewImages();
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
@ -330,11 +354,17 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
this.data = data.policy;
|
||||
this.loading = false;
|
||||
|
||||
if ((canUse === true && this.serviceType === PolicyComponentServiceType.MGMT) ||
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN) {
|
||||
// this.loadingImages = true;
|
||||
this.loadImages();
|
||||
}
|
||||
}
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private loadImages(): void {
|
||||
@ -451,7 +481,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
private loadAsset(imagekey: string, url: string): Promise<any> {
|
||||
return this.assetService.load(`${url}`).then(data => {
|
||||
return this.assetService.load(`${url}`, this.org.id).then(data => {
|
||||
const objectURL = URL.createObjectURL(data);
|
||||
this.images[imagekey] = this.sanitizer.bypassSecurityTrustUrl(objectURL);
|
||||
this.refreshPreview.emit();
|
||||
@ -462,7 +492,7 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
|
||||
public removePolicy(): void {
|
||||
if (this.service instanceof ManagementService) {
|
||||
this.service.resetPasswordComplexityPolicyToDefault().then(() => {
|
||||
this.service.resetLabelPolicyToDefault().then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
|
||||
setTimeout(() => {
|
||||
this.fetchData();
|
||||
@ -473,14 +503,14 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public savePolicy(): void {
|
||||
public savePolicy(): Promise<any> {
|
||||
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(() => {
|
||||
return (this.service as ManagementService).addCustomLabelPolicy(req0).then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||
}).catch((error: HttpErrorResponse) => {
|
||||
this.toast.showError(error);
|
||||
@ -489,22 +519,20 @@ export class PrivateLabelingPolicyComponent implements OnDestroy {
|
||||
const req1 = new UpdateCustomLabelPolicyRequest();
|
||||
this.overwriteValues(req1);
|
||||
|
||||
(this.service as ManagementService).updateCustomLabelPolicy(req1).then(() => {
|
||||
return (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(() => {
|
||||
return (this.service as AdminService).updateLabelPolicy(req).then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.SET', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,8 +22,8 @@ export const IAM_LOGIN_POLICY_LINK = {
|
||||
};
|
||||
|
||||
export const IAM_PRIVATELABEL_LINK = {
|
||||
i18nTitle: 'POLICY.LABEL.TITLE',
|
||||
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
|
||||
i18nTitle: 'POLICY.PRIVATELABELING.TITLE',
|
||||
i18nDesc: 'POLICY.PRIVATELABELING.DESCRIPTION',
|
||||
routerLink: ['/iam', 'policy', PolicyComponentType.PRIVATELABEL],
|
||||
withRole: ['iam.policy.read'],
|
||||
};
|
||||
@ -51,8 +51,8 @@ export const ORG_LOGIN_POLICY_LINK = {
|
||||
|
||||
|
||||
export const ORG_PRIVATELABEL_LINK = {
|
||||
i18nTitle: 'POLICY.LABEL.TITLE',
|
||||
i18nDesc: 'POLICY.LABEL.DESCRIPTION',
|
||||
i18nTitle: 'POLICY.PRIVATELABELING.TITLE',
|
||||
i18nDesc: 'POLICY.PRIVATELABELING.DESCRIPTION',
|
||||
routerLink: ['/org', 'policy', PolicyComponentType.PRIVATELABEL],
|
||||
withRole: ['policy.read'],
|
||||
};
|
||||
|
@ -57,7 +57,7 @@
|
||||
<div class="circle">
|
||||
<app-avatar
|
||||
*ngIf="user.human && user.human.displayName && user.human?.firstName && user.human?.lastName; else cog"
|
||||
class="avatar" [name]="user.human.displayName" [size]="32">
|
||||
class="avatar" [name]="user.human.displayName" [forColor]="user?.preferredLoginName" [size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon">
|
||||
|
@ -31,7 +31,7 @@
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
<app-avatar
|
||||
*ngIf="context !== UserGrantContext.USER && row && row?.displayName && row.firstName && row.lastName"
|
||||
class="avatar" [name]="row.displayName" [size]="32">
|
||||
class="avatar" [name]="row.displayName" [forColor]="row?.preferredLoginName" [size]="32">
|
||||
</app-avatar>
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
|
@ -3,6 +3,7 @@
|
||||
<app-avatar [routerLink]="['/users/me']"
|
||||
*ngIf="user && (user.human?.profile?.displayName || (user.human?.profile?.firstName && user.human?.profile?.lastName))"
|
||||
class="avatar"
|
||||
[forColor]="user?.preferredLoginName"
|
||||
[name]="user.human?.profile?.displayName ? user.human?.profile?.displayName : (user.human?.profile?.firstName + ' '+ user.human?.profile?.lastName)"
|
||||
[size]="100">
|
||||
</app-avatar>
|
||||
|
@ -103,7 +103,6 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
|
||||
this.grid = false;
|
||||
}
|
||||
this.dataSource.data = this.grantedProjectList;
|
||||
console.log(resp.resultList);
|
||||
|
||||
this.loadingSubject.next(false);
|
||||
}).catch(error => {
|
||||
|
@ -151,7 +151,6 @@ export class UserGrantCreateComponent implements OnDestroy {
|
||||
});
|
||||
break;
|
||||
case UserGrantContext.NONE:
|
||||
console.log('none');
|
||||
let tempGrantId;
|
||||
|
||||
if ((this.project as GrantedProject.AsObject)?.grantId) {
|
||||
|
@ -28,7 +28,7 @@
|
||||
</app-card>
|
||||
|
||||
<app-card *ngIf="user && user.human?.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
<app-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
|
||||
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
|
@ -48,6 +48,7 @@ export class AuthUserDetailComponent implements OnDestroy {
|
||||
this.userService.getMyUser().then(resp => {
|
||||
if (resp.user) {
|
||||
this.user = resp.user;
|
||||
console.log(resp.user);
|
||||
}
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
|
@ -1,9 +1,22 @@
|
||||
<form [formGroup]="profileForm" *ngIf="profileForm" (ngSubmit)="submitForm()">
|
||||
<div class="content">
|
||||
<div class="user-form-content">
|
||||
<div class="user-form-content inner">
|
||||
<button class="camera-wrapper" type="button" (click)="showEditImage ? openUploadDialog() : null">
|
||||
<div class="i-wrapper" *ngIf="showEditImage">
|
||||
<i class="las la-camera"></i>
|
||||
</div>
|
||||
<img class="pic" [src]="profilePic" *ngIf="profilePic"/>
|
||||
<app-avatar
|
||||
*ngIf="!profilePic && user && user.profile?.displayName && user.profile?.firstName && user?.profile.lastName"
|
||||
class="avatar" [name]="user.profile?.displayName" [forColor]="preferredLoginName" [size]="80">
|
||||
</app-avatar>
|
||||
</button>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="userName" />
|
||||
</cnsl-form-field>
|
||||
</div>
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="firstName" />
|
||||
|
@ -1,10 +1,60 @@
|
||||
|
||||
.content {
|
||||
.user-form-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
&.inner {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.camera-wrapper {
|
||||
margin: 0 .5rem;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
transition: all .3s ease;
|
||||
|
||||
.i-wrapper {
|
||||
border-radius: 50%;
|
||||
background-color: #00000050;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
font-size: 3rem;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.i-wrapper {
|
||||
background-color: #00000080;
|
||||
}
|
||||
}
|
||||
|
||||
.pic {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.formfield {
|
||||
flex: 1 1 33%;
|
||||
margin: 0 .5rem;
|
||||
|
@ -1,8 +1,13 @@
|
||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { AssetService } from 'src/app/services/asset.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-detail-form',
|
||||
@ -10,6 +15,8 @@ import { Gender, Human, User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
styleUrls: ['./detail-form.component.scss'],
|
||||
})
|
||||
export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
@Input() public showEditImage: boolean = false;
|
||||
@Input() public preferredLoginName: string = '';
|
||||
@Input() public username!: string;
|
||||
@Input() public user!: Human.AsObject;
|
||||
@Input() public disabled: boolean = false;
|
||||
@ -18,11 +25,18 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
|
||||
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
public profilePic: any = null;
|
||||
public profileForm!: FormGroup;
|
||||
|
||||
private sub: Subscription = new Subscription();
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private dialog: MatDialog,
|
||||
private assetService: AssetService,
|
||||
private toast: ToastService,
|
||||
private sanitizer: DomSanitizer,
|
||||
) {
|
||||
this.profileForm = this.fb.group({
|
||||
userName: [{ value: '', disabled: true }, [
|
||||
Validators.required,
|
||||
@ -34,6 +48,8 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
gender: [{ value: 0, disabled: this.disabled }],
|
||||
preferredLanguage: [{ value: '', disabled: this.disabled }],
|
||||
});
|
||||
|
||||
this.loadAvatar();
|
||||
}
|
||||
|
||||
public ngOnChanges(): void {
|
||||
@ -66,6 +82,29 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
|
||||
this.submitData.emit(this.profileForm.value);
|
||||
}
|
||||
|
||||
public openUploadDialog(): void {
|
||||
const dialogRef = this.dialog.open(ProfilePictureComponent, {
|
||||
data: {
|
||||
profilePic: this.profilePic,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public loadAvatar(): Promise<any> {
|
||||
return this.assetService.load(`users/me/avatar`).then(data => {
|
||||
const objectURL = URL.createObjectURL(data);
|
||||
this.profilePic = this.sanitizer.bypassSecurityTrustUrl(objectURL);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public get userName(): AbstractControl | null {
|
||||
return this.profileForm.get('userName');
|
||||
}
|
||||
|
@ -4,22 +4,32 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { ImageCropperModule } from 'ngx-image-cropper';
|
||||
import { DropzoneModule } from 'src/app/directives/dropzone/dropzone.module';
|
||||
import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
|
||||
import { DetailFormComponent } from './detail-form.component';
|
||||
import { ProfilePictureComponent } from './profile-picture/profile-picture.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DetailFormComponent,
|
||||
ProfilePictureComponent,
|
||||
],
|
||||
imports: [
|
||||
DropzoneModule,
|
||||
AvatarModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ImageCropperModule,
|
||||
TranslateModule,
|
||||
MatSelectModule,
|
||||
MatButtonModule,
|
||||
MatTooltipModule,
|
||||
MatIconModule,
|
||||
TranslateModule,
|
||||
InputModule,
|
||||
|
@ -0,0 +1,39 @@
|
||||
<span class="title" mat-dialog-title>{{'USER.PROFILE.AVATAR.UPLOADTITLE' | translate}}</span>
|
||||
<div mat-dialog-content>
|
||||
<p class="desc">{{'USER.PROFILE.AVATAR.CURRENT' | translate}}</p>
|
||||
<div class="current-pic-wrapper">
|
||||
<img class="pic" [src]="data.profilePic" *ngIf="data.profilePic"/>
|
||||
<span class="fill-space"></span>
|
||||
<input #selectedFile style="display: none;" class="file-input" type="file" (change)="onDrop($event)">
|
||||
<button class="btn" mat-stroked-button type="button" (click)="selectedFile.click();">{{'USER.PROFILE.AVATAR.UPLOADBTN' | translate}}</button>
|
||||
<button *ngIf="data.profilePic" matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" (click)="deletePic()" mat-icon-button><mat-icon>remove_circle</mat-icon></button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="imageChangedEvent">
|
||||
<p class="desc">{{'USER.PROFILE.AVATAR.PREVIEW' | translate}}</p>
|
||||
|
||||
<div class="cropped-preview" *ngIf="croppedImage">
|
||||
<img class="pic" [src]="croppedImage"/>
|
||||
<button color="primary" mat-raised-button (click)="upload()">{{'USER.PROFILE.AVATAR.UPLOAD' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<p class="error" *ngIf="showCropperError">{{'USER.PROFILE.AVATAR.CROPPERERROR' | translate}}</p>
|
||||
|
||||
<image-cropper
|
||||
class="cropper"
|
||||
[imageChangedEvent]="imageChangedEvent"
|
||||
[maintainAspectRatio]="true"
|
||||
[aspectRatio]="4 / 4"
|
||||
[roundCropper]="true"
|
||||
[autoCrop]="true"
|
||||
(imageCropped)="imageCropped($event)"
|
||||
(loadImageFailed)="loadImageFailed()"
|
||||
></image-cropper>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button color="primary" mat-raised-button class="ok-button" (click)="closeDialog()">
|
||||
{{'ACTIONS.CLOSE' | translate}}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,68 @@
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: var(--grey);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.current-pic-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.pic {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
background-color: #00000030;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f44336;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cropped-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
|
||||
.pic {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
object-fit: contain;
|
||||
border-radius: 50%;
|
||||
background-color: #00000030;
|
||||
}
|
||||
}
|
||||
|
||||
.cropper {
|
||||
margin: 1rem 0;
|
||||
width: auto;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.ok-button {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--cropper-outline-color: black !important;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfilePictureComponent } from './profile-picture.component';
|
||||
|
||||
describe('ProfilePictureComponent', () => {
|
||||
let component: ProfilePictureComponent;
|
||||
let fixture: ComponentFixture<ProfilePictureComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ProfilePictureComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfilePictureComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,99 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { ImageCroppedEvent } from 'ngx-image-cropper';
|
||||
import { AssetService } from 'src/app/services/asset.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-profile-picture',
|
||||
templateUrl: './profile-picture.component.html',
|
||||
styleUrls: ['./profile-picture.component.scss'],
|
||||
})
|
||||
export class ProfilePictureComponent implements OnInit {
|
||||
public isHovering: boolean = false;
|
||||
|
||||
public imageChangedEvent: any = '';
|
||||
public imageChangedFormat: string = '';
|
||||
public croppedImage: any = '';
|
||||
public showCropperError: boolean = false;
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
public dialogRef: MatDialogRef<ProfilePictureComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private toast: ToastService,
|
||||
private assetService: AssetService) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
}
|
||||
|
||||
public toggleHover(isHovering: boolean): void {
|
||||
this.isHovering = isHovering;
|
||||
}
|
||||
|
||||
public onDrop(event: any): Promise<any> | void {
|
||||
const filelist: FileList = event.target.files;
|
||||
console.log(event.target.files);
|
||||
this.imageChangedEvent = event;
|
||||
const file = filelist.item(0);
|
||||
if (file) {
|
||||
this.imageChangedFormat = file.type;
|
||||
}
|
||||
}
|
||||
|
||||
public deletePic(): void {
|
||||
console.log('delete');
|
||||
this.authService.removeMyAvatar().then(() => {
|
||||
this.toast.showInfo('USER.PROFILE.AVATAR.DELETESUCCESS', true);
|
||||
this.data.profilePic = null;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private handleUploadPromise(task: Promise<any>): Promise<any> {
|
||||
return task.then(() => {
|
||||
this.toast.showInfo('POLICY.TOAST.UPLOADSUCCESS', true);
|
||||
this.data.profilePic = this.croppedImage;
|
||||
}).catch(error => this.toast.showError(error));
|
||||
}
|
||||
|
||||
public fileChangeEvent(event: any): void {
|
||||
this.imageChangedEvent = event;
|
||||
}
|
||||
|
||||
public imageCropped(event: ImageCroppedEvent): void {
|
||||
this.showCropperError = false;
|
||||
this.croppedImage = event.base64;
|
||||
}
|
||||
|
||||
public upload(): Promise<any> | void {
|
||||
const formData = new FormData();
|
||||
const splitted = this.croppedImage.split(';base64,');
|
||||
if (splitted[1]) {
|
||||
const blob = this.base64toBlob(splitted[1]);
|
||||
formData.append('file', blob);
|
||||
return this.handleUploadPromise(this.assetService.upload('users/me/avatar', formData));
|
||||
}
|
||||
}
|
||||
|
||||
public loadImageFailed(): void {
|
||||
this.showCropperError = true;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
public base64toBlob(b64: string): Blob {
|
||||
const byteCharacters = atob(b64);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: this.imageChangedFormat });
|
||||
return blob;
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read$', 'user.read:'+user?.id]">
|
||||
<app-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
|
||||
<app-detail-form [disabled]="(canWrite$ | async) == false" [genders]="genders" [languages]="languages"
|
||||
<app-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) == false" [genders]="genders" [languages]="languages"
|
||||
[username]="user.userName" [user]="user.human" (submitData)="saveProfile($event)">
|
||||
</app-detail-form>
|
||||
</app-card>
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
<app-avatar
|
||||
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
|
||||
class="avatar" [name]="user.human.profile.displayName" [size]="32">
|
||||
class="avatar" [name]="user.human.profile.displayName" [forColor]="user?.preferredLoginName" [size]="32">
|
||||
</app-avatar>
|
||||
<ng-template #cog>
|
||||
<div class="sa-icon" *ngIf="user.machine">
|
||||
|
@ -3,10 +3,8 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { PolicyComponentServiceType } from '../modules/policies/policy-component-types.enum';
|
||||
import { Theme } from '../modules/policies/private-labeling-policy/private-labeling-policy.component';
|
||||
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';
|
||||
|
||||
@ -70,62 +68,64 @@ export const ENDPOINT = {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AssetService {
|
||||
private serviceUrl: string = '';
|
||||
private serviceUrl!: Promise<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;
|
||||
this.serviceUrl = this.getServiceUrl();
|
||||
}
|
||||
|
||||
private async getServiceUrl(): Promise<string> {
|
||||
const url = await this.http.get('./assets/environment.json')
|
||||
.toPromise().then((data: any) => {
|
||||
if (data && data.assetServiceUrl) {
|
||||
console.log(data.assetServiceUrl);
|
||||
return data.assetServiceUrl;
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public upload(endpoint: AssetEndpoint, body: any): Promise<any> {
|
||||
return this.http.post(`${this.serviceUrl}/assets/v1/${endpoint}`,
|
||||
public upload(endpoint: AssetEndpoint | string, body: any, orgId?: string): Promise<any> {
|
||||
const headers: any = {
|
||||
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
|
||||
};
|
||||
|
||||
if (orgId) {
|
||||
headers[orgKey] = `${orgId}`;
|
||||
}
|
||||
|
||||
return this.serviceUrl.then((url) =>
|
||||
this.http.post(`${url}/assets/v1/${endpoint}`,
|
||||
body,
|
||||
{
|
||||
headers: {
|
||||
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
|
||||
[orgKey]: `${this.org.id}`,
|
||||
},
|
||||
}).toPromise();
|
||||
headers: headers,
|
||||
}).toPromise(),
|
||||
);
|
||||
}
|
||||
|
||||
public load(endpoint: string): Promise<any> {
|
||||
return this.http.get(`${this.serviceUrl}/assets/v1/${endpoint}`,
|
||||
public load(endpoint: string, orgId?: string): Promise<any> {
|
||||
const headers: any = {
|
||||
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
|
||||
};
|
||||
|
||||
if (orgId) {
|
||||
headers[orgKey] = `${orgId}`;
|
||||
}
|
||||
|
||||
return this.serviceUrl.then((url) =>
|
||||
this.http.get(`${url}/assets/v1/${endpoint}`,
|
||||
{
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
|
||||
[orgKey]: `${this.org.id}`,
|
||||
},
|
||||
}).toPromise();
|
||||
}
|
||||
|
||||
public delete(endpoint: AssetEndpoint): Promise<any> {
|
||||
return this.http.delete(`${this.serviceUrl}/assets/v1/${endpoint}`,
|
||||
{
|
||||
headers: {
|
||||
[authorizationKey]: `${bearerPrefix} ${this.accessToken}`,
|
||||
[orgKey]: `${this.org.id}`,
|
||||
},
|
||||
}).toPromise();
|
||||
headers: headers,
|
||||
}).toPromise(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ import {
|
||||
RemoveMyAuthFactorOTPResponse,
|
||||
RemoveMyAuthFactorU2FRequest,
|
||||
RemoveMyAuthFactorU2FResponse,
|
||||
RemoveMyAvatarRequest,
|
||||
RemoveMyAvatarResponse,
|
||||
RemoveMyLinkedIDPRequest,
|
||||
RemoveMyLinkedIDPResponse,
|
||||
RemoveMyPasswordlessRequest,
|
||||
@ -412,6 +414,11 @@ export class GrpcAuthService {
|
||||
return this.grpcService.auth.removeMyLinkedIDP(req, null).then(resp => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyAvatar(): Promise<RemoveMyAvatarResponse.AsObject> {
|
||||
const req = new RemoveMyAvatarRequest();
|
||||
return this.grpcService.auth.removeMyAvatar(req, null).then(resp => resp.toObject());
|
||||
}
|
||||
|
||||
public listMyLinkedIDPs(
|
||||
limit: number,
|
||||
offset: number,
|
||||
|
@ -27,7 +27,6 @@ export class GrpcService {
|
||||
private authenticationService: AuthenticationService,
|
||||
private storageService: StorageService,
|
||||
private dialog: MatDialog,
|
||||
// private toast: ToastService,
|
||||
) { }
|
||||
|
||||
public async loadAppEnvironment(): Promise<any> {
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { OAuthModuleConfig } from 'angular-oauth2-oidc';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Org } from '../../proto/generated/zitadel/org_pb';
|
||||
import { StorageService } from '../storage.service';
|
||||
|
||||
const orgKey = 'x-zitadel-orgid';
|
||||
const ORG_STORAGE_KEY = 'organization';
|
||||
export abstract class HttpOrgInterceptor implements HttpInterceptor {
|
||||
private org!: Org.AsObject;
|
||||
|
||||
protected get validUrls(): string[] {
|
||||
return this.oauthModuleConfig.resourceServer.allowedUrls || [];
|
||||
}
|
||||
|
||||
constructor(
|
||||
private storageService: StorageService,
|
||||
protected oauthModuleConfig: OAuthModuleConfig,
|
||||
) {
|
||||
const org: Org.AsObject | null = (this.storageService.getItem(ORG_STORAGE_KEY));
|
||||
|
||||
if (org) {
|
||||
this.org = org;
|
||||
}
|
||||
}
|
||||
|
||||
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (!this.urlValidation(req.url)) {
|
||||
return next.handle(req);
|
||||
}
|
||||
|
||||
return next.handle(req.clone({
|
||||
setHeaders: {
|
||||
[orgKey]: this.org.id
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
private urlValidation(toIntercept: string): boolean {
|
||||
const URLS = ['https://api.zitadel.dev/assets', 'https://api.zitadel.ch/assets'];
|
||||
|
||||
return URLS.findIndex(url => toIntercept.startsWith(url)) > -1;
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
"mgmtServiceUrl": "https://api.zitadel.dev",
|
||||
"adminServiceUrl":"https://api.zitadel.dev",
|
||||
"subscriptionServiceUrl":"https://sub.zitadel.dev",
|
||||
"uploadServiceUrl":"https://api.zitadel.dev",
|
||||
"assetServiceUrl":"https://api.zitadel.dev",
|
||||
"issuer": "https://issuer.zitadel.dev",
|
||||
"clientid": "70669160379706195@zitadel"
|
||||
}
|
||||
|
@ -306,7 +306,16 @@
|
||||
"DISPLAYNAME": "Anzeigename",
|
||||
"PREFERRED_LANGUAGE": "Sprache",
|
||||
"GENDER": "Geschlecht",
|
||||
"PASSWORD": "Passwort"
|
||||
"PASSWORD": "Passwort",
|
||||
"AVATAR": {
|
||||
"UPLOADTITLE":"Profilfoto uploaden",
|
||||
"UPLOADBTN":"Datei auswählen",
|
||||
"UPLOAD":"Hochladen",
|
||||
"CURRENT":"Aktuelles Bild",
|
||||
"PREVIEW":"Vorschau",
|
||||
"DELETESUCCESS":"Erfolgreich gelöscht!",
|
||||
"CROPPERERROR":"Ein Fehler beim hochladen Ihrer Datei ist fehlgeschlagen. Versuchen Sie es mit ggf mit einem anderen Format und Grösse."
|
||||
}
|
||||
},
|
||||
"MACHINE": {
|
||||
"TITLE": "Details Service-Benutzer",
|
||||
@ -642,7 +651,7 @@
|
||||
"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.",
|
||||
"PREVIEW_DESCRIPTION":"Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar.",
|
||||
"BTN":"Datei auswählen",
|
||||
"ACTIVATEPREVIEW":"Konfiguration übernehmen",
|
||||
"DARK":"Dunkler Modus",
|
||||
@ -653,6 +662,8 @@
|
||||
"COLORS":"Farben",
|
||||
"FONT":"Schrift",
|
||||
"ADVANCEDBEHAVIOR":"Erweitertes Verhalten",
|
||||
"DROP":"Bild hier ablegen",
|
||||
"RELEASE":"Jetzt loslassen",
|
||||
"PREVIEW": {
|
||||
"TITLE":"Anmeldung",
|
||||
"SECOND":"mit ZITADEL-Konto anmelden.",
|
||||
|
@ -306,7 +306,16 @@
|
||||
"DISPLAYNAME": "Display Name",
|
||||
"PREFERRED_LANGUAGE": "Language",
|
||||
"GENDER": "Gender",
|
||||
"PASSWORD": "Password"
|
||||
"PASSWORD": "Password",
|
||||
"AVATAR": {
|
||||
"UPLOADTITLE":"Upload your Profile Picture",
|
||||
"UPLOADBTN":"Choose file",
|
||||
"UPLOAD":"Upload",
|
||||
"CURRENT":"Current Picture",
|
||||
"PREVIEW":"Preview",
|
||||
"DELETESUCCESS":"Deleted successfully!",
|
||||
"CROPPERERROR":"An error while uploading your file failed. Try a different format and size if necessary."
|
||||
}
|
||||
},
|
||||
"MACHINE": {
|
||||
"TITLE": "Service User Details",
|
||||
@ -642,7 +651,7 @@
|
||||
"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.",
|
||||
"PREVIEW_DESCRIPTION":"Changes of the policy will automatically deployed to preview environment.",
|
||||
"BTN":"Select File",
|
||||
"ACTIVATEPREVIEW":"Set preview as current configuration",
|
||||
"DARK":"Dark Mode",
|
||||
@ -653,6 +662,8 @@
|
||||
"COLORS":"Colors",
|
||||
"FONT":"Font",
|
||||
"ADVANCEDBEHAVIOR":"Advanced Behavior",
|
||||
"DROP":"Drop image here",
|
||||
"RELEASE":"Release",
|
||||
"PREVIEW": {
|
||||
"TITLE":"Login",
|
||||
"SECOND":"login with your ZITADEL-Account.",
|
||||
|
@ -118,7 +118,6 @@ export class AuthenticationService {
|
||||
public async authenticate(
|
||||
setState: boolean = true,
|
||||
): Promise<boolean> {
|
||||
console.log('auth');
|
||||
this.oauthService.configure(this.authConfig);
|
||||
|
||||
this.oauthService.strictDiscoveryDocumentValidation = false;
|
||||
|
2
go.mod
2
go.mod
@ -69,7 +69,7 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/stdout v0.13.0
|
||||
go.opentelemetry.io/otel/sdk v0.13.0
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/text v0.3.6
|
||||
golang.org/x/tools v0.1.0
|
||||
google.golang.org/api v0.34.0
|
||||
|
@ -20,17 +20,43 @@ for (let i = 0; i < avatars.length; i++) {
|
||||
}
|
||||
}
|
||||
|
||||
function getColor(username) {
|
||||
const s = 40;
|
||||
const l = 50;
|
||||
const l2 = 62.5;
|
||||
function getColor(userName) {
|
||||
const colors = [
|
||||
'linear-gradient(40deg, #B44D51 30%, rgb(241,138,138))',
|
||||
'linear-gradient(40deg, #B75073 30%, rgb(234,96,143))',
|
||||
'linear-gradient(40deg, #84498E 30%, rgb(214,116,230))',
|
||||
'linear-gradient(40deg, #705998 30%, rgb(163,131,220))',
|
||||
'linear-gradient(40deg, #5C6598 30%, rgb(135,148,222))',
|
||||
'linear-gradient(40deg, #7F90D3 30%, rgb(181,196,247))',
|
||||
'linear-gradient(40deg, #3E93B9 30%, rgb(150,215,245))',
|
||||
'linear-gradient(40deg, #3494A0 30%, rgb(71,205,222))',
|
||||
'linear-gradient(40deg, #25716A 30%, rgb(58,185,173))',
|
||||
'linear-gradient(40deg, #427E41 30%, rgb(97,185,96))',
|
||||
'linear-gradient(40deg, #89A568 30%, rgb(176,212,133))',
|
||||
'linear-gradient(40deg, #90924D 30%, rgb(187,189,98))',
|
||||
'linear-gradient(40deg, #E2B032 30%, rgb(245,203,99))',
|
||||
'linear-gradient(40deg, #C97358 30%, rgb(245,148,118))',
|
||||
'linear-gradient(40deg, #6D5B54 30%, rgb(152,121,108))',
|
||||
'linear-gradient(40deg, #6B7980 30%, rgb(134,163,177))',
|
||||
];
|
||||
|
||||
let hash = 0;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + ((hash << 5) - hash);
|
||||
if (userName.length === 0) {
|
||||
return colors[hash];
|
||||
}
|
||||
|
||||
const h = hash % 360;
|
||||
const col1 = 'hsl(' + h + ', ' + s + '%, ' + l + '%)';
|
||||
const col2 = 'hsl(' + h + ', ' + s + '%, ' + l2 + '%)';
|
||||
return 'linear-gradient(40deg, ' + col1 + ' 30%, ' + col2 + ')';
|
||||
hash = this.hashCode(userName);
|
||||
return colors[hash % colors.length];
|
||||
}
|
||||
|
||||
function hashCode(str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user