diff --git a/src/portal/src/app/base/account-settings/account-settings-modal.component.html b/src/portal/src/app/base/account-settings/account-settings-modal.component.html index 81d9b325594..fa66164af1d 100644 --- a/src/portal/src/app/base/account-settings/account-settings-modal.component.html +++ b/src/portal/src/app/base/account-settings/account-settings-modal.component.html @@ -93,7 +93,7 @@ <h3 class="modal-title">{{'PROFILE.TITLE' | translate}}</h3> <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="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> </div> </clr-modal> diff --git a/src/portal/src/app/base/account-settings/account-settings-modal.component.ts b/src/portal/src/app/base/account-settings/account-settings-modal.component.ts index a3fcc1f7bb0..3dc54ce3f10 100644 --- a/src/portal/src/app/base/account-settings/account-settings-modal.component.ts +++ b/src/portal/src/app/base/account-settings/account-settings-modal.component.ts @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ChangeDetectorRef } from '@angular/core'; -import { Component, OnInit, ViewChild, AfterViewChecked } from "@angular/core"; +import { AfterViewChecked, Component, OnInit, ViewChild } from "@angular/core"; 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 { SessionService } from "../../shared/services/session.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"; @Component({ - selector: "account-settings-modal", - templateUrl: "account-settings-modal.component.html", - styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"] + selector: "account-settings-modal", + templateUrl: "account-settings-modal.component.html", + styleUrls: ["./account-settings-modal.component.scss", "../../common.scss"] }) export class AccountSettingsModalComponent implements OnInit, AfterViewChecked { - opened = false; - staticBackdrop = true; - originalStaticData: SessionUser; - account: SessionUser; - error: any = null; - emailTooltip = "TOOLTIP.EMAIL"; - mailAlreadyChecked = {}; - isOnCalling = false; - formValueChanged = false; - checkOnGoing = false; - RenameOnGoing = false; - originAdminName = "admin"; - newAdminName = "admin@harbor.local"; - renameConfirmation = false; - showSecretDetail = false; - resetForms = new ResetSecret(); - showGenerateCli: boolean = false; - @ViewChild("confirmationDialog") - confirmationDialogComponent: ConfirmationDialogComponent; - - accountFormRef: NgForm; - @ViewChild("accountSettingsFrom", {static: true}) accountForm: NgForm; - @ViewChild("resetSecretFrom", {static: true}) resetSecretFrom: NgForm; - @ViewChild("accountSettingInlineAlert") inlineAlert: InlineAlertComponent; - @ViewChild("resetSecretInlineAlert") resetSecretInlineAlert: InlineAlertComponent; - @ViewChild("copyInput") copyInput: CopyInputComponent; - showInputSecret: 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 = { - account_settings_email: true, - account_settings_full_name: true - }; - ngOnInit(): void { - // Value copy - 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(); - } - }); - } + opened = false; + staticBackdrop = true; + originalStaticData: SessionUser; + account: SessionUser; + error: any = null; + emailTooltip = "TOOLTIP.EMAIL"; + mailAlreadyChecked = {}; + isOnCalling = false; + formValueChanged = false; + checkOnGoing = false; + RenameOnGoing = false; + originAdminName = "admin"; + newAdminName = "admin@harbor.local"; + renameConfirmation = false; + showSecretDetail = false; + resetForms = new ResetSecret(); + showGenerateCli: boolean = false; + @ViewChild("confirmationDialog") + confirmationDialogComponent: ConfirmationDialogComponent; + + accountFormRef: NgForm; + @ViewChild("accountSettingsFrom", {static: true}) accountForm: NgForm; + @ViewChild("resetSecretFrom", {static: true}) resetSecretFrom: NgForm; + @ViewChild("accountSettingInlineAlert") inlineAlert: InlineAlertComponent; + @ViewChild("resetSecretInlineAlert") resetSecretInlineAlert: InlineAlertComponent; + @ViewChild("copyInput") copyInput: CopyInputComponent; + showInputSecret: boolean = false; + showConfirmSecret: boolean = false; + + constructor( + private session: SessionService, + private msgHandler: MessageHandlerService, + private router: Router, + private searchTrigger: SearchTriggerService, + private accountSettingsService: AccountSettingsModalService, + ) { } - } - - getValidationState(key: string): boolean { - return this.validationStateMap[key]; - } - - handleValidation(key: string, flag: boolean): void { - if (flag) { - // Checking - let cont = this.accountForm.controls[key]; - 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; + + private validationStateMap: any = { + account_settings_email: true, + account_settings_full_name: true + }; + + ngOnInit(): void { + this.refreshAccount(); + } + + refreshAccount() { + // Value copy + 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(); + } + }); } + } + } - // 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"; + getValidationState(key: string): boolean { + return this.validationStateMap[key]; + } + + handleValidation(key: string, flag: boolean): void { + if (flag) { + // Checking + let cont = this.accountForm.controls[key]; + 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"; } - } - } else { - // Reset - this.validationStateMap[key] = true; - this.emailTooltip = "TOOLTIP.EMAIL"; } - } - isUserDataChange(): boolean { - if (!this.originalStaticData || !this.account) { - return false; + 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; } - for (let prop in this.originalStaticData) { - if (this.originalStaticData[prop] !== this.account[prop]) { - return true; - } + + public get isValid(): boolean { + return ( + this.accountForm && + this.accountForm.valid && + this.error === null && + this.validationStateMap["account_settings_email"] + ); // backend check is valid as well } - 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 { - this.inlineAlert.showInlineError(error); + + 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 { + 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; - } + + // 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() { if (this.formValueChanged) { @@ -304,6 +309,10 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked { this.isOnCalling = false; this.opened = false; this.msgHandler.showSuccess("PROFILE.SAVE_SUCCESS"); + // get user info from back-end then refresh account + this.session.retrieveUser().subscribe(() => { + this.refreshAccount(); + }); }, error => { this.isOnCalling = false; this.error = error; @@ -317,68 +326,77 @@ export class AccountSettingsModalComponent implements OnInit, AfterViewChecked { } } - confirmNo($event: any): void { - if (this.RenameOnGoing) { - this.RenameOnGoing = false; + confirmNo($event: any): void { + if (this.RenameOnGoing) { + this.RenameOnGoing = 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; } - if (this.renameConfirmation) { - this.renameConfirmation = false; + + onSuccess(event) { + this.inlineAlert.showInlineSuccess({message: 'PROFILE.COPY_SUCCESS'}); } - } - confirmYes($event: any): void { - if (this.RenameOnGoing) { - this.RenameOnGoing = false; + + onError(event) { + this.inlineAlert.showInlineError({message: 'PROFILE.COPY_ERROR'}); } - if (this.renameConfirmation) { - this.renameConfirmation = false; + + 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) { + 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(); } - 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) { - 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(); - } } diff --git a/src/portal/src/app/shared/services/session.service.ts b/src/portal/src/app/shared/services/session.service.ts index 659fe93eab3..f5db71220b7 100644 --- a/src/portal/src/app/shared/services/session.service.ts +++ b/src/portal/src/app/shared/services/session.service.ts @@ -35,7 +35,7 @@ const signOffEndpoint = "/c/log_out"; const accountEndpoint = CURRENT_BASE_HREF + "/users/:id"; const langEndpoint = "/language"; const userExistsEndpoint = "/c/userExists"; -const renameAdminEndpoint = CURRENT_BASE_HREF + '/internal/renameadmin'; +const renameAdminEndpoint = 'api/internal/renameadmin'; const langMap = { "zh": "zh-CN", "en": "en-US"