]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: fix blockUI implementation 62392/head
authorAashish Sharma <Aashish.Sharma1@ibm.com>
Wed, 19 Mar 2025 11:29:27 +0000 (16:59 +0530)
committerAashish Sharma <Aashish.Sharma1@ibm.com>
Mon, 24 Mar 2025 10:28:53 +0000 (15:58 +0530)
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 <aasharma@redhat.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/layouts/workbench-layout/workbench-layout.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/mgr-module.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/telemetry-notification/telemetry-notification.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/module-status-guard.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts

index 09b74b51d7b7f0e39e8fc451d12442c541679cf4..68f382b778bc876f2a2ecb7909a93eaabce937aa 100644 (file)
@@ -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<SnapshotSchedule[]>;
   subject$ = new BehaviorSubject<SnapshotSchedule[]>([]);
   snapScheduleModuleStatus$ = new BehaviorSubject<boolean>(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'
     );
   }
 
index 1ceac305944545ccf09f3e2caf994b3b072023f6..3f9794e26261ab86b6184ffa196ba9e85853707b 100644 (file)
@@ -4,11 +4,11 @@
   <cd-alert-panel *ngIf="permissions.configOpt.create && !rgwModuleStatus"
                   type="info"
                   spacingClass="mb-3"
-                  class="d-flex align-items-center"
-                  i18n>In order to access the import/export feature, the rgw module must be enabled
-    <button class="btn btn-light mx-2"
-            type="button"
-            (click)="enableRgwModule()">Enable</button>
+                  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.
   </cd-alert-panel>
   <cd-alert-panel   *ngIf="restartGatewayMessage"
                     type="warning"
index ed21e126507f3c600bacc4aecf82970d289bae8c..6a4c9495151ac847aa497766f2e663c4fbb3213e 100644 (file)
@@ -11,7 +11,7 @@ import { Node } from 'carbon-components-angular/treeview/tree-node.types';
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import _ from 'lodash';
 
-import { forkJoin, Subscription, timer as observableTimer } from 'rxjs';
+import { forkJoin, Subscription } from 'rxjs';
 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
@@ -37,7 +37,6 @@ import { RgwMultisiteZoneFormComponent } from '../rgw-multisite-zone-form/rgw-mu
 import { RgwMultisiteZonegroupFormComponent } from '../rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component';
 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
-import { BlockUI, NgBlockUI } from 'ng-block-ui';
 import { Router } from '@angular/router';
 import { RgwMultisiteWizardComponent } from '../rgw-multisite-wizard/rgw-multisite-wizard.component';
 import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
@@ -66,9 +65,6 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
     disableExport: $localize`Please create master zone group and master zone for each of the realms`
   };
 
-  @BlockUI()
-  blockUI: NgBlockUI;
-
   icons = Icons;
   permissions: Permissions;
   selection = new CdTableSelection();
@@ -128,6 +124,8 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
   rgwModuleData: string | any[] = [];
   activeId: string;
   activeNodeId?: string;
+  MODULE_NAME = 'rgw';
+  NAVIGATE_TO = '/rgw/multisite';
 
   constructor(
     private modalService: ModalService,
@@ -610,44 +608,13 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
   }
 
   enableRgwModule() {
-    let $obs;
-    const fnWaitUntilReconnected = () => {
-      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
     );
   }
 }
index 2337172a08d2901e024541b95de97c6e424843b2..d90b82df449a006a5adc0194b0bbd7aee3c16ccb 100644 (file)
@@ -1,4 +1,4 @@
-<block-ui>
+<block-ui name="global">
   <cd-navigation>
     <div class="container-fluid h-100"
          [ngClass]="{'dashboard': (router.url == '/dashboard' || router.url == '/dashboard_3' || router.url == '/multi-cluster/overview'), 'rgw-dashboard': (router.url == '/rgw/overview')}">
index 7996a39753422bc9270ee70edee8c8fbad074091..da9f0cdb05dbfbd9eee8ebe574b13cf88b96cf75 100644 (file)
@@ -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', () => {
index c0532f5dc6706324c0a0a6a2ec2e6a1593128201..940a8b55404166f3a07493636a7ec826a9082423 100644 (file)
@@ -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();
       }
     );
index e946e79d87e6bde8789dfcba1059b864382a199d..dd5012bd537798dc46f37a403cdcb7b798c99ce0 100644 (file)
@@ -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]
   });
 
index 32731a25e0f83a3cb3ee00d06a79666906ce945e..78c1b01e9853673a5c51fbe4ba145eced99733f7 100644 (file)
@@ -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 },
index 9b119c700ca3788333c48f28abd9f73fac59e4e2..a918ff01d3334e67bb66d43d5b74ae3612d83ef6 100644 (file)
@@ -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 {}