From f7fbbdaddd5d73c000f70cd35ce046b92685b546 Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Wed, 19 Mar 2025 16:59:27 +0530 Subject: [PATCH] mgr/dashboard: fix blockUI implementation Enable/Disable mgr modules from SMB page, Rgw multisite Page and Mgr Modules page doesn't give proper message and doesn't block the UI This PR intends to fix this issue Fixes: https://tracker.ceph.com/issues/70557 Signed-off-by: Aashish Sharma --- .../cephfs-snapshotschedule-list.component.ts | 57 +++---------------- .../rgw-multisite-details.component.html | 10 ++-- .../rgw-multisite-details.component.ts | 53 ++++------------- .../workbench-layout.component.html | 2 +- .../app/shared/api/mgr-module.service.spec.ts | 15 +++-- .../src/app/shared/api/mgr-module.service.ts | 33 ++++++++--- .../telemetry-notification.component.spec.ts | 9 ++- .../module-status-guard.service.spec.ts | 3 +- .../frontend/src/app/shared/shared.module.ts | 6 +- 9 files changed, 71 insertions(+), 117 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts index 09b74b51d7b7f..68f382b778bc8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts @@ -8,8 +8,8 @@ import { ViewChild } from '@angular/core'; import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { BehaviorSubject, Observable, Subscription, of, timer } from 'rxjs'; -import { finalize, map, shareReplay, switchMap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subscription, of } from 'rxjs'; +import { map, shareReplay, switchMap } from 'rxjs/operators'; import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service'; import { CdForm } from '~/app/shared/forms/cd-form'; import { CdTableAction } from '~/app/shared/models/cd-table-action'; @@ -22,9 +22,6 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; import { Icons } from '~/app/shared/enum/icons.enum'; import { CellTemplate } from '~/app/shared/enum/cell-template.enum'; import { MgrModuleService } from '~/app/shared/api/mgr-module.service'; -import { NotificationService } from '~/app/shared/services/notification.service'; -import { BlockUI, NgBlockUI } from 'ng-block-ui'; -import { NotificationType } from '~/app/shared/enum/notification-type.enum'; import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CephfsSnapshotscheduleFormComponent } from '../cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component'; import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component'; @@ -52,9 +49,6 @@ export class CephfsSnapshotscheduleListComponent @ViewChild('subvolTpl', { static: true }) subvolTpl: any; - @BlockUI() - blockUI: NgBlockUI; - snapshotSchedules$!: Observable; subject$ = new BehaviorSubject([]); snapScheduleModuleStatus$ = new BehaviorSubject(false); @@ -78,7 +72,6 @@ export class CephfsSnapshotscheduleListComponent private authStorageService: AuthStorageService, private modalService: ModalCdsService, private mgrModuleService: MgrModuleService, - private notificationService: NotificationService, private actionLabels: ActionLabelsI18n, private taskWrapper: TaskWrapperService ) { @@ -199,46 +192,12 @@ export class CephfsSnapshotscheduleListComponent } enableSnapshotSchedule() { - let $obs; - const fnWaitUntilReconnected = () => { - timer(this.ENABLE_MODULE_TIMER).subscribe(() => { - // Trigger an API request to check if the connection is - // re-established. - this.mgrModuleService.list().subscribe( - () => { - // Resume showing the notification toasties. - this.notificationService.suspendToasties(false); - // Unblock the whole UI. - this.blockUI.stop(); - // Reload the data table content. - this.notificationService.show( - NotificationType.success, - $localize`Enabled Snapshot Schedule Module` - ); - // Reload the data table content. - }, - () => { - fnWaitUntilReconnected(); - } - ); - }); - }; - - if (!this.snapScheduleModuleStatus$.value) { - $obs = this.mgrModuleService - .enable(this.MODULE_NAME) - .pipe(finalize(() => this.snapScheduleModuleStatus$.next(true))); - } - $obs.subscribe( - () => undefined, - () => { - // Suspend showing the notification toasties. - this.notificationService.suspendToasties(true); - // Block the whole UI to prevent user interactions until - // the connection to the backend is reestablished - this.blockUI.start($localize`Reconnecting, please wait ...`); - fnWaitUntilReconnected(); - } + this.mgrModuleService.updateModuleState( + this.MODULE_NAME, + false, + null, + '', + 'Enabled Snapshot Schedule Module' ); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html index 1ceac30594454..3f9794e26261a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html @@ -4,11 +4,11 @@ In order to access the import/export feature, the rgw module must be enabled - + i18n + class="align-items-center" + actionName="Enable" + (action)="enableRgwModule()"> + In order to access the import/export/Setup Multi-site Replication feature, the rgw module must be enabled. { - observableTimer(2000).subscribe(() => { - // Trigger an API request to check if the connection is - // re-established. - this.mgrModuleService.list().subscribe( - () => { - // Resume showing the notification toasties. - this.notificationService.suspendToasties(false); - // Unblock the whole UI. - this.blockUI.stop(); - // Reload the data table content. - this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`); - this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => { - this.router.navigate(['/rgw/multisite']); - }); - // Reload the data table content. - }, - () => { - fnWaitUntilReconnected(); - } - ); - }); - }; - - if (!this.rgwModuleStatus) { - $obs = this.mgrModuleService.enable(RGW); - } - $obs.subscribe( - () => undefined, - () => { - // Suspend showing the notification toasties. - this.notificationService.suspendToasties(true); - // Block the whole UI to prevent user interactions until - // the connection to the backend is reestablished - this.blockUI.start($localize`Reconnecting, please wait ...`); - fnWaitUntilReconnected(); - } + this.mgrModuleService.updateModuleState( + this.MODULE_NAME, + false, + null, + this.NAVIGATE_TO, + 'Enabled RGW Module', + true ); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html index 2337172a08d29..d90b82df449a0 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts index 7996a39753422..da9f0cdb05dbf 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts @@ -9,10 +9,12 @@ import { NotificationService } from '~/app/shared/services/notification.service' import { MgrModuleListComponent } from '~/app/ceph/cluster/mgr-modules/mgr-module-list/mgr-module-list.component'; import { ToastrModule } from 'ngx-toastr'; import { SharedModule } from '../shared.module'; +import { BlockUIService } from 'ng-block-ui'; describe('MgrModuleService', () => { let service: MgrModuleService; let httpTesting: HttpTestingController; + let blockUIService: BlockUIService; configureTestBed({ declarations: [MgrModuleListComponent], @@ -23,6 +25,7 @@ describe('MgrModuleService', () => { beforeEach(() => { service = TestBed.inject(MgrModuleService); httpTesting = TestBed.inject(HttpTestingController); + blockUIService = TestBed.inject(BlockUIService); }); afterEach(() => { @@ -83,8 +86,8 @@ describe('MgrModuleService', () => { component.selection = new CdTableSelection(); spyOn(notificationService, 'suspendToasties'); - spyOn(service.blockUI, 'start'); - spyOn(service.blockUI, 'stop'); + spyOn(blockUIService, 'start'); + spyOn(blockUIService, 'stop'); }); it('should enable module', fakeAsync(() => { @@ -102,8 +105,8 @@ describe('MgrModuleService', () => { expect(service.enable).toHaveBeenCalledWith('foo'); expect(service.list).toHaveBeenCalledTimes(2); expect(notificationService.suspendToasties).toHaveBeenCalledTimes(2); - expect(service.blockUI.start).toHaveBeenCalled(); - expect(service.blockUI.stop).toHaveBeenCalled(); + expect(blockUIService.start).toHaveBeenCalled(); + expect(blockUIService.stop).toHaveBeenCalled(); })); it('should disable module', fakeAsync(() => { @@ -120,8 +123,8 @@ describe('MgrModuleService', () => { expect(service.disable).toHaveBeenCalledWith('bar'); expect(service.list).toHaveBeenCalledTimes(1); expect(notificationService.suspendToasties).toHaveBeenCalledTimes(2); - expect(service.blockUI.start).toHaveBeenCalled(); - expect(service.blockUI.stop).toHaveBeenCalled(); + expect(blockUIService.start).toHaveBeenCalled(); + expect(blockUIService.stop).toHaveBeenCalled(); })); it('should not disable module without selecting one', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts index c0532f5dc6706..940a8b5540416 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts @@ -1,12 +1,13 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { BlockUI, NgBlockUI } from 'ng-block-ui'; +import { BlockUIService } from 'ng-block-ui'; import { Observable, timer as observableTimer } from 'rxjs'; import { NotificationService } from '../services/notification.service'; import { TableComponent } from '../datatable/table/table.component'; import { Router } from '@angular/router'; import { MgrModuleInfo } from '../models/mgr-modules.interface'; +import { NotificationType } from '../enum/notification-type.enum'; @Injectable({ providedIn: 'root' @@ -14,12 +15,10 @@ import { MgrModuleInfo } from '../models/mgr-modules.interface'; export class MgrModuleService { private url = 'api/mgr/module'; - @BlockUI() - blockUI: NgBlockUI; - readonly REFRESH_INTERVAL = 2000; constructor( + private blockUI: BlockUIService, private http: HttpClient, private notificationService: NotificationService, private router: Router @@ -84,7 +83,9 @@ export class MgrModuleService { module: string, enabled: boolean = false, table: TableComponent = null, - navigateTo: string = '' + navigateTo: string = '', + notificationText?: string, + navigateByUrl?: boolean ): void { let $obs; const fnWaitUntilReconnected = () => { @@ -96,13 +97,27 @@ export class MgrModuleService { // Resume showing the notification toasties. this.notificationService.suspendToasties(false); // Unblock the whole UI. - this.blockUI.stop(); + this.blockUI.stop('global'); // Reload the data table content. if (table) { table.refreshBtn(); } - if (navigateTo) { - this.router.navigate([navigateTo]); + + if (notificationText) { + this.notificationService.show( + NotificationType.success, + $localize`${notificationText}` + ); + } + + if (!navigateTo) return; + + const navigate = () => this.router.navigate([navigateTo]); + + if (navigateByUrl) { + this.router.navigateByUrl('/', { skipLocationChange: true }).then(navigate); + } else { + navigate(); } }, () => { @@ -126,7 +141,7 @@ export class MgrModuleService { this.notificationService.suspendToasties(true); // Block the whole UI to prevent user interactions until // the connection to the backend is reestablished - this.blockUI.start($localize`Reconnecting, please wait ...`); + this.blockUI.start('global', $localize`Reconnecting, please wait ...`); fnWaitUntilReconnected(); } ); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts index e946e79d87e6b..dd5012bd53779 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts @@ -15,6 +15,7 @@ import { NotificationService } from '~/app/shared/services/notification.service' import { TelemetryNotificationService } from '~/app/shared/services/telemetry-notification.service'; import { configureTestBed } from '~/testing/unit-test-helper'; import { TelemetryNotificationComponent } from './telemetry-notification.component'; +import { BlockUIModule } from 'ng-block-ui'; describe('TelemetryActivationNotificationComponent', () => { let component: TelemetryNotificationComponent; @@ -41,7 +42,13 @@ describe('TelemetryActivationNotificationComponent', () => { configureTestBed({ declarations: [TelemetryNotificationComponent, AlertPanelComponent], - imports: [NgbAlertModule, HttpClientTestingModule, ToastrModule.forRoot(), PipesModule], + imports: [ + NgbAlertModule, + HttpClientTestingModule, + ToastrModule.forRoot(), + PipesModule, + BlockUIModule.forRoot() + ], providers: [MgrModuleService, UserService] }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts index 32731a25e0f83..78c1b01e98536 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts @@ -11,6 +11,7 @@ import { MgrModuleService } from '../api/mgr-module.service'; import { ModuleStatusGuardService } from './module-status-guard.service'; import { ToastrModule } from 'ngx-toastr'; import { CdDatePipe } from '../pipes/cd-date.pipe'; +import { SharedModule } from '../shared.module'; describe('ModuleStatusGuardService', () => { let service: ModuleStatusGuardService; @@ -55,7 +56,7 @@ describe('ModuleStatusGuardService', () => { }; configureTestBed({ - imports: [RouterTestingModule.withRoutes(routes), ToastrModule.forRoot()], + imports: [RouterTestingModule.withRoutes(routes), ToastrModule.forRoot(), SharedModule], providers: [ ModuleStatusGuardService, { provide: HttpClient, useValue: fakeService }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts index 9b119c700ca37..a918ff01d3334 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts @@ -16,6 +16,7 @@ import { FormlyArrayTypeComponent } from './forms/crud-form/formly-array-type/fo import { FormlyObjectTypeComponent } from './forms/crud-form/formly-object-type/formly-object-type.component'; import { FormlyInputTypeComponent } from './forms/crud-form/formly-input-type/formly-input-type.component'; import { FormlyTextareaTypeComponent } from './forms/crud-form/formly-textarea-type/formly-textarea-type.component'; +import { BlockUIModule, BlockUIService } from 'ng-block-ui'; @NgModule({ imports: [ @@ -34,10 +35,11 @@ import { FormlyTextareaTypeComponent } from './forms/crud-form/formly-textarea-t ], validationMessages: [{ name: 'required', message: 'This field is required' }] }), - FormlyBootstrapModule + FormlyBootstrapModule, + BlockUIModule.forRoot() ], declarations: [FormlyTextareaTypeComponent], exports: [ComponentsModule, PipesModule, DataTableModule, DirectivesModule], - providers: [AuthStorageService, AuthGuardService, FormatterService, CssHelper] + providers: [AuthStorageService, AuthGuardService, FormatterService, CssHelper, BlockUIService] }) export class SharedModule {} -- 2.39.5