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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

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] <[email protected]>

* sort

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <[email protected]>

* 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] <[email protected]>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Peintner <[email protected]>

* 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:
Max Peintner
2021-06-11 11:15:04 +02:00
committed by GitHub
co-authored by dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
parent 2502f379d9
commit 1e77b8aeae
45 changed files with 11966 additions and 8112 deletions
@@ -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 => {
@@ -13,190 +13,189 @@ import { StorageKey, StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-user-grant-create',
templateUrl: './user-grant-create.component.html',
styleUrls: ['./user-grant-create.component.scss'],
selector: 'app-user-grant-create',
templateUrl: './user-grant-create.component.html',
styleUrls: ['./user-grant-create.component.scss'],
})
export class UserGrantCreateComponent implements OnDestroy {
public context!: UserGrantContext;
public context!: UserGrantContext;
public org!: Org.AsObject;
public userId: string = '';
public org!: Org.AsObject;
public userId: string = '';
public projectId: string = '';
public project!: GrantedProject.AsObject | Project.AsObject;
public projectId: string = '';
public project!: GrantedProject.AsObject | Project.AsObject;
public grantId: string = '';
public rolesList: string[] = [];
public grantId: string = '';
public rolesList: string[] = [];
public STEPS: number = 2; // project, roles
public currentCreateStep: number = 1;
public STEPS: number = 2; // project, roles
public currentCreateStep: number = 1;
public filterValue: string = '';
public filterValue: string = '';
private subscription: Subscription = new Subscription();
private subscription: Subscription = new Subscription();
public UserGrantContext: any = UserGrantContext;
public UserGrantContext: any = UserGrantContext;
public grantedRoleKeysList: string[] = [];
public grantedRoleKeysList: string[] = [];
public user!: User.AsObject;
public UserTarget: any = UserTarget;
public user!: User.AsObject;
public UserTarget: any = UserTarget;
constructor(
private userService: ManagementService,
private toast: ToastService,
private _location: Location,
private route: ActivatedRoute,
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private storage: StorageService,
) {
this.subscription = this.route.params.subscribe((params: Params) => {
const { projectid, grantid, userid } = params;
this.context = UserGrantContext.NONE;
constructor(
private userService: ManagementService,
private toast: ToastService,
private _location: Location,
private route: ActivatedRoute,
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private storage: StorageService,
) {
this.subscription = this.route.params.subscribe((params: Params) => {
const { projectid, grantid, userid } = params;
this.context = UserGrantContext.NONE;
this.projectId = projectid;
this.grantId = grantid;
this.userId = userid;
this.projectId = projectid;
this.grantId = grantid;
this.userId = userid;
if (this.projectId && !this.grantId) {
this.context = UserGrantContext.OWNED_PROJECT;
if (this.projectId && !this.grantId) {
this.context = UserGrantContext.OWNED_PROJECT;
this.mgmtService.getProjectByID(this.projectId).then(resp => {
if (resp.project) {
this.project = resp.project;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.projectId && this.grantId) {
this.context = UserGrantContext.GRANTED_PROJECT;
this.mgmtService.getGrantedProjectByID(this.projectId, this.grantId).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
}
if (resp.grantedProject?.grantedRoleKeysList) {
this.grantedRoleKeysList = resp.grantedProject?.grantedRoleKeysList;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.userId) {
this.context = UserGrantContext.USER;
this.mgmtService.getUserByID(this.userId).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
this.mgmtService.getProjectByID(this.projectId).then(resp => {
if (resp.project) {
this.project = resp.project;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.projectId && this.grantId) {
this.context = UserGrantContext.GRANTED_PROJECT;
this.mgmtService.getGrantedProjectByID(this.projectId, this.grantId).then(resp => {
if (resp.grantedProject) {
this.project = resp.grantedProject;
}
if (resp.grantedProject?.grantedRoleKeysList) {
this.grantedRoleKeysList = resp.grantedProject?.grantedRoleKeysList;
}
}).catch((error: any) => {
this.toast.showError(error);
});
} else if (this.userId) {
this.context = UserGrantContext.USER;
this.mgmtService.getUserByID(this.userId).then(resp => {
if (resp.user) {
this.user = resp.user;
}
}).catch((error: any) => {
this.toast.showError(error);
});
}
});
const temporg = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (temporg) {
this.org = temporg;
const temporg = this.storage.getItem<Org.AsObject>(StorageKey.organization);
if (temporg) {
this.org = temporg;
}
}
public close(): void {
this._location.back();
}
public addGrant(): void {
switch (this.context) {
case UserGrantContext.OWNED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.GRANTED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
this.grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.USER:
let grantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
grantId = (this.project as GrantedProject.AsObject).grantId;
}
}
public close(): void {
this._location.back();
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.NONE:
let tempGrantId;
public addGrant(): void {
switch (this.context) {
case UserGrantContext.OWNED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.GRANTED_PROJECT:
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
this.grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.USER:
let grantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
grantId = (this.project as GrantedProject.AsObject).grantId;
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
grantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
case UserGrantContext.NONE:
console.log('none');
let tempGrantId;
if ((this.project as GrantedProject.AsObject)?.grantId) {
tempGrantId = (this.project as GrantedProject.AsObject).grantId;
}
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
tempGrantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
if ((this.project as GrantedProject.AsObject)?.grantId) {
tempGrantId = (this.project as GrantedProject.AsObject).grantId;
}
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.project = project;
this.projectId = project.id || project.projectId;
this.grantedRoleKeysList = project.grantedRoleKeysList ?? [];
this.userService.addUserGrant(
this.userId,
this.rolesList,
this.projectId,
tempGrantId,
).then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUSERGRANTADDED', true);
this.close();
}).catch((error: any) => {
this.toast.showError(error);
});
break;
}
}
public selectUser(user: User.AsObject): void {
this.userId = user.id;
}
public selectProject(project: Project.AsObject | GrantedProject.AsObject | any): void {
this.project = project;
this.projectId = project.id || project.projectId;
public selectRoles(roles: Role.AsObject[]): void {
this.rolesList = roles.map(role => role.key);
}
this.grantedRoleKeysList = project.grantedRoleKeysList ?? [];
}
public next(): void {
this.currentCreateStep++;
}
public selectUser(user: User.AsObject): void {
this.userId = user.id;
}
public previous(): void {
this.currentCreateStep--;
}
public selectRoles(roles: Role.AsObject[]): void {
this.rolesList = roles.map(role => role.key);
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public next(): void {
this.currentCreateStep++;
}
public previous(): void {
this.currentCreateStep--;
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
@@ -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>
@@ -11,196 +11,197 @@ import { ToastService } from 'src/app/services/toast.service';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
@Component({
selector: 'app-auth-user-detail',
templateUrl: './auth-user-detail.component.html',
styleUrls: ['./auth-user-detail.component.scss'],
selector: 'app-auth-user-detail',
templateUrl: './auth-user-detail.component.html',
styleUrls: ['./auth-user-detail.component.scss'],
})
export class AuthUserDetailComponent implements OnDestroy {
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
public user!: User.AsObject;
public genders: Gender[] = [Gender.GENDER_MALE, Gender.GENDER_FEMALE, Gender.GENDER_DIVERSE];
public languages: string[] = ['de', 'en'];
private subscription: Subscription = new Subscription();
private subscription: Subscription = new Subscription();
public loading: boolean = false;
public loading: boolean = false;
public copied: string = '';
public copied: string = '';
public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false;
public UserState: any = UserState;
public ChangeType: any = ChangeType;
public userLoginMustBeDomain: boolean = false;
public UserState: any = UserState;
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public USERGRANTCONTEXT: UserGrantContext = UserGrantContext.USER;
public refreshChanges$: EventEmitter<void> = new EventEmitter();
constructor(
public translate: TranslateService,
private toast: ToastService,
public userService: GrpcAuthService,
private dialog: MatDialog,
) {
this.loading = true;
constructor(
public translate: TranslateService,
private toast: ToastService,
public userService: GrpcAuthService,
private dialog: MatDialog,
) {
this.loading = true;
this.refreshUser();
}
refreshUser(): void {
this.refreshChanges$.emit();
this.userService.getMyUser().then(resp => {
if (resp.user) {
this.user = resp.user;
console.log(resp.user);
}
this.loading = false;
}).catch(error => {
this.toast.showError(error);
this.loading = false;
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.userService
.updateMyProfile(
this.user.human.profile?.firstName,
this.user.human.profile?.lastName,
this.user.human.profile?.nickName,
this.user.human.profile?.displayName,
this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender,
)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public saveEmail(email: string): void {
this.userService
.setMyEmail(email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
const mailToSet = new Email();
mailToSet.setEmail(email);
this.user.human.email = mailToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
public enteredPhoneCode(code: string): void {
this.userService.verifyMyPhone(code).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.refreshUser();
}).catch(error => {
this.toast.showError(error);
});
}
public changedLanguage(language: string): void {
this.translate.use(language);
}
public resendPhoneVerification(): void {
this.userService.resendMyPhoneVerification().then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public resendEmailVerification(): void {
this.userService.resendMyEmailVerification().then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
});
}
public deletePhone(): void {
this.userService.removeMyPhone().then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human?.phone) {
const phone = new Phone();
this.user.human.phone = phone.toObject();
this.refreshUser();
}
}
}).catch(error => {
this.toast.showError(error);
});
}
refreshUser(): void {
this.refreshChanges$.emit();
this.userService.getMyUser().then(resp => {
if (resp.user) {
this.user = resp.user;
}
this.loading = false;
}).catch(error => {
this.toast.showError(error);
this.loading = false;
});
}
public ngOnDestroy(): void {
this.subscription.unsubscribe();
}
public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) {
this.user.human.profile = profileData;
this.userService
.updateMyProfile(
this.user.human.profile?.firstName,
this.user.human.profile?.lastName,
this.user.human.profile?.nickName,
this.user.human.profile?.displayName,
this.user.human.profile?.preferredLanguage,
this.user.human.profile?.gender,
)
.then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit();
})
.catch(error => {
this.toast.showError(error);
});
}
}
public saveEmail(email: string): void {
this.userService
.setMyEmail(email).then(() => {
this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
if (this.user.human) {
const mailToSet = new Email();
mailToSet.setEmail(email);
this.user.human.email = mailToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
public enteredPhoneCode(code: string): void {
this.userService.verifyMyPhone(code).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
public savePhone(phone: string): void {
if (this.user.human) {
this.userService
.setMyPhone(phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
const phoneToSet = new Phone();
phoneToSet.setPhone(phone);
this.user.human.phone = phoneToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
this.toast.showError(error);
});
}
}
public changedLanguage(language: string): void {
this.translate.use(language);
}
public resendPhoneVerification(): void {
this.userService.resendMyPhoneVerification().then(() => {
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: type,
},
width: '400px',
});
}
public resendEmailVerification(): void {
this.userService.resendMyEmailVerification().then(() => {
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.refreshChanges$.emit();
}).catch(error => {
this.toast.showError(error);
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
}
public deletePhone(): void {
this.userService.removeMyPhone().then(() => {
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
if (this.user.human?.phone) {
const phone = new Phone();
this.user.human.phone = phone.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: type,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
public savePhone(phone: string): void {
if (this.user.human) {
this.userService
.setMyPhone(phone).then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) {
const phoneToSet = new Phone();
phoneToSet.setPhone(phone);
this.user.human.phone = phoneToSet.toObject();
this.refreshUser();
}
}).catch(error => {
this.toast.showError(error);
});
}
}
public openEditDialog(type: EditDialogType): void {
switch (type) {
case EditDialogType.PHONE:
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.PHONE.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.EDITDESC',
value: this.user.human?.phone?.phone,
type: type,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp) {
this.savePhone(resp);
}
});
break;
case EditDialogType.EMAIL:
const dialogRefEmail = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.SAVE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.LOGINMETHODS.EMAIL.EDITTITLE',
descriptionKey: 'USER.LOGINMETHODS.EMAIL.EDITDESC',
value: this.user.human?.email?.email,
type: type,
},
width: '400px',
});
dialogRefEmail.afterClosed().subscribe(resp => {
if (resp) {
this.saveEmail(resp);
}
});
break;
}
}
}
}
@@ -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,92 +1,131 @@
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',
templateUrl: './detail-form.component.html',
styleUrls: ['./detail-form.component.scss'],
selector: 'app-detail-form',
templateUrl: './detail-form.component.html',
styleUrls: ['./detail-form.component.scss'],
})
export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public username!: string;
@Input() public user!: Human.AsObject;
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
@Input() public showEditImage: boolean = false;
@Input() public preferredLoginName: string = '';
@Input() public username!: string;
@Input() public user!: Human.AsObject;
@Input() public disabled: boolean = false;
@Input() public genders: Gender[] = [];
@Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<User> = new EventEmitter<User>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
public profileForm!: FormGroup;
public profilePic: any = null;
public profileForm!: FormGroup;
private sub: Subscription = new Subscription();
private sub: Subscription = new Subscription();
constructor(private fb: FormBuilder) {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
}
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,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
public ngOnChanges(): void {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
this.loadAvatar();
}
this.profileForm.patchValue({ userName: this.username, ...this.user.profile });
public ngOnChanges(): void {
this.profileForm = this.fb.group({
userName: [{ value: '', disabled: true }, [
Validators.required,
]],
firstName: [{ value: '', disabled: this.disabled }, Validators.required],
lastName: [{ value: '', disabled: this.disabled }, Validators.required],
nickName: [{ value: '', disabled: this.disabled }],
displayName: [{ value: '', disabled: this.disabled }, Validators.required],
gender: [{ value: 0, disabled: this.disabled }],
preferredLanguage: [{ value: '', disabled: this.disabled }],
});
if (this.preferredLanguage) {
this.sub = this.preferredLanguage.valueChanges.subscribe(value => {
this.changedLanguage.emit(value);
});
}
}
this.profileForm.patchValue({ userName: this.username, ...this.user.profile });
public ngOnDestroy(): void {
this.sub.unsubscribe();
if (this.preferredLanguage) {
this.sub = this.preferredLanguage.valueChanges.subscribe(value => {
this.changedLanguage.emit(value);
});
}
}
public submitForm(): void {
this.submitData.emit(this.profileForm.value);
}
public ngOnDestroy(): void {
this.sub.unsubscribe();
}
public get userName(): AbstractControl | null {
return this.profileForm.get('userName');
}
public submitForm(): void {
this.submitData.emit(this.profileForm.value);
}
public get firstName(): AbstractControl | null {
return this.profileForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.profileForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.profileForm.get('nickName');
}
public get displayName(): AbstractControl | null {
return this.profileForm.get('displayName');
}
public get gender(): AbstractControl | null {
return this.profileForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.profileForm.get('preferredLanguage');
}
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');
}
public get firstName(): AbstractControl | null {
return this.profileForm.get('firstName');
}
public get lastName(): AbstractControl | null {
return this.profileForm.get('lastName');
}
public get nickName(): AbstractControl | null {
return this.profileForm.get('nickName');
}
public get displayName(): AbstractControl | null {
return this.profileForm.get('displayName');
}
public get gender(): AbstractControl | null {
return this.profileForm.get('gender');
}
public get preferredLanguage(): AbstractControl | null {
return this.profileForm.get('preferredLanguage');
}
}
@@ -4,28 +4,38 @@ 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,
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
TranslateModule,
MatSelectModule,
MatButtonModule,
MatIconModule,
TranslateModule,
InputModule,
],
exports: [
DetailFormComponent,
],
declarations: [
DetailFormComponent,
ProfilePictureComponent,
],
imports: [
DropzoneModule,
AvatarModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
ImageCropperModule,
TranslateModule,
MatSelectModule,
MatButtonModule,
MatTooltipModule,
MatIconModule,
TranslateModule,
InputModule,
],
exports: [
DetailFormComponent,
],
})
export class DetailFormModule { }
@@ -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;
}
}
@@ -11,195 +11,195 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-memberships',
templateUrl: './memberships.component.html',
styleUrls: ['./memberships.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
selector: 'app-memberships',
templateUrl: './memberships.component.html',
styleUrls: ['./memberships.component.scss'],
animations: [
trigger('cardAnimation', [
transition('* => *', [
query('@animate', stagger('40ms', animateChild()), { optional: true }),
]),
]),
trigger('animate', [
transition(':enter', [
animate('.2s ease-in', keyframes([
style({ opacity: 0, offset: 0 }),
style({ opacity: .5, transform: 'scale(1.05)', offset: 0.3 }),
style({ opacity: 1, transform: 'scale(1)', offset: 1 }),
])),
]),
]),
],
})
export class MembershipsComponent implements OnInit {
public loading: boolean = false;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public totalResult: number = 0;
public loading: boolean = false;
public memberships!: Membership.AsObject[] | UserGrant.AsObject[];
public totalResult: number = 0;
@Input() public auth: boolean = false;
@Input() public user!: User.AsObject;
@Input() public disabled: boolean = false;
@Input() public auth: boolean = false;
@Input() public user!: User.AsObject;
@Input() public disabled: boolean = false;
constructor(
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private adminService: AdminService,
private dialog: MatDialog,
private toast: ToastService,
private router: Router,
) { }
constructor(
private authService: GrpcAuthService,
private mgmtService: ManagementService,
private adminService: AdminService,
private dialog: MatDialog,
private toast: ToastService,
private router: Router,
) { }
ngOnInit(): void {
this.loadManager(this.user.id);
ngOnInit(): void {
this.loadManager(this.user.id);
}
public async loadManager(userId: string): Promise<void> {
if (this.auth) {
this.authService.listMyUserGrants(100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
} else {
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
}
}
public async loadManager(userId: string): Promise<void> {
if (this.auth) {
this.authService.listMyUserGrants(100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
} else {
this.mgmtService.listUserMemberships(userId, 100, 0, []).then(resp => {
this.memberships = resp.resultList;
this.totalResult = resp.details?.totalResult || 0;
this.loading = false;
});
public navigateToObject(): void {
if (!this.disabled) {
this.router.navigate(['/users', this.user.id, 'memberships']);
}
}
public addMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
width: '400px',
data: {
user: this.user,
},
});
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.creationType !== undefined) {
switch (resp.creationType) {
case CreationType.IAM:
this.createIamMember(resp);
break;
case CreationType.ORG:
this.createOrgMember(resp);
break;
case CreationType.PROJECT_OWNED:
this.createOwnedProjectMember(resp);
break;
case CreationType.PROJECT_GRANTED:
this.createGrantedProjectMember(resp);
break;
}
}
}
});
}
public navigateToObject(): void {
if (!this.disabled) {
this.router.navigate(['/users', this.user.id, 'memberships']);
}
}
public createIamMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
public addMember(): void {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
width: '400px',
data: {
user: this.user,
},
});
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
dialogRef.afterClosed().subscribe(resp => {
if (resp && resp.creationType !== undefined) {
switch (resp.creationType) {
case CreationType.IAM:
this.createIamMember(resp);
break;
case CreationType.ORG:
this.createOrgMember(resp);
break;
case CreationType.PROJECT_OWNED:
this.createOwnedProjectMember(resp);
break;
case CreationType.PROJECT_GRANTED:
this.createGrantedProjectMember(resp);
break;
}
}
private createOrgMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
}
private createGrantedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
roles,
).then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
public createIamMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
private createOwnedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.adminService.addIAMMember(user.id, roles);
})).then(() => {
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
private createOrgMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',
'rgb(226, 176, 50)',
'rgb(112, 89, 152)',
];
if (users && users.length && roles && roles.length) {
Promise.all(users.map(user => {
return this.mgmtService.addOrgMember(user.id, roles);
})).then(() => {
this.toast.showInfo('ORG.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
}
let hash = 0;
if (gen.length === 0) {
return colors[hash];
}
private createGrantedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectGrantMember(
response.projectId,
response.grantId,
user.id,
roles,
).then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
private createOwnedProjectMember(response: any): void {
const users: User.AsObject[] = response.users;
const roles: string[] = response.roles;
if (users && users.length && roles && roles.length) {
users.forEach(user => {
return this.mgmtService.addProjectMember(response.projectId, user.id, roles)
.then(() => {
this.toast.showInfo('PROJECT.TOAST.MEMBERADDED', true);
setTimeout(() => {
this.loadManager(this.user.id);
}, 1000);
}).catch(error => {
this.toast.showError(error);
});
});
}
}
getColor(type: Membership.AsObject[] | UserGrant.AsObject[]): string {
const gen = type.toString();
const colors = [
'rgb(201, 115, 88)',
'rgb(226, 176, 50)',
'rgb(112, 89, 152)',
];
let hash = 0;
if (gen.length === 0) {
return colors[hash];
}
for (let i = 0; i < gen.length; i++) {
// tslint:disable-next-line: no-bitwise
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
for (let i = 0; i < gen.length; i++) {
// tslint:disable-next-line: no-bitwise
hash = gen.charCodeAt(i) + ((hash << 5) - hash);
// tslint:disable-next-line: no-bitwise
hash = hash & hash;
}
hash = ((hash % colors.length) + colors.length) % colors.length;
return colors[hash];
}
}
@@ -51,59 +51,59 @@ import { UserDetailComponent } from './user-detail/user-detail.component';
import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
@NgModule({
declarations: [
AuthUserDetailComponent,
UserDetailComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
UserMfaComponent,
PasswordlessComponent,
ThemeSettingComponent,
PasswordComponent,
CodeDialogComponent,
MembershipsComponent,
ExternalIdpsComponent,
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
AuthFactorDialogComponent,
],
imports: [
UserDetailRoutingModule,
ChangesModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
DetailFormModule,
DetailFormMachineModule,
WarnDialogModule,
MatDialogModule,
QRCodeModule,
MetaLayoutModule,
MatCheckboxModule,
HasRolePipeModule,
UserGrantsModule,
MatButtonModule,
MatIconModule,
CardModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatTooltipModule,
HasRoleModule,
TranslateModule,
MatTableModule,
PaginatorModule,
SharedModule,
RefreshTableModule,
CopyToClipboardModule,
DetailLayoutModule,
PasswordComplexityViewModule,
MemberCreateDialogModule,
TimestampToDatePipeModule,
LocalizedDatePipeModule,
InputModule,
MachineKeysModule,
],
declarations: [
AuthUserDetailComponent,
UserDetailComponent,
EditDialogComponent,
AuthUserMfaComponent,
AuthPasswordlessComponent,
UserMfaComponent,
PasswordlessComponent,
ThemeSettingComponent,
PasswordComponent,
CodeDialogComponent,
MembershipsComponent,
ExternalIdpsComponent,
ContactComponent,
ResendEmailDialogComponent,
DialogU2FComponent,
AuthFactorDialogComponent,
],
imports: [
UserDetailRoutingModule,
ChangesModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
DetailFormModule,
DetailFormMachineModule,
WarnDialogModule,
MatDialogModule,
QRCodeModule,
MetaLayoutModule,
MatCheckboxModule,
HasRolePipeModule,
UserGrantsModule,
MatButtonModule,
MatIconModule,
CardModule,
MatProgressSpinnerModule,
MatProgressBarModule,
MatTooltipModule,
HasRoleModule,
TranslateModule,
MatTableModule,
PaginatorModule,
SharedModule,
RefreshTableModule,
CopyToClipboardModule,
DetailLayoutModule,
PasswordComplexityViewModule,
MemberCreateDialogModule,
TimestampToDatePipeModule,
LocalizedDatePipeModule,
InputModule,
MachineKeysModule,
],
})
export class UserDetailModule { }
@@ -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">