]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard Adds multiple selection to osd table 29662/head
authorPooja <pooja.gautam@ts.fujitsu.com>
Fri, 30 Aug 2019 14:13:20 +0000 (16:13 +0200)
committerPooja <pooja.gautam@ts.fujitsu.com>
Wed, 18 Sep 2019 15:43:13 +0000 (17:43 +0200)
Fixes: https://tracker.ceph.com/issues/38091
Signed-off-by: Pooja <pooja.gautam@ts.fujitsu.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-configuration-list/rbd-configuration-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-reweight-modal/osd-reweight-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/osd.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.html

index 8743cf3580938cbcb28c498584f34b7102caf898..e0cc67d97855e71c1f1c68fa7ea5feed5cd730da 100644 (file)
@@ -6,11 +6,12 @@ import { NgxDatatableModule } from '@swimlane/ngx-datatable';
 import { ChartsModule } from 'ng2-charts';
 import { AlertModule } from 'ngx-bootstrap/alert';
 import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
+import { PopoverModule } from 'ngx-bootstrap/popover';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
-import { ErrorPanelComponent } from '../../../shared/components/error-panel/error-panel.component';
-import { SparklineComponent } from '../../../shared/components/sparkline/sparkline.component';
 import { TableComponent } from '../../../shared/datatable/table/table.component';
+
+import { ComponentsModule } from '../../../shared/components/components.module';
 import { RbdConfigurationEntry } from '../../../shared/models/configuration';
 import { PipesModule } from '../../../shared/pipes/pipes.module';
 import { FormatterService } from '../../../shared/services/formatter.service';
@@ -26,17 +27,14 @@ describe('RbdConfigurationListComponent', () => {
       FormsModule,
       NgxDatatableModule,
       RouterTestingModule,
+      ComponentsModule,
       AlertModule,
       BsDropdownModule.forRoot(),
       ChartsModule,
-      PipesModule
-    ],
-    declarations: [
-      RbdConfigurationListComponent,
-      TableComponent,
-      ErrorPanelComponent,
-      SparklineComponent
+      PipesModule,
+      PopoverModule
     ],
+    declarations: [RbdConfigurationListComponent, TableComponent],
     providers: [FormatterService, RbdConfigurationService, i18nProviders]
   });
 
index 19f79533f101188f4e9fda41309fd15bfdeacecd..7b2dcd69b31bdb68a1174d002f6097fc23246f3d 100644 (file)
@@ -1,12 +1,14 @@
 <tabset>
   <tab i18n-heading
        heading="OSDs List">
+
     <cd-table [data]="osds"
               (fetchData)="getOsdList()"
               [columns]="columns"
-              selectionType="single"
+              selectionType="multi"
               (updateSelection)="updateSelection($event)"
               [updateSelectionOnRefresh]="'never'">
+
       <div class="table-actions btn-toolbar">
         <cd-table-actions [permission]="permissions.osd"
                           [selection]="selection"
@@ -57,7 +59,7 @@
 
 <ng-template #markOsdConfirmationTpl
              let-markActionDescription="markActionDescription">
-  <ng-container i18n><strong>OSD {{ selection.first().id }}</strong> will be marked
+  <ng-container i18n><strong>OSD(s) {{  getSelectedIds() | list }}</strong> will be marked
 <strong>{{ markActionDescription }}</strong> if you proceed.</ng-container>
 </ng-template>
 
@@ -66,8 +68,8 @@
              let-actionDescription="actionDescription">
   <div *ngIf="!safeToDestroyResult['is_safe_to_destroy']"
        class="danger">
-    <cd-warning-panel i18n>The OSD is not safe to destroy!</cd-warning-panel>
+  <cd-warning-panel i18n>The {selection.hasSingleSelection, select, 1 {OSD is} 0 {OSDs are}} not safe to destroy!</cd-warning-panel>
   </div>
-  <ng-container i18n><strong>OSD {{ selection.first().id }}</strong> will be
+  <ng-container i18n><strong>OSD {{ getSelectedIds() | list }}</strong> will be
 <strong>{{ actionDescription }}</strong> if you proceed.</ng-container>
 </ng-template>
index 610bcd3b4afeb7502ffcb3e887940cc2662b086c..a1cd929f0f429b465dc3187176f41c1a8478b806 100644 (file)
@@ -1,8 +1,9 @@
 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
 
 import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
-import { Observable } from 'rxjs';
+import { forkJoin as observableForkJoin, Observable } from 'rxjs';
 
 import { OsdService } from '../../../../shared/api/osd.service';
 import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
@@ -51,8 +52,8 @@ export class OsdListComponent implements OnInit {
   clusterWideActions: CdTableAction[];
   icons = Icons;
 
-  osds = [];
   selection = new CdTableSelection();
+  osds = [];
 
   protected static collectStates(osd) {
     return [osd['in'] ? 'in' : 'out', osd['up'] ? 'up' : 'down'];
@@ -86,7 +87,7 @@ export class OsdListComponent implements OnInit {
         name: this.actionLabels.REWEIGHT,
         permission: 'update',
         click: () => this.reweight(),
-        disable: () => !this.hasOsdSelected,
+        disable: () => !this.hasOsdSelected || !this.selection.hasSingleSelection,
         icon: Icons.reweight
       },
       {
@@ -206,11 +207,17 @@ export class OsdListComponent implements OnInit {
     ];
   }
 
+  getSelectedIds() {
+    return this.selection.selected.map((row) => row.id);
+  }
+
   get hasOsdSelected() {
+    const validOsds = [];
     if (this.selection.hasSelection) {
-      const osdId = this.selection.first().id;
-      const osd = this.osds.filter((o) => o.id === osdId).pop();
-      return !!osd;
+      for (const osdId of this.getSelectedIds()) {
+        validOsds.push(this.osds.filter((o) => o.id === osdId).pop());
+      }
+      return validOsds.length > 0;
     }
     return false;
   }
@@ -228,23 +235,27 @@ export class OsdListComponent implements OnInit {
       return true;
     }
 
-    const osdId = this.selection.first().id;
-    const osd = this.osds.filter((o) => o.id === osdId).pop();
+    const validOsds = [];
+    if (this.selection.hasSelection) {
+      for (const osdId of this.getSelectedIds()) {
+        validOsds.push(this.osds.filter((o) => o.id === osdId).pop());
+      }
+    }
 
-    if (!osd) {
+    if (validOsds.length === 0) {
       // `osd` is undefined if the selected OSD has been removed.
       return true;
     }
 
     switch (state) {
       case 'in':
-        return osd.in === 1;
+        return validOsds.some((osd) => osd.in === 1);
       case 'out':
-        return osd.in !== 1;
+        return validOsds.some((osd) => osd.in !== 1);
       case 'down':
-        return osd.up !== 1;
+        return validOsds.some((osd) => osd.up !== 1);
       case 'up':
-        return osd.up === 1;
+        return validOsds.some((osd) => osd.up === 1);
     }
   }
 
@@ -267,7 +278,7 @@ export class OsdListComponent implements OnInit {
     }
 
     const initialState = {
-      selected: this.tableComponent.selection.selected,
+      selected: this.getSelectedIds(),
       deep: deep
     };
 
@@ -288,9 +299,11 @@ export class OsdListComponent implements OnInit {
           markActionDescription: markAction
         },
         onSubmit: () => {
-          onSubmit
-            .call(this.osdService, this.selection.first().id)
-            .subscribe(() => this.bsModalRef.hide());
+          observableForkJoin(
+            this.getSelectedIds().map((osd: any) => {
+              onSubmit.call(this.osdService, osd).subscribe(() => this.bsModalRef.hide());
+            })
+          );
         }
       }
     });
@@ -312,7 +325,7 @@ export class OsdListComponent implements OnInit {
     templateItemDescription: string,
     action: (id: number) => Observable<any>
   ): void {
-    this.osdService.safeToDestroy(this.selection.first().id).subscribe((result) => {
+    this.osdService.safeToDestroy(JSON.stringify(this.getSelectedIds())).subscribe((result) => {
       const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
         initialState: {
           actionDescription: actionDescription,
@@ -323,9 +336,11 @@ export class OsdListComponent implements OnInit {
             actionDescription: templateItemDescription
           },
           submitAction: () => {
-            action
-              .call(this.osdService, this.selection.first().id)
-              .subscribe(() => modalRef.hide());
+            observableForkJoin(
+              this.getSelectedIds().map((osd: any) => {
+                action.call(this.osdService, osd).subscribe(() => modalRef.hide());
+              })
+            );
           }
         }
       });
index d4cfce7e715422472fb314d8788bdb76a42f3e2a..ecdbc5c012d2ecefa606ccff81c45af2ce1ded12 100644 (file)
@@ -1,6 +1,6 @@
 <cd-modal [modalRef]="bsModalRef">
   <ng-container class="modal-title"
-                i18n>Reweight OSD</ng-container>
+                i18n>Reweight OSD: {{ osdId }}</ng-container>
 
   <ng-container class="modal-content">
     <form [formGroup]="reweightForm">
index 1000f5fb34e3522b72b15618fb448395ebc0c24b..f02a04afd0dc0d9bb0d77b4b4a526ee039c0ffb4 100644 (file)
@@ -8,10 +8,8 @@
           [formGroup]="scrubForm"
           novalidate>
       <div class="modal-body">
-        <div *ngIf="selected.length === 1">
-          <p i18n>You are about to apply a {deep, select, 1 {deep }}scrub to
-            the OSD <strong>{{ selected[0].id }}</strong>.</p>
-        </div>
+        <p i18n>You are about to apply a {deep, select, 1 {deep }}scrub to
+          the OSD(s): <strong>{{ selected | list }}</strong>.</p>
       </div>
 
       <div class="modal-footer">
index cf08843c263f13dbb6f54bb01fb187dfb0c3a1b3..bf8a553ba50b0b6b15e9fdd2eff86a145edc75e9 100644 (file)
@@ -3,9 +3,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
 import { ReactiveFormsModule } from '@angular/forms';
 
 import { BsModalRef } from 'ngx-bootstrap/modal';
-
 import { configureTestBed, i18nProviders } from '../../../../../testing/unit-test-helper';
 import { OsdService } from '../../../../shared/api/osd.service';
+import { ListPipe } from '../../../../shared/pipes/list.pipe';
 import { NotificationService } from '../../../../shared/services/notification.service';
 import { OsdScrubModalComponent } from './osd-scrub-modal.component';
 
@@ -27,10 +27,11 @@ describe('OsdScrubModalComponent', () => {
 
   configureTestBed({
     imports: [ReactiveFormsModule],
-    declarations: [OsdScrubModalComponent],
+    declarations: [OsdScrubModalComponent, ListPipe],
     schemas: [NO_ERRORS_SCHEMA],
     providers: [
       BsModalRef,
+      ListPipe,
       { provide: OsdService, useValue: fakeService },
       { provide: NotificationService, useValue: fakeService },
       i18nProviders
index 18b03254f61c1d2fa775945b5dc40ef6e5453e7c..f25ae21e149764b7af95f541b41dcddd185e1b43 100644 (file)
@@ -6,6 +6,7 @@ import { BsModalRef } from 'ngx-bootstrap/modal';
 
 import { OsdService } from '../../../../shared/api/osd.service';
 import { NotificationType } from '../../../../shared/enum/notification-type.enum';
+import { ListPipe } from '../../../../shared/pipes/list.pipe';
 import { NotificationService } from '../../../../shared/services/notification.service';
 
 @Component({
@@ -15,14 +16,15 @@ import { NotificationService } from '../../../../shared/services/notification.se
 })
 export class OsdScrubModalComponent implements OnInit {
   deep: boolean;
-  selected = [];
   scrubForm: FormGroup;
+  selected = [];
 
   constructor(
     public bsModalRef: BsModalRef,
     private osdService: OsdService,
     private notificationService: NotificationService,
-    private i18n: I18n
+    private i18n: I18n,
+    private listPipe: ListPipe
   ) {}
 
   ngOnInit() {
@@ -30,25 +32,24 @@ export class OsdScrubModalComponent implements OnInit {
   }
 
   scrub() {
-    const id = this.selected[0].id;
-
-    this.osdService.scrub(id, this.deep).subscribe(
-      () => {
-        const operation = this.deep ? 'Deep scrub' : 'Scrub';
-
-        this.notificationService.show(
-          NotificationType.success,
-          this.i18n('{{operation}} was initialized in the following OSD: {{id}}', {
-            operation: operation,
-            id: id
-          })
-        );
-
-        this.bsModalRef.hide();
-      },
-      () => {
-        this.bsModalRef.hide();
-      }
-    );
+    for (const id of this.selected) {
+      this.osdService.scrub(id, this.deep).subscribe(
+        () => {
+          const operation = this.deep ? 'Deep scrub' : 'Scrub';
+
+          this.notificationService.show(
+            NotificationType.success,
+            this.i18n('{{operation}} was initialized in the following OSD(s): {{id}}', {
+              operation: operation,
+              id: this.listPipe.transform(this.selected)
+            })
+          );
+          this.bsModalRef.hide();
+        },
+        () => {
+          this.bsModalRef.hide();
+        }
+      );
+    }
   }
 }
index e9225ef1359ca5ce83773739a6529ea8789320f2..fc1e52fff372420cf19a1779644000489abcf211 100644 (file)
@@ -107,8 +107,8 @@ describe('OsdService', () => {
   });
 
   it('should return if it is safe to destroy an OSD', () => {
-    service.safeToDestroy(1).subscribe();
-    const req = httpTesting.expectOne('api/osd/1/safe_to_destroy');
+    service.safeToDestroy('[0,1]').subscribe();
+    const req = httpTesting.expectOne('api/osd/[0,1]/safe_to_destroy');
     expect(req.request.method).toBe('GET');
   });
 });
index 06c63bf8c0bdaff579bcc2a86bc023d980622f37..81783baf45ba316165073fc18e999c2a9f180715 100644 (file)
@@ -106,11 +106,11 @@ export class OsdService {
     return this.http.post(`${this.path}/${id}/destroy`, null);
   }
 
-  safeToDestroy(id: number) {
+  safeToDestroy(ids: string) {
     interface SafeToDestroyResponse {
       'safe-to-destroy': boolean;
       message?: string;
     }
-    return this.http.get<SafeToDestroyResponse>(`${this.path}/${id}/safe_to_destroy`);
+    return this.http.get<SafeToDestroyResponse>(`${this.path}/${ids}/safe_to_destroy`);
   }
 }
index 2bf5af20b5998d8a7aed1ae9ab8fb8bf0ef02f59..1a74935ef9c043cdd177f358da99765117fae7fb 100644 (file)
                    let-offset="offset"
                    let-isVisible="isVisible">
         <div class="page-count">
+          <span *ngIf="selectionType == 'multi'">
+            <cd-helper i18n>Press and hold control button to select multiple rows to execute a common action on.</cd-helper>
+          </span>
           <span *ngIf="selectionType">
             {{ selectedCount }} <ng-container i18n="X selected">selected</ng-container> /
           </span>