feat(console): change my username (#2587)

* feat: username change

* use btn, update on dist

* disable mgmt side

* lint

* rm scope

* chore(deps-dev): bump eslint from 7.32.0 to 8.1.0 in /console (#2569)

Bumps [eslint](https://github.com/eslint/eslint) from 7.32.0 to 8.1.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v7.32.0...v8.1.0)

---
updated-dependencies:
- dependency-name: eslint
  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-dev): bump ts-node from 10.2.1 to 10.4.0 in /console (#2568)

Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 10.2.1 to 10.4.0.
- [Release notes](https://github.com/TypeStrong/ts-node/releases)
- [Commits](https://github.com/TypeStrong/ts-node/compare/v10.2.1...v10.4.0)

---
updated-dependencies:
- dependency-name: ts-node
  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-dev): bump @types/node from 16.10.2 to 16.11.4 in /console (#2567)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.10.2 to 16.11.4.
- [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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

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

* cli core

* material, cdk

* eslint schematics

* legacy peer deps

* grpcweb, types

* dontgrade eslint

* revert package mods

* change username mgmt

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Max Peintner
2021-11-02 09:03:02 +01:00
committed by GitHub
parent bc526561d0
commit 5ba1e45423
13 changed files with 782 additions and 647 deletions

View File

@@ -80,10 +80,7 @@ const authConfig: AuthConfig = {
}; };
@NgModule({ @NgModule({
declarations: [ declarations: [AppComponent, SignedoutComponent],
AppComponent,
SignedoutComponent,
],
imports: [ imports: [
AppRoutingModule, AppRoutingModule,
CommonModule, CommonModule,
@@ -91,7 +88,12 @@ const authConfig: AuthConfig = {
OverlayModule, OverlayModule,
OAuthModule.forRoot({ OAuthModule.forRoot({
resourceServer: { resourceServer: {
allowedUrls: ['https://test.api.zitadel.caos.ch/caos.zitadel.auth.api.v1.AuthService', 'https://test.api.zitadel.caos.ch/oauth/v2/userinfo', 'https://test.api.zitadel.caos.ch/caos.zitadel.management.api.v1.ManagementService/', 'https://preview.api.zitadel.caos.ch'], allowedUrls: [
'https://test.api.zitadel.caos.ch/caos.zitadel.auth.api.v1.AuthService',
'https://test.api.zitadel.caos.ch/oauth/v2/userinfo',
'https://test.api.zitadel.caos.ch/caos.zitadel.management.api.v1.ManagementService/',
'https://preview.api.zitadel.caos.ch',
],
sendAccessToken: true, sendAccessToken: true,
}, },
}), }),
@@ -185,7 +187,6 @@ const authConfig: AuthConfig = {
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { export class AppModule {
constructor() { } constructor() {}
} }

View File

@@ -1,82 +1,85 @@
<cnsl-meta-layout> <cnsl-meta-layout>
<div class="max-width-container"> <div class="max-width-container">
<div class="header-row"> <div class="header-row">
<div class="text"> <div class="text">
<h1 class="h1">{{ 'USER.TITLE' | translate }}</h1> <h1 class="h1">{{ 'USER.TITLE' | translate }}</h1>
<p class="sub">{{'USER.DESCRIPTION' | translate}}</p> <p class="sub">{{'USER.DESCRIPTION' | translate}}</p>
</div> </div>
<div class="theme"> <div class="theme">
<cnsl-theme-setting></cnsl-theme-setting> <cnsl-theme-setting></cnsl-theme-setting>
</div>
</div>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<cnsl-card *ngIf="user && user.human && user.human.profile" class=" app-card" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<cnsl-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(changedLanguage)="changedLanguage($event)" (submitData)="saveProfile($event)">
</cnsl-detail-form>
</cnsl-card>
<cnsl-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button class="icon-button" card-actions mat-icon-button (click)="refreshUser()" matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<cnsl-contact *ngIf="user.human" [human]="user.human" [state]="user.state" [canWrite]="true"
(editType)="openEditDialog($event)" (enteredPhoneCode)="enteredPhoneCode($event)"
(deletedPhone)="deletePhone()" (resendEmailVerification)="resendEmailVerification()"
(resendPhoneVerification)="resendPhoneVerification()">
</cnsl-contact>
</cnsl-card>
<cnsl-external-idps *ngIf="user && user.id" [userId]="user.id" [service]="userService"></cnsl-external-idps>
<cnsl-auth-passwordless *ngIf="user" #mfaComponent></cnsl-auth-passwordless>
<cnsl-auth-user-mfa *ngIf="user" #mfaComponent></cnsl-auth-user-mfa>
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
</cnsl-user-grants>
</cnsl-card>
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user?.id" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
<div *ngIf="user" class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="user?.id" class="second">{{ user.id }}</span>
</div>
<div class="meta-row" *ngIf="user?.preferredLoginName">
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
</div>
</div> </div>
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
<mat-tab label="Details">
<div class="side-padding">
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
<cnsl-memberships [auth]="true" [user]="user"></cnsl-memberships>
</ng-template>
</div>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
</cnsl-changes>
</mat-tab>
</mat-tab-group>
</div> </div>
<mat-progress-bar *ngIf="loading" color="primary" mode="indeterminate"></mat-progress-bar>
<span *ngIf="!loading && !user">{{ 'USER.PAGES.NOUSER' | translate }}</span>
<cnsl-info-row *ngIf="user" [user]="user"></cnsl-info-row>
<cnsl-card *ngIf="user && user.human && user.human.profile" class=" app-card"
title="{{ 'USER.PROFILE.TITLE' | translate }}">
<cnsl-detail-form [showEditImage]="true" [preferredLoginName]="user.preferredLoginName" [genders]="genders"
[languages]="languages" [username]="user.userName" [user]="user.human"
(changedLanguage)="changedLanguage($event)" (changeUsernameClicked)="changeUsername()"
(submitData)="saveProfile($event)">
</cnsl-detail-form>
</cnsl-card>
<cnsl-card *ngIf="user" title="{{ 'USER.LOGINMETHODS.TITLE' | translate }}"
description="{{ 'USER.LOGINMETHODS.DESCRIPTION' | translate }}">
<button class="icon-button" card-actions mat-icon-button (click)="refreshUser()"
matTooltip="{{'ACTIONS.REFRESH' | translate}}">
<mat-icon class="icon">refresh</mat-icon>
</button>
<cnsl-contact *ngIf="user.human" [human]="user.human" [state]="user.state" [canWrite]="true"
(editType)="openEditDialog($event)" (enteredPhoneCode)="enteredPhoneCode($event)" (deletedPhone)="deletePhone()"
(resendEmailVerification)="resendEmailVerification()" (resendPhoneVerification)="resendPhoneVerification()">
</cnsl-contact>
</cnsl-card>
<cnsl-external-idps *ngIf="user && user.id" [userId]="user.id" [service]="userService"></cnsl-external-idps>
<cnsl-auth-passwordless *ngIf="user" #mfaComponent></cnsl-auth-passwordless>
<cnsl-auth-user-mfa *ngIf="user" #mfaComponent></cnsl-auth-user-mfa>
<cnsl-card *ngIf="user?.id" title="{{ 'GRANTS.USER.TITLE' | translate }}"
description="{{'GRANTS.USER.DESCRIPTION' | translate }}">
<cnsl-user-grants [userId]="user.id" [context]="USERGRANTCONTEXT"
[displayedColumns]="['select', 'projectId', 'dates', 'roleNamesList']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false">
</cnsl-user-grants>
</cnsl-card>
<ng-template cnslHasFeature [hasFeature]="['metadata.user']">
<cnsl-metadata *ngIf="user?.id" [userId]="user.id"></cnsl-metadata>
</ng-template>
</div>
<div *ngIf="user" class="side" metainfo>
<div class="meta-details">
<div class="meta-row">
<span class="first">{{'RESOURCEID' | translate}}:</span>
<span *ngIf="user?.id" class="second">{{ user.id }}</span>
</div>
<div class="meta-row" *ngIf="user?.preferredLoginName">
<span class="first">{{'USER.PREFERRED_LOGINNAME' | translate}}</span>
<span class="second"><span style="display: block;">{{user.preferredLoginName}}</span></span>
</div>
</div>
<mat-tab-group mat-stretch-tabs class="tab-group" [disablePagination]="true">
<mat-tab label="Details">
<div class="side-padding">
<ng-template cnslHasRole [hasRole]="['user.membership.read']">
<cnsl-memberships [auth]="true" [user]="user"></cnsl-memberships>
</ng-template>
</div>
</mat-tab>
<mat-tab label="{{ 'CHANGES.PROJECT.TITLE' | translate }}" class="meta-flex-col">
<cnsl-changes class="changes" [refresh]="refreshChanges$" [changeType]="ChangeType.MYUSER" [id]="user.id">
</cnsl-changes>
</mat-tab>
</mat-tab-group>
</div>
</cnsl-meta-layout> </cnsl-meta-layout>

View File

@@ -58,6 +58,32 @@ export class AuthUserDetailComponent implements OnDestroy {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
public changeUsername(): void {
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.CHANGE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.PROFILE.CHANGEUSERNAME_TITLE',
descriptionKey: 'USER.PROFILE.CHANGEUSERNAME_DESC',
value: this.user.userName,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe(resp => {
if (resp && resp !== this.user.userName) {
this.userService.updateMyUserName(resp).then(() => {
this.toast.showInfo('USER.TOAST.USERNAMECHANGED', true);
this.refreshUser();
})
.catch(error => {
this.toast.showError(error);
});
}
});
}
public saveProfile(profileData: Profile.AsObject): void { public saveProfile(profileData: Profile.AsObject): void {
if (this.user.human) { if (this.user.human) {
this.user.human.profile = profileData; this.user.human.profile = profileData;

View File

@@ -1,56 +1,61 @@
<form [formGroup]="profileForm" *ngIf="profileForm" (ngSubmit)="submitForm()"> <form [formGroup]="profileForm" *ngIf="profileForm" (ngSubmit)="submitForm()">
<div class="user-form-content"> <div class="user-form-content">
<div class="user-form-content inner"> <div class="user-form-content inner">
<button class="camera-wrapper" type="button" (click)="showEditImage ? openUploadDialog() : null"> <button class="camera-wrapper" type="button" (click)="showEditImage ? openUploadDialog() : null">
<div class="i-wrapper" *ngIf="showEditImage"> <div class="i-wrapper" *ngIf="showEditImage">
<i class="las la-camera"></i> <i class="las la-camera"></i>
</div> </div>
<cnsl-avatar <cnsl-avatar *ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName"
*ngIf="user && user.profile?.displayName && user.profile?.firstName && user.profile?.lastName" class="avatar" [name]="user.profile?.displayName ?? ''" [avatarUrl]="user?.profile?.avatarUrl || ''"
class="avatar" [name]="user.profile?.displayName ?? ''" [avatarUrl]="user?.profile?.avatarUrl || ''" [forColor]="preferredLoginName" [size]="80"> [forColor]="preferredLoginName" [size]="80">
</cnsl-avatar> </cnsl-avatar>
</button> </button>
<div className="usernamediv">
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label> <cnsl-label>{{ 'USER.PROFILE.USERNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="userName" /> <input cnslInput formControlName="userName" />
</cnsl-form-field> </cnsl-form-field>
<button type="button" color="primary" mat-stroked-button class="edit"
(click)="changeUsername()">{{'USER.PROFILE.CHANGEUSERNAME' |
translate}}</button>
</div> </div>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="firstName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="lastName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="displayName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
<mat-select formControlName="gender">
<mat-option *ngFor="let gender of genders" [value]="gender">
{{ 'GENDERS.'+gender | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'LANGUAGES.'+language | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div class="btn-container">
<button [disabled]="disabled" class="submit-button" type="submit" color="primary" mat-raised-button>{{
'ACTIONS.SAVE' | translate }}</button>
</div> </div>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.FIRSTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="firstName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.LASTNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="lastName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.NICKNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="nickName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.DISPLAYNAME' | translate }}</cnsl-label>
<input cnslInput formControlName="displayName" />
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.GENDER' | translate }}</cnsl-label>
<mat-select formControlName="gender">
<mat-option *ngFor="let gender of genders" [value]="gender">
{{ 'GENDERS.'+gender | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="formfield">
<cnsl-label>{{ 'USER.PROFILE.PREFERRED_LANGUAGE' | translate }}</cnsl-label>
<mat-select formControlName="preferredLanguage">
<mat-option *ngFor="let language of languages" [value]="language">
{{ 'LANGUAGES.'+language | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div class="btn-container">
<button [disabled]="disabled" class="submit-button" type="submit" color="primary" mat-raised-button>{{
'ACTIONS.SAVE' | translate }}</button>
</div>
</form> </form>

View File

@@ -7,6 +7,22 @@
&.inner { &.inner {
margin: 0; margin: 0;
width: 100%; width: 100%;
display: flex;
align-items: center;
.usernamediv {
margin-left: .5rem;
margin-bottom: .5rem;
.formfield {
margin: 0;
flex: 1;
}
.edit {
cursor: pointer !important;
}
}
} }
.camera-wrapper { .camera-wrapper {

View File

@@ -21,7 +21,8 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
@Input() public languages: string[] = ['de', 'en']; @Input() public languages: string[] = ['de', 'en'];
@Output() public submitData: EventEmitter<Profile.AsObject> = new EventEmitter<Profile.AsObject>(); @Output() public submitData: EventEmitter<Profile.AsObject> = new EventEmitter<Profile.AsObject>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>(); @Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
@Output() public changeUsernameClicked: EventEmitter<void> = new EventEmitter();
public profileForm!: FormGroup; public profileForm!: FormGroup;
private sub: Subscription = new Subscription(); private sub: Subscription = new Subscription();
@@ -73,6 +74,10 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
this.submitData.emit(this.profileForm.value); this.submitData.emit(this.profileForm.value);
} }
public changeUsername(): void {
this.changeUsernameClicked.emit();
}
public openUploadDialog(): void { public openUploadDialog(): void {
const dialogRef = this.dialog.open(ProfilePictureComponent, { const dialogRef = this.dialog.open(ProfilePictureComponent, {
data: { data: {

View File

@@ -48,7 +48,7 @@
<cnsl-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}"> <cnsl-card *ngIf="user.human" title="{{ 'USER.PROFILE.TITLE' | translate }}">
<cnsl-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) === false" <cnsl-detail-form [preferredLoginName]="user.preferredLoginName" [disabled]="(canWrite$ | async) === false"
[genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human" [genders]="genders" [languages]="languages" [username]="user.userName" [user]="user.human"
(submitData)="saveProfile($event)"> (submitData)="saveProfile($event)" (changeUsernameClicked)="changeUsername()">
</cnsl-detail-form> </cnsl-detail-form>
</cnsl-card> </cnsl-card>

View File

@@ -49,28 +49,34 @@ export class UserDetailComponent implements OnInit {
private _location: Location, private _location: Location,
private dialog: MatDialog, private dialog: MatDialog,
private router: Router, private router: Router,
) { } ) {}
refreshUser(): void { refreshUser(): void {
this.refreshChanges$.emit(); this.refreshChanges$.emit();
this.route.params.pipe(take(1)).subscribe(params => { this.route.params.pipe(take(1)).subscribe((params) => {
const { id } = params; const { id } = params;
this.mgmtUserService.getUserByID(id).then(resp => { this.mgmtUserService
if (resp.user) { .getUserByID(id)
this.user = resp.user; .then((resp) => {
} if (resp.user) {
}).catch(err => { this.user = resp.user;
this.error = err.message ?? ''; }
this.toast.showError(err); })
}); .catch((err) => {
this.error = err.message ?? '';
this.toast.showError(err);
});
this.mgmtUserService.listUserMetadata(id, 0, 100, []).then(resp => { this.mgmtUserService
if (resp.resultList) { .listUserMetadata(id, 0, 100, [])
this.metadata = resp.resultList; .then((resp) => {
} if (resp.resultList) {
}).catch(err => { this.metadata = resp.resultList;
console.error(err); }
}); })
.catch((err) => {
console.error(err);
});
}); });
} }
@@ -78,32 +84,69 @@ export class UserDetailComponent implements OnInit {
this.refreshUser(); this.refreshUser();
} }
public changeUsername(): void {
const dialogRefPhone = this.dialog.open(EditDialogComponent, {
data: {
confirmKey: 'ACTIONS.CHANGE',
cancelKey: 'ACTIONS.CANCEL',
labelKey: 'ACTIONS.NEWVALUE',
titleKey: 'USER.PROFILE.CHANGEUSERNAME_TITLE',
descriptionKey: 'USER.PROFILE.CHANGEUSERNAME_DESC',
value: this.user.userName,
},
width: '400px',
});
dialogRefPhone.afterClosed().subscribe((resp) => {
if (resp && resp !== this.user.userName) {
this.mgmtUserService
.updateUserName(this.user.id, resp)
.then(() => {
this.toast.showInfo('USER.TOAST.USERNAMECHANGED', true);
this.refreshUser();
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
public unlockUser(): void { public unlockUser(): void {
const req = new UnlockUserRequest(); const req = new UnlockUserRequest();
req.setId(this.user.id); req.setId(this.user.id);
this.mgmtUserService.unlockUser(req).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.UNLOCKED', true); .unlockUser(req)
this.refreshUser(); .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.UNLOCKED', true);
this.toast.showError(error); this.refreshUser();
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
public changeState(newState: UserState): void { public changeState(newState: UserState): void {
if (newState === UserState.USER_STATE_ACTIVE) { if (newState === UserState.USER_STATE_ACTIVE) {
this.mgmtUserService.reactivateUser(this.user.id).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.REACTIVATED', true); .reactivateUser(this.user.id)
this.user.state = newState; .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.REACTIVATED', true);
this.toast.showError(error); this.user.state = newState;
}); })
.catch((error) => {
this.toast.showError(error);
});
} else if (newState === UserState.USER_STATE_INACTIVE) { } else if (newState === UserState.USER_STATE_INACTIVE) {
this.mgmtUserService.deactivateUser(this.user.id).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.DEACTIVATED', true); .deactivateUser(this.user.id)
this.user.state = newState; .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.DEACTIVATED', true);
this.toast.showError(error); this.user.state = newState;
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
} }
@@ -118,12 +161,13 @@ export class UserDetailComponent implements OnInit {
this.user.human.profile.nickName, this.user.human.profile.nickName,
this.user.human.profile.displayName, this.user.human.profile.displayName,
this.user.human.profile.preferredLanguage, this.user.human.profile.preferredLanguage,
this.user.human.profile.gender) this.user.human.profile.gender,
)
.then(() => { .then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true); this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit(); this.refreshChanges$.emit();
}) })
.catch(error => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
@@ -135,82 +179,96 @@ export class UserDetailComponent implements OnInit {
this.user.machine.description = machineData.description; this.user.machine.description = machineData.description;
this.mgmtUserService this.mgmtUserService
.updateMachine( .updateMachine(this.user.id, this.user.machine.name, this.user.machine.description)
this.user.id,
this.user.machine.name,
this.user.machine.description)
.then(() => { .then(() => {
this.toast.showInfo('USER.TOAST.SAVED', true); this.toast.showInfo('USER.TOAST.SAVED', true);
this.refreshChanges$.emit(); this.refreshChanges$.emit();
}) })
.catch(error => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
} }
public resendEmailVerification(): void { public resendEmailVerification(): void {
this.mgmtUserService.resendHumanEmailVerification(this.user.id).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true); .resendHumanEmailVerification(this.user.id)
this.refreshChanges$.emit(); .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.EMAILVERIFICATIONSENT', true);
this.toast.showError(error); this.refreshChanges$.emit();
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
public resendPhoneVerification(): void { public resendPhoneVerification(): void {
this.mgmtUserService.resendHumanPhoneVerification(this.user.id).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true); .resendHumanPhoneVerification(this.user.id)
this.refreshChanges$.emit(); .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.PHONEVERIFICATIONSENT', true);
this.toast.showError(error); this.refreshChanges$.emit();
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
public deletePhone(): void { public deletePhone(): void {
this.mgmtUserService.removeHumanPhone(this.user.id).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.PHONEREMOVED', true); .removeHumanPhone(this.user.id)
if (this.user.human) { .then(() => {
this.user.human.phone = new Phone().setPhone('').toObject(); this.toast.showInfo('USER.TOAST.PHONEREMOVED', true);
this.refreshUser(); if (this.user.human) {
} this.user.human.phone = new Phone().setPhone('').toObject();
}).catch(error => { this.refreshUser();
this.toast.showError(error); }
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
public saveEmail(email: string): void { public saveEmail(email: string): void {
if (this.user.id && email) { if (this.user.id && email) {
this.mgmtUserService.updateHumanEmail(this.user.id, email).then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.EMAILSAVED', true); .updateHumanEmail(this.user.id, email)
if (this.user.state === UserState.USER_STATE_INITIAL) { .then(() => {
this.mgmtUserService.resendHumanInitialization(this.user.id, email ?? '').then(() => { this.toast.showInfo('USER.TOAST.EMAILSAVED', true);
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true); if (this.user.state === UserState.USER_STATE_INITIAL) {
this.refreshChanges$.emit(); this.mgmtUserService
}).catch(error => { .resendHumanInitialization(this.user.id, email ?? '')
this.toast.showError(error); .then(() => {
}); this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
} this.refreshChanges$.emit();
if (this.user.human) { })
this.user.human.email = new Email().setEmail(email).toObject(); .catch((error) => {
this.refreshUser(); this.toast.showError(error);
} });
}).catch(error => { }
this.toast.showError(error); if (this.user.human) {
}); this.user.human.email = new Email().setEmail(email).toObject();
this.refreshUser();
}
})
.catch((error) => {
this.toast.showError(error);
});
} }
} }
public savePhone(phone: string): void { public savePhone(phone: string): void {
if (this.user.id && phone) { if (this.user.id && phone) {
this.mgmtUserService this.mgmtUserService
.updateHumanPhone(this.user.id, phone).then(() => { .updateHumanPhone(this.user.id, phone)
.then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true); this.toast.showInfo('USER.TOAST.PHONESAVED', true);
if (this.user.human) { if (this.user.human) {
this.user.human.phone = new Phone().setPhone(phone).toObject(); this.user.human.phone = new Phone().setPhone(phone).toObject();
this.refreshUser(); this.refreshUser();
} }
}).catch(error => { })
.catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
@@ -221,15 +279,15 @@ export class UserDetailComponent implements OnInit {
} }
public sendSetPasswordNotification(): void { public sendSetPasswordNotification(): void {
this.mgmtUserService.sendHumanResetPasswordNotification( this.mgmtUserService
this.user.id, .sendHumanResetPasswordNotification(this.user.id, SendHumanResetPasswordNotificationRequest.Type.TYPE_EMAIL)
SendHumanResetPasswordNotificationRequest.Type.TYPE_EMAIL, .then(() => {
).then(() => { this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true);
this.toast.showInfo('USER.TOAST.PASSWORDNOTIFICATIONSENT', true); this.refreshChanges$.emit();
this.refreshChanges$.emit(); })
}).catch(error => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
public deleteUser(): void { public deleteUser(): void {
@@ -243,17 +301,20 @@ export class UserDetailComponent implements OnInit {
width: '400px', width: '400px',
}); });
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
this.mgmtUserService.removeUser(this.user.id).then(() => { this.mgmtUserService
const params: Params = { .removeUser(this.user.id)
'deferredReload': true, .then(() => {
}; const params: Params = {
this.router.navigate(['/users/list', this.user.human ? 'humans' : 'machines'], { queryParams: params }); deferredReload: true,
this.toast.showInfo('USER.TOAST.DELETED', true); };
}).catch(error => { this.router.navigate(['/users/list', this.user.human ? 'humans' : 'machines'], { queryParams: params });
this.toast.showError(error); this.toast.showInfo('USER.TOAST.DELETED', true);
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
}); });
} }
@@ -263,14 +324,17 @@ export class UserDetailComponent implements OnInit {
width: '400px', width: '400px',
}); });
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe((resp) => {
if (resp.send && this.user.id) { if (resp.send && this.user.id) {
this.mgmtUserService.resendHumanInitialization(this.user.id, resp.email ?? '').then(() => { this.mgmtUserService
this.toast.showInfo('USER.TOAST.INITEMAILSENT', true); .resendHumanInitialization(this.user.id, resp.email ?? '')
this.refreshChanges$.emit(); .then(() => {
}).catch(error => { this.toast.showInfo('USER.TOAST.INITEMAILSENT', true);
this.toast.showError(error); this.refreshChanges$.emit();
}); })
.catch((error) => {
this.toast.showError(error);
});
} }
}); });
} }
@@ -291,7 +355,7 @@ export class UserDetailComponent implements OnInit {
width: '400px', width: '400px',
}); });
dialogRefPhone.afterClosed().subscribe(resp => { dialogRefPhone.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
this.savePhone(resp); this.savePhone(resp);
} }
@@ -311,7 +375,7 @@ export class UserDetailComponent implements OnInit {
width: '400px', width: '400px',
}); });
dialogRefEmail.afterClosed().subscribe(resp => { dialogRefEmail.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
this.saveEmail(resp); this.saveEmail(resp);
} }

View File

@@ -68,6 +68,8 @@ import {
UpdateMyPasswordResponse, UpdateMyPasswordResponse,
UpdateMyProfileRequest, UpdateMyProfileRequest,
UpdateMyProfileResponse, UpdateMyProfileResponse,
UpdateMyUserNameRequest,
UpdateMyUserNameResponse,
VerifyMyAuthFactorOTPRequest, VerifyMyAuthFactorOTPRequest,
VerifyMyAuthFactorOTPResponse, VerifyMyAuthFactorOTPResponse,
VerifyMyAuthFactorU2FRequest, VerifyMyAuthFactorU2FRequest,
@@ -407,6 +409,12 @@ export class GrpcAuthService {
).then(resp => resp.toObject()); ).then(resp => resp.toObject());
} }
public updateMyUserName(username: string): Promise<UpdateMyUserNameResponse.AsObject> {
const req = new UpdateMyUserNameRequest();
req.setUserName(username);
return this.grpcService.auth.updateMyUserName(req, null).then(resp => resp.toObject());
}
public listMyZitadelPermissions(): Promise<ListMyZitadelPermissionsResponse.AsObject> { public listMyZitadelPermissions(): Promise<ListMyZitadelPermissionsResponse.AsObject> {
return this.grpcService.auth.listMyZitadelPermissions( return this.grpcService.auth.listMyZitadelPermissions(
new ListMyZitadelPermissionsRequest(), null, new ListMyZitadelPermissionsRequest(), null,

View File

@@ -27,11 +27,13 @@ export class GrpcService {
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private storageService: StorageService, private storageService: StorageService,
private dialog: MatDialog, private dialog: MatDialog,
) { } ) {}
public async loadAppEnvironment(): Promise<any> { public async loadAppEnvironment(): Promise<any> {
return this.http.get('./assets/environment.json') return this.http
.toPromise().then((data: any) => { .get('./assets/environment.json')
.toPromise()
.then((data: any) => {
if (data && data.authServiceUrl && data.mgmtServiceUrl && data.issuer) { if (data && data.authServiceUrl && data.mgmtServiceUrl && data.issuer) {
const interceptors = { const interceptors = {
unaryInterceptors: [ unaryInterceptors: [
@@ -74,7 +76,8 @@ export class GrpcService {
this.authenticationService.initConfig(authConfig); this.authenticationService.initConfig(authConfig);
} }
return Promise.resolve(data); return Promise.resolve(data);
}).catch(() => { })
.catch(() => {
console.error('Failed to load environment from assets'); console.error('Failed to load environment from assets');
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@@ -339,6 +339,9 @@
"EMAIL": "E-Mail", "EMAIL": "E-Mail",
"PHONE": "Telefonnummer", "PHONE": "Telefonnummer",
"USERNAME": "Benutzername", "USERNAME": "Benutzername",
"CHANGEUSERNAME":"bearbeiten",
"CHANGEUSERNAME_TITLE":"Benutzername ändern",
"CHANGEUSERNAME_DESC":"Geben Sie ihren neuen Namen an.",
"FIRSTNAME": "Vorname", "FIRSTNAME": "Vorname",
"LASTNAME": "Nachname", "LASTNAME": "Nachname",
"NICKNAME": "Spitzname", "NICKNAME": "Spitzname",
@@ -473,6 +476,7 @@
"TOAST": { "TOAST": {
"CREATED": "Benutzer erfolgreich erstellt.", "CREATED": "Benutzer erfolgreich erstellt.",
"SAVED": "Profil gespeichert.", "SAVED": "Profil gespeichert.",
"USERNAMECHANGED":"Username geändert.",
"EMAILSAVED": "E-Mail gespeichert.", "EMAILSAVED": "E-Mail gespeichert.",
"INITEMAILSENT": "Initialisierung Email gesendet.", "INITEMAILSENT": "Initialisierung Email gesendet.",
"PHONESAVED": "Telefonnummer gespeichert.", "PHONESAVED": "Telefonnummer gespeichert.",

View File

@@ -339,6 +339,9 @@
"EMAIL": "E-mail", "EMAIL": "E-mail",
"PHONE": "Phonenumber", "PHONE": "Phonenumber",
"USERNAME": "User Name", "USERNAME": "User Name",
"CHANGEUSERNAME":"modify",
"CHANGEUSERNAME_TITLE":"Change username",
"CHANGEUSERNAME_DESC":"Enter the new name in the field below.",
"FIRSTNAME": "First Name", "FIRSTNAME": "First Name",
"LASTNAME": "Last Name", "LASTNAME": "Last Name",
"NICKNAME": "Nickname", "NICKNAME": "Nickname",
@@ -473,6 +476,7 @@
"TOAST": { "TOAST": {
"CREATED": "User created successfully.", "CREATED": "User created successfully.",
"SAVED": "Profile saved successfully.", "SAVED": "Profile saved successfully.",
"USERNAMECHANGED":"Username changed.",
"EMAILSAVED": "E-mail saved successfully.", "EMAILSAVED": "E-mail saved successfully.",
"INITEMAILSENT": "Initializing mail sent.", "INITEMAILSENT": "Initializing mail sent.",
"PHONESAVED": "Phone saved successfully.", "PHONESAVED": "Phone saved successfully.",