Improve user setting component (#16665)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Shijun Sun 2022-04-07 15:45:00 +08:00 committed by GitHub
parent bb6693b496
commit 185d38cf49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 290 additions and 272 deletions

View File

@ -93,7 +93,7 @@
<span class="spinner spinner-inline loading-top" [hidden]="showProgress === false"></span> <span class="spinner spinner-inline loading-top" [hidden]="showProgress === false"></span>
<button type="button" id="cancel-btn" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button> <button type="button" id="cancel-btn" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate}}</button>
<button type="button" id="submit-btn" class="btn btn-primary" [disabled]="!isValid || showProgress" <button type="button" id="submit-btn" class="btn btn-primary" [disabled]="!isValid || showProgress || !isUserDataChange()"
(click)="submit()">{{'BUTTON.OK' | translate}}</button> (click)="submit()">{{'BUTTON.OK' | translate}}</button>
</div> </div>
</clr-modal> </clr-modal>

View File

@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { ChangeDetectorRef } from '@angular/core'; import { AfterViewChecked, Component, OnInit, ViewChild } from "@angular/core";
import { Component, OnInit, ViewChild, AfterViewChecked } from "@angular/core";
import { NgForm } from "@angular/forms"; import { NgForm } from "@angular/forms";
import { Router, NavigationExtras } from "@angular/router"; import { NavigationExtras, Router } from "@angular/router";
import { SessionUser } from "../../shared/entities/session-user"; import { SessionUser } from "../../shared/entities/session-user";
import { SessionService } from "../../shared/services/session.service"; import { SessionService } from "../../shared/services/session.service";
import { MessageHandlerService } from "../../shared/services/message-handler.service"; import { MessageHandlerService } from "../../shared/services/message-handler.service";
@ -30,229 +29,235 @@ import { InlineAlertComponent } from "../../shared/components/inline-alert/inlin
import { ConfirmationMessage } from "../global-confirmation-dialog/confirmation-message"; import { ConfirmationMessage } from "../global-confirmation-dialog/confirmation-message";
@Component({ @Component({
selector: "account-settings-modal", selector: "account-settings-modal",
templateUrl: "account-settings-modal.component.html", templateUrl: "account-settings-modal.component.html",
styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"] styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"]
}) })
export class AccountSettingsModalComponent implements OnInit, AfterViewChecked { export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
opened = false; opened = false;
staticBackdrop = true; staticBackdrop = true;
originalStaticData: SessionUser; originalStaticData: SessionUser;
account: SessionUser; account: SessionUser;
error: any = null; error: any = null;
emailTooltip = "TOOLTIP.EMAIL"; emailTooltip = "TOOLTIP.EMAIL";
mailAlreadyChecked = {}; mailAlreadyChecked = {};
isOnCalling = false; isOnCalling = false;
formValueChanged = false; formValueChanged = false;
checkOnGoing = false; checkOnGoing = false;
RenameOnGoing = false; RenameOnGoing = false;
originAdminName = "admin"; originAdminName = "admin";
newAdminName = "admin@harbor.local"; newAdminName = "admin@harbor.local";
renameConfirmation = false; renameConfirmation = false;
showSecretDetail = false; showSecretDetail = false;
resetForms = new ResetSecret(); resetForms = new ResetSecret();
showGenerateCli: boolean = false; showGenerateCli: boolean = false;
@ViewChild("confirmationDialog") @ViewChild("confirmationDialog")
confirmationDialogComponent: ConfirmationDialogComponent; confirmationDialogComponent: ConfirmationDialogComponent;
accountFormRef: NgForm; accountFormRef: NgForm;
@ViewChild("accountSettingsFrom", {static: true}) accountForm: NgForm; @ViewChild("accountSettingsFrom", {static: true}) accountForm: NgForm;
@ViewChild("resetSecretFrom", {static: true}) resetSecretFrom: NgForm; @ViewChild("resetSecretFrom", {static: true}) resetSecretFrom: NgForm;
@ViewChild("accountSettingInlineAlert") inlineAlert: InlineAlertComponent; @ViewChild("accountSettingInlineAlert") inlineAlert: InlineAlertComponent;
@ViewChild("resetSecretInlineAlert") resetSecretInlineAlert: InlineAlertComponent; @ViewChild("resetSecretInlineAlert") resetSecretInlineAlert: InlineAlertComponent;
@ViewChild("copyInput") copyInput: CopyInputComponent; @ViewChild("copyInput") copyInput: CopyInputComponent;
showInputSecret: boolean = false; showInputSecret: boolean = false;
showConfirmSecret: boolean = false; showConfirmSecret: boolean = false;
constructor(
private session: SessionService,
private msgHandler: MessageHandlerService,
private router: Router,
private searchTrigger: SearchTriggerService,
private accountSettingsService: AccountSettingsModalService,
private ref: ChangeDetectorRef
) {}
private validationStateMap: any = { constructor(
account_settings_email: true, private session: SessionService,
account_settings_full_name: true private msgHandler: MessageHandlerService,
}; private router: Router,
ngOnInit(): void { private searchTrigger: SearchTriggerService,
// Value copy private accountSettingsService: AccountSettingsModalService,
this.account = Object.assign({}, this.session.getCurrentUser()); ) {
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
}
ngAfterViewChecked(): void {
if (this.accountFormRef !== this.accountForm) {
this.accountFormRef = this.accountForm;
if (this.accountFormRef) {
this.accountFormRef.valueChanges.subscribe(data => {
if (this.error) {
this.error = null;
}
this.formValueChanged = true;
if (this.account.username === this.originAdminName) {
this.inlineAlert.close();
}
});
}
} }
}
getValidationState(key: string): boolean { private validationStateMap: any = {
return this.validationStateMap[key]; account_settings_email: true,
} account_settings_full_name: true
};
handleValidation(key: string, flag: boolean): void { ngOnInit(): void {
if (flag) { this.refreshAccount();
// Checking }
let cont = this.accountForm.controls[key];
if (cont) { refreshAccount() {
this.validationStateMap[key] = cont.valid; // Value copy
// Check email existing from backend this.account = Object.assign({}, this.session.getCurrentUser());
if (cont.valid && key === "account_settings_email") { this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
if ( }
this.formValueChanged &&
this.account.email !== this.originalStaticData.email ngAfterViewChecked(): void {
) { if (this.accountFormRef !== this.accountForm) {
if (this.mailAlreadyChecked[this.account.email]) { this.accountFormRef = this.accountForm;
this.validationStateMap[key] = !this.mailAlreadyChecked[ if (this.accountFormRef) {
this.account.email this.accountFormRef.valueChanges.subscribe(data => {
].result; if (this.error) {
if (!this.validationStateMap[key]) { this.error = null;
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING"; }
} this.formValueChanged = true;
return; if (this.account.username === this.originAdminName) {
this.inlineAlert.close();
}
});
} }
}
}
// Mail changed getValidationState(key: string): boolean {
this.checkOnGoing = true; return this.validationStateMap[key];
this.session }
.checkUserExisting("email", this.account.email)
.subscribe((res: boolean) => { handleValidation(key: string, flag: boolean): void {
this.checkOnGoing = false; if (flag) {
this.validationStateMap[key] = !res; // Checking
if (res) { let cont = this.accountForm.controls[key];
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING"; if (cont) {
this.validationStateMap[key] = cont.valid;
// Check email existing from backend
if (cont.valid && key === "account_settings_email") {
if (
this.formValueChanged &&
this.account.email !== this.originalStaticData.email
) {
if (this.mailAlreadyChecked[this.account.email]) {
this.validationStateMap[key] = !this.mailAlreadyChecked[
this.account.email
].result;
if (!this.validationStateMap[key]) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
return;
}
// Mail changed
this.checkOnGoing = true;
this.session
.checkUserExisting("email", this.account.email)
.subscribe((res: boolean) => {
this.checkOnGoing = false;
this.validationStateMap[key] = !res;
if (res) {
this.emailTooltip = "TOOLTIP.EMAIL_EXISTING";
}
this.mailAlreadyChecked[this.account.email] = {
result: res
}; // Tag it checked
}, error => {
this.checkOnGoing = false;
this.validationStateMap[key] = false; // Not valid @ backend
});
}
} }
this.mailAlreadyChecked[this.account.email] = { }
result: res
}; // Tag it checked
}, error => {
this.checkOnGoing = false;
this.validationStateMap[key] = false; // Not valid @ backend
});
}
}
}
} else {
// Reset
this.validationStateMap[key] = true;
this.emailTooltip = "TOOLTIP.EMAIL";
}
}
isUserDataChange(): boolean {
if (!this.originalStaticData || !this.account) {
return false;
}
for (let prop in this.originalStaticData) {
if (this.originalStaticData[prop] !== this.account[prop]) {
return true;
}
}
return false;
}
public get isValid(): boolean {
return (
this.accountForm &&
this.accountForm.valid &&
this.error === null &&
this.validationStateMap["account_settings_email"]
); // backend check is valid as well
}
public get showProgress(): boolean {
return this.isOnCalling;
}
public get checkProgress(): boolean {
return this.checkOnGoing;
}
public get canRename(): boolean {
return (
this.account &&
this.account.has_admin_role &&
this.originalStaticData.username === "admin" &&
this.account.user_id === 1
);
}
onRename(): void {
this.account.username = this.newAdminName;
this.RenameOnGoing = true;
}
confirmRename(): void {
if (this.canRename) {
this.session
.updateAccountSettings(this.account)
.subscribe(() => {
this.session.renameAdmin(this.account)
.subscribe(() => {
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
this.opened = false;
this.logOut();
}, error => {
this.msgHandler.handleError(error);
});
}, error => {
this.isOnCalling = false;
this.error = error;
if (this.msgHandler.isAppLevel(error)) {
this.opened = false;
this.msgHandler.handleError(error);
} else { } else {
this.inlineAlert.showInlineError(error); // Reset
this.validationStateMap[key] = true;
this.emailTooltip = "TOOLTIP.EMAIL";
} }
});
} }
}
// Log out system isUserDataChange(): boolean {
logOut(): void { if (!this.originalStaticData || !this.account) {
// Naviagte to the sign in router-guard return false;
// Appending 'signout' means destroy session cache }
let navigatorExtra: NavigationExtras = { for (let prop in this.originalStaticData) {
queryParams: { signout: true } if (this.originalStaticData[prop] !== this.account[prop]) {
}; return true;
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra); }
// Confirm search result panel is close }
this.searchTrigger.closeSearch(true); return false;
} }
open() { public get isValid(): boolean {
// Keep the initial data for future diff return (
this.originalStaticData = Object.assign({}, this.session.getCurrentUser()); this.accountForm &&
this.account = Object.assign({}, this.session.getCurrentUser()); this.accountForm.valid &&
this.formValueChanged = false; this.error === null &&
this.validationStateMap["account_settings_email"]
); // backend check is valid as well
}
// Confirm inline alert is closed public get showProgress(): boolean {
this.inlineAlert.close(); return this.isOnCalling;
}
// Clear check history public get checkProgress(): boolean {
this.mailAlreadyChecked = {}; return this.checkOnGoing;
}
// Reset validation status public get canRename(): boolean {
this.validationStateMap = { return (
account_settings_email: true, this.account &&
account_settings_full_name: true this.account.has_admin_role &&
}; this.originalStaticData.username === "admin" &&
this.showGenerateCli = false; this.account.user_id === 1
this.opened = true; );
} }
onRename(): void {
this.account.username = this.newAdminName;
this.RenameOnGoing = true;
}
confirmRename(): void {
if (this.canRename) {
this.session
.updateAccountSettings(this.account)
.subscribe(() => {
this.session.renameAdmin(this.account)
.subscribe(() => {
this.msgHandler.showSuccess("PROFILE.RENAME_SUCCESS");
this.opened = false;
this.logOut();
}, error => {
this.msgHandler.handleError(error);
});
}, error => {
this.isOnCalling = false;
this.error = error;
if (this.msgHandler.isAppLevel(error)) {
this.opened = false;
this.msgHandler.handleError(error);
} else {
this.inlineAlert.showInlineError(error);
}
});
}
}
// Log out system
logOut(): void {
// Naviagte to the sign in router-guard
// Appending 'signout' means destroy session cache
let navigatorExtra: NavigationExtras = {
queryParams: {signout: true}
};
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], navigatorExtra);
// Confirm search result panel is close
this.searchTrigger.closeSearch(true);
}
open() {
// Keep the initial data for future diff
this.originalStaticData = Object.assign({}, this.session.getCurrentUser());
this.account = Object.assign({}, this.session.getCurrentUser());
this.formValueChanged = false;
// Confirm inline alert is closed
this.inlineAlert.close();
// Clear check history
this.mailAlreadyChecked = {};
// Reset validation status
this.validationStateMap = {
account_settings_email: true,
account_settings_full_name: true
};
this.showGenerateCli = false;
this.opened = true;
}
close() { close() {
if (this.formValueChanged) { if (this.formValueChanged) {
@ -304,6 +309,10 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
this.isOnCalling = false; this.isOnCalling = false;
this.opened = false; this.opened = false;
this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS"); this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS");
// get user info from back-end then refresh account
this.session.retrieveUser().subscribe(() => {
this.refreshAccount();
});
}, error => { }, error => {
this.isOnCalling = false; this.isOnCalling = false;
this.error = error; this.error = error;
@ -317,68 +326,77 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked {
} }
} }
confirmNo($event: any): void { confirmNo($event: any): void {
if (this.RenameOnGoing) { if (this.RenameOnGoing) {
this.RenameOnGoing = false; this.RenameOnGoing = false;
}
if (this.renameConfirmation) {
this.renameConfirmation = false;
}
} }
if (this.renameConfirmation) {
this.renameConfirmation = false;
}
}
confirmYes($event: any): void {
if (this.RenameOnGoing) {
this.RenameOnGoing = false;
}
if (this.renameConfirmation) {
this.renameConfirmation = false;
}
this.inlineAlert.close();
this.opened = false;
}
onSuccess(event) {
this.inlineAlert.showInlineSuccess({message: 'PROFILE.COPY_SUCCESS'});
}
onError(event) {
this.inlineAlert.showInlineError({message: 'PROFILE.COPY_ERROR'});
}
generateCli(userId): void {
let generateCliMessage = new ConfirmationMessage(
'PROFILE.CONFIRM_TITLE_CLI_GENERATE',
'PROFILE.CONFIRM_BODY_CLI_GENERATE',
'',
userId,
ConfirmationTargets.TARGET,
ConfirmationButtons.CONFIRM_CANCEL);
this.confirmationDialogComponent.open(generateCliMessage);
}
showGenerateCliFn() {
this.showGenerateCli = !this.showGenerateCli;
}
confirmGenerate(event): void {
this.account.oidc_user_meta.secret = randomWord(9);
this.resetCliSecret(this.account.oidc_user_meta.secret);
}
resetCliSecret(secret) { confirmYes($event: any): void {
let userId = this.account.user_id; if (this.RenameOnGoing) {
this.accountSettingsService.saveNewCli(userId, {secret: secret}).subscribe(cliSecret => { this.RenameOnGoing = false;
this.account.oidc_user_meta.secret = secret; }
this.closeReset(); if (this.renameConfirmation) {
this.inlineAlert.showInlineSuccess({message: 'PROFILE.GENERATE_SUCCESS'}); this.renameConfirmation = false;
}, error => { }
this.resetSecretInlineAlert.showInlineError({message: 'PROFILE.GENERATE_ERROR'}); this.inlineAlert.close();
}); this.opened = false;
} }
disableChangeCliSecret() {
return this.resetSecretFrom.invalid || (this.resetSecretFrom.value.input_secret !== this.resetSecretFrom.value.confirm_secret); onSuccess(event) {
} this.inlineAlert.showInlineSuccess({message: 'PROFILE.COPY_SUCCESS'});
closeReset() { }
this.showSecretDetail = false;
this.showGenerateCliFn(); onError(event) {
this.resetSecretFrom.resetForm(new ResetSecret()); this.inlineAlert.showInlineError({message: 'PROFILE.COPY_ERROR'});
} }
openSecretDetail() {
this.showSecretDetail = true; generateCli(userId): void {
this.resetSecretInlineAlert.close(); let generateCliMessage = new ConfirmationMessage(
} 'PROFILE.CONFIRM_TITLE_CLI_GENERATE',
'PROFILE.CONFIRM_BODY_CLI_GENERATE',
'',
userId,
ConfirmationTargets.TARGET,
ConfirmationButtons.CONFIRM_CANCEL);
this.confirmationDialogComponent.open(generateCliMessage);
}
showGenerateCliFn() {
this.showGenerateCli = !this.showGenerateCli;
}
confirmGenerate(event): void {
this.account.oidc_user_meta.secret = randomWord(9);
this.resetCliSecret(this.account.oidc_user_meta.secret);
}
resetCliSecret(secret) {
let userId = this.account.user_id;
this.accountSettingsService.saveNewCli(userId, {secret: secret}).subscribe(cliSecret => {
this.account.oidc_user_meta.secret = secret;
this.closeReset();
this.inlineAlert.showInlineSuccess({message: 'PROFILE.GENERATE_SUCCESS'});
}, error => {
this.resetSecretInlineAlert.showInlineError({message: 'PROFILE.GENERATE_ERROR'});
});
}
disableChangeCliSecret() {
return this.resetSecretFrom.invalid || (this.resetSecretFrom.value.input_secret !== this.resetSecretFrom.value.confirm_secret);
}
closeReset() {
this.showSecretDetail = false;
this.showGenerateCliFn();
this.resetSecretFrom.resetForm(new ResetSecret());
}
openSecretDetail() {
this.showSecretDetail = true;
this.resetSecretInlineAlert.close();
}
} }

View File

@ -35,7 +35,7 @@ const signOffEndpoint = "/c/log_out";
const accountEndpoint = CURRENT_BASE_HREF + "/users/:id"; const accountEndpoint = CURRENT_BASE_HREF + "/users/:id";
const langEndpoint = "/language"; const langEndpoint = "/language";
const userExistsEndpoint = "/c/userExists"; const userExistsEndpoint = "/c/userExists";
const renameAdminEndpoint = CURRENT_BASE_HREF + '/internal/renameadmin'; const renameAdminEndpoint = 'api/internal/renameadmin';
const langMap = { const langMap = {
"zh": "zh-CN", "zh": "zh-CN",
"en": "en-US" "en": "en-US"