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>
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';
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';
@ViewChild('subvolTpl', { static: true })
subvolTpl: any;
- @BlockUI()
- blockUI: NgBlockUI;
-
snapshotSchedules$!: Observable<SnapshotSchedule[]>;
subject$ = new BehaviorSubject<SnapshotSchedule[]>([]);
snapScheduleModuleStatus$ = new BehaviorSubject<boolean>(false);
private authStorageService: AuthStorageService,
private modalService: ModalCdsService,
private mgrModuleService: MgrModuleService,
- private notificationService: NotificationService,
private actionLabels: ActionLabelsI18n,
private taskWrapper: TaskWrapperService
) {
}
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'
);
}
<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"
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';
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';
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();
rgwModuleData: string | any[] = [];
activeId: string;
activeNodeId?: string;
+ MODULE_NAME = 'rgw';
+ NAVIGATE_TO = '/rgw/multisite';
constructor(
private modalService: ModalService,
}
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
);
}
}
-<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')}">
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],
beforeEach(() => {
service = TestBed.inject(MgrModuleService);
httpTesting = TestBed.inject(HttpTestingController);
+ blockUIService = TestBed.inject(BlockUIService);
});
afterEach(() => {
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(() => {
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(() => {
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', () => {
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'
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
module: string,
enabled: boolean = false,
table: TableComponent = null,
- navigateTo: string = ''
+ navigateTo: string = '',
+ notificationText?: string,
+ navigateByUrl?: boolean
): void {
let $obs;
const fnWaitUntilReconnected = () => {
// 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();
}
},
() => {
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();
}
);
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;
configureTestBed({
declarations: [TelemetryNotificationComponent, AlertPanelComponent],
- imports: [NgbAlertModule, HttpClientTestingModule, ToastrModule.forRoot(), PipesModule],
+ imports: [
+ NgbAlertModule,
+ HttpClientTestingModule,
+ ToastrModule.forRoot(),
+ PipesModule,
+ BlockUIModule.forRoot()
+ ],
providers: [MgrModuleService, UserService]
});
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;
};
configureTestBed({
- imports: [RouterTestingModule.withRoutes(routes), ToastrModule.forRoot()],
+ imports: [RouterTestingModule.withRoutes(routes), ToastrModule.forRoot(), SharedModule],
providers: [
ModuleStatusGuardService,
{ provide: HttpClient, useValue: fakeService },
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: [
],
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 {}