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';
FormsModule,
NgxDatatableModule,
RouterTestingModule,
+ ComponentsModule,
AlertModule,
BsDropdownModule.forRoot(),
ChartsModule,
- PipesModule
- ],
- declarations: [
- RbdConfigurationListComponent,
- TableComponent,
- ErrorPanelComponent,
- SparklineComponent
+ PipesModule,
+ PopoverModule
],
+ declarations: [RbdConfigurationListComponent, TableComponent],
providers: [FormatterService, RbdConfigurationService, i18nProviders]
});
<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"
<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>
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>
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';
clusterWideActions: CdTableAction[];
icons = Icons;
- osds = [];
selection = new CdTableSelection();
+ osds = [];
protected static collectStates(osd) {
return [osd['in'] ? 'in' : 'out', osd['up'] ? 'up' : 'down'];
name: this.actionLabels.REWEIGHT,
permission: 'update',
click: () => this.reweight(),
- disable: () => !this.hasOsdSelected,
+ disable: () => !this.hasOsdSelected || !this.selection.hasSingleSelection,
icon: Icons.reweight
},
{
];
}
+ 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;
}
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);
}
}
}
const initialState = {
- selected: this.tableComponent.selection.selected,
+ selected: this.getSelectedIds(),
deep: deep
};
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());
+ })
+ );
}
}
});
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,
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());
+ })
+ );
}
}
});
<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">
[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">
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';
configureTestBed({
imports: [ReactiveFormsModule],
- declarations: [OsdScrubModalComponent],
+ declarations: [OsdScrubModalComponent, ListPipe],
schemas: [NO_ERRORS_SCHEMA],
providers: [
BsModalRef,
+ ListPipe,
{ provide: OsdService, useValue: fakeService },
{ provide: NotificationService, useValue: fakeService },
i18nProviders
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({
})
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() {
}
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();
+ }
+ );
+ }
}
}
});
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');
});
});
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`);
}
}
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>