1 import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { FormControl } from '@angular/forms';
3 import { Router } from '@angular/router';
5 import { I18n } from '@ngx-translate/i18n-polyfill';
6 import * as _ from 'lodash';
7 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
8 import { forkJoin as observableForkJoin, Observable } from 'rxjs';
10 import { OsdService } from '../../../../shared/api/osd.service';
11 import { ListWithDetails } from '../../../../shared/classes/list-with-details.class';
12 import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
13 import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
14 import { FormModalComponent } from '../../../../shared/components/form-modal/form-modal.component';
15 import { ActionLabelsI18n, URLVerbs } from '../../../../shared/constants/app.constants';
16 import { TableComponent } from '../../../../shared/datatable/table/table.component';
17 import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
18 import { Icons } from '../../../../shared/enum/icons.enum';
19 import { NotificationType } from '../../../../shared/enum/notification-type.enum';
20 import { CdFormGroup } from '../../../../shared/forms/cd-form-group';
21 import { CdTableAction } from '../../../../shared/models/cd-table-action';
22 import { CdTableColumn } from '../../../../shared/models/cd-table-column';
23 import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
24 import { FinishedTask } from '../../../../shared/models/finished-task';
25 import { Permissions } from '../../../../shared/models/permissions';
26 import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
27 import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
28 import { DepCheckerService } from '../../../../shared/services/dep-checker.service';
29 import { NotificationService } from '../../../../shared/services/notification.service';
30 import { TaskWrapperService } from '../../../../shared/services/task-wrapper.service';
31 import { URLBuilderService } from '../../../../shared/services/url-builder.service';
32 import { OsdFlagsModalComponent } from '../osd-flags-modal/osd-flags-modal.component';
33 import { OsdPgScrubModalComponent } from '../osd-pg-scrub-modal/osd-pg-scrub-modal.component';
34 import { OsdRecvSpeedModalComponent } from '../osd-recv-speed-modal/osd-recv-speed-modal.component';
35 import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component';
36 import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
38 const BASE_URL = 'osd';
41 selector: 'cd-osd-list',
42 templateUrl: './osd-list.component.html',
43 styleUrls: ['./osd-list.component.scss'],
44 providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
46 export class OsdListComponent extends ListWithDetails implements OnInit {
47 @ViewChild('osdUsageTpl', { static: true })
48 osdUsageTpl: TemplateRef<any>;
49 @ViewChild('markOsdConfirmationTpl', { static: true })
50 markOsdConfirmationTpl: TemplateRef<any>;
51 @ViewChild('criticalConfirmationTpl', { static: true })
52 criticalConfirmationTpl: TemplateRef<any>;
53 @ViewChild(TableComponent, { static: true })
54 tableComponent: TableComponent;
55 @ViewChild('reweightBodyTpl', { static: false })
56 reweightBodyTpl: TemplateRef<any>;
57 @ViewChild('safeToDestroyBodyTpl', { static: false })
58 safeToDestroyBodyTpl: TemplateRef<any>;
59 @ViewChild('deleteOsdExtraTpl', { static: false })
60 deleteOsdExtraTpl: TemplateRef<any>;
62 permissions: Permissions;
63 tableActions: CdTableAction[];
64 bsModalRef: BsModalRef;
65 columns: CdTableColumn[];
66 clusterWideActions: CdTableAction[];
69 selection = new CdTableSelection();
72 protected static collectStates(osd: any) {
73 const states = [osd['in'] ? 'in' : 'out'];
76 } else if (osd.state.includes('destroyed')) {
77 states.push('destroyed');
85 private authStorageService: AuthStorageService,
86 private osdService: OsdService,
87 private dimlessBinaryPipe: DimlessBinaryPipe,
88 private modalService: BsModalService,
90 private urlBuilder: URLBuilderService,
91 private router: Router,
92 private depCheckerService: DepCheckerService,
93 private taskWrapper: TaskWrapperService,
94 public actionLabels: ActionLabelsI18n,
95 public notificationService: NotificationService
98 this.permissions = this.authStorageService.getPermissions();
101 name: this.actionLabels.CREATE,
102 permission: 'create',
105 this.depCheckerService.checkOrchestratorOrModal(
106 this.actionLabels.CREATE,
109 this.router.navigate([this.urlBuilder.getCreate()]);
113 canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
116 name: this.actionLabels.EDIT,
117 permission: 'update',
119 click: () => this.editAction()
122 name: this.actionLabels.SCRUB,
123 permission: 'update',
125 click: () => this.scrubAction(false),
126 disable: () => !this.hasOsdSelected,
127 canBePrimary: (selection: CdTableSelection) => selection.hasSelection
130 name: this.actionLabels.DEEP_SCRUB,
131 permission: 'update',
132 icon: Icons.deepCheck,
133 click: () => this.scrubAction(true),
134 disable: () => !this.hasOsdSelected
137 name: this.actionLabels.REWEIGHT,
138 permission: 'update',
139 click: () => this.reweight(),
140 disable: () => !this.hasOsdSelected || !this.selection.hasSingleSelection,
144 name: this.actionLabels.MARK_OUT,
145 permission: 'update',
146 click: () => this.showConfirmationModal(this.i18n('out'), this.osdService.markOut),
147 disable: () => this.isNotSelectedOrInState('out'),
151 name: this.actionLabels.MARK_IN,
152 permission: 'update',
153 click: () => this.showConfirmationModal(this.i18n('in'), this.osdService.markIn),
154 disable: () => this.isNotSelectedOrInState('in'),
158 name: this.actionLabels.MARK_DOWN,
159 permission: 'update',
160 click: () => this.showConfirmationModal(this.i18n('down'), this.osdService.markDown),
161 disable: () => this.isNotSelectedOrInState('down'),
165 name: this.actionLabels.MARK_LOST,
166 permission: 'delete',
168 this.showCriticalConfirmationModal(
170 this.i18n('OSD lost'),
171 this.i18n('marked lost'),
173 return this.osdService.safeToDestroy(JSON.stringify(ids));
175 'is_safe_to_destroy',
176 this.osdService.markLost
178 disable: () => this.isNotSelectedOrInState('up'),
182 name: this.actionLabels.PURGE,
183 permission: 'delete',
185 this.showCriticalConfirmationModal(
190 return this.osdService.safeToDestroy(JSON.stringify(ids));
192 'is_safe_to_destroy',
194 this.selection = new CdTableSelection();
195 return this.osdService.purge(id);
198 disable: () => this.isNotSelectedOrInState('up'),
202 name: this.actionLabels.DESTROY,
203 permission: 'delete',
205 this.showCriticalConfirmationModal(
206 this.i18n('destroy'),
208 this.i18n('destroyed'),
210 return this.osdService.safeToDestroy(JSON.stringify(ids));
212 'is_safe_to_destroy',
214 this.selection = new CdTableSelection();
215 return this.osdService.destroy(id);
218 disable: () => this.isNotSelectedOrInState('up'),
219 icon: Icons.destroyCircle
222 name: this.actionLabels.DELETE,
223 permission: 'delete',
224 click: () => this.delete(),
225 disable: () => !this.hasOsdSelected,
232 this.clusterWideActions = [
234 name: this.i18n('Flags'),
236 click: () => this.configureFlagsAction(),
238 visible: () => this.permissions.osd.read
241 name: this.i18n('Recovery Priority'),
242 icon: Icons.deepCheck,
243 click: () => this.configureQosParamsAction(),
245 visible: () => this.permissions.configOpt.read
248 name: this.i18n('PG scrub'),
250 click: () => this.configurePgScrubAction(),
252 visible: () => this.permissions.configOpt.read
256 { prop: 'host.name', name: this.i18n('Host') },
257 { prop: 'id', name: this.i18n('ID'), flexGrow: 1, cellTransformation: CellTemplate.bold },
259 prop: 'collectedStates',
260 name: this.i18n('Status'),
262 cellTransformation: CellTemplate.badge,
263 customTemplateConfig: {
265 in: { class: 'badge-success' },
266 up: { class: 'badge-success' },
267 down: { class: 'badge-danger' },
268 out: { class: 'badge-danger' },
269 destroyed: { class: 'badge-danger' }
274 prop: 'tree.device_class',
275 name: this.i18n('Device class'),
277 cellTransformation: CellTemplate.badge,
278 customTemplateConfig: {
280 hdd: { class: 'badge-hdd' },
281 ssd: { class: 'badge-ssd' }
287 name: this.i18n('PGs'),
291 prop: 'stats.stat_bytes',
292 name: this.i18n('Size'),
294 pipe: this.dimlessBinaryPipe
296 { prop: 'stats.usage', name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
298 prop: 'stats_history.out_bytes',
299 name: this.i18n('Read bytes'),
300 cellTransformation: CellTemplate.sparkline
303 prop: 'stats_history.in_bytes',
304 name: this.i18n('Write bytes'),
305 cellTransformation: CellTemplate.sparkline
309 name: this.i18n('Read ops'),
310 cellTransformation: CellTemplate.perSecond
314 name: this.i18n('Write ops'),
315 cellTransformation: CellTemplate.perSecond
321 * Only returns valid IDs, e.g. if an OSD is falsely still selected after being deleted, it won't
324 getSelectedOsdIds(): number[] {
325 const osdIds = this.osds.map((osd) => osd.id);
326 return this.selection.selected.map((row) => row.id).filter((id) => osdIds.includes(id));
329 getSelectedOsds(): any[] {
330 return this.osds.filter(
331 (osd) => !_.isUndefined(osd) && this.getSelectedOsdIds().includes(osd.id)
335 get hasOsdSelected(): boolean {
336 return this.getSelectedOsdIds().length > 0;
339 updateSelection(selection: CdTableSelection) {
340 this.selection = selection;
344 * Returns true if no rows are selected or if *any* of the selected rows are in the given
345 * state. Useful for deactivating the corresponding menu entry.
347 isNotSelectedOrInState(state: 'in' | 'up' | 'down' | 'out'): boolean {
348 const selectedOsds = this.getSelectedOsds();
349 if (selectedOsds.length === 0) {
354 return selectedOsds.some((osd) => osd.in === 1);
356 return selectedOsds.some((osd) => osd.in !== 1);
358 return selectedOsds.some((osd) => osd.up !== 1);
360 return selectedOsds.some((osd) => osd.up === 1);
365 this.osdService.getList().subscribe((data: any[]) => {
366 this.osds = data.map((osd) => {
367 osd.collectedStates = OsdListComponent.collectStates(osd);
368 osd.stats_history.out_bytes = osd.stats_history.op_out_bytes.map((i: string) => i[1]);
369 osd.stats_history.in_bytes = osd.stats_history.op_in_bytes.map((i: string) => i[1]);
370 osd.stats.usage = osd.stats.stat_bytes_used / osd.stats.stat_bytes;
371 osd.cdIsBinary = true;
378 const selectedOsd = _.filter(this.osds, ['id', this.selection.first().id]).pop();
380 this.modalService.show(FormModalComponent, {
382 titleText: this.i18n('Edit OSD: {{id}}', {
389 value: selectedOsd.tree.device_class,
390 label: this.i18n('Device class'),
394 submitButtonText: this.i18n('Edit OSD'),
395 onSubmit: (values: any) => {
396 this.osdService.update(selectedOsd.id, values.deviceClass).subscribe(() => {
397 this.notificationService.show(
398 NotificationType.success,
399 this.i18n('Updated OSD "{{id}}"', {
410 scrubAction(deep: boolean) {
411 if (!this.hasOsdSelected) {
415 const initialState = {
416 selected: this.getSelectedOsdIds(),
420 this.bsModalRef = this.modalService.show(OsdScrubModalComponent, { initialState });
423 configureFlagsAction() {
424 this.bsModalRef = this.modalService.show(OsdFlagsModalComponent, {});
427 showConfirmationModal(markAction: string, onSubmit: (id: number) => Observable<any>) {
428 this.bsModalRef = this.modalService.show(ConfirmationModalComponent, {
430 titleText: this.i18n('Mark OSD {{markAction}}', { markAction: markAction }),
431 buttonText: this.i18n('Mark {{markAction}}', { markAction: markAction }),
432 bodyTpl: this.markOsdConfirmationTpl,
434 markActionDescription: markAction
438 this.getSelectedOsdIds().map((osd: any) => onSubmit.call(this.osdService, osd))
439 ).subscribe(() => this.bsModalRef.hide());
446 const selectedOsd = this.osds.filter((o) => o.id === this.selection.first().id).pop();
447 this.modalService.show(OsdReweightModalComponent, {
449 currentWeight: selectedOsd.weight,
450 osdId: selectedOsd.id
456 const deleteFormGroup = new CdFormGroup({
457 preserve: new FormControl(false)
460 this.depCheckerService.checkOrchestratorOrModal(
461 this.actionLabels.DELETE,
464 this.showCriticalConfirmationModal(
467 this.i18n('deleted'),
469 return this.osdService.safeToDelete(JSON.stringify(ids));
473 this.selection = new CdTableSelection();
474 return this.taskWrapper.wrapTaskAroundCall({
475 task: new FinishedTask('osd/' + URLVerbs.DELETE, {
478 call: this.osdService.delete(id, deleteFormGroup.value.preserve, true)
483 this.deleteOsdExtraTpl
490 * Perform check first and display a critical confirmation modal.
491 * @param {string} actionDescription name of the action.
492 * @param {string} itemDescription the item's name that the action operates on.
493 * @param {string} templateItemDescription the action name to be displayed in modal template.
494 * @param {Function} check the function is called to check if the action is safe.
495 * @param {string} checkKey the safe indicator's key in the check response.
496 * @param {Function} action the action function.
497 * @param {boolean} taskWrapped if true, hide confirmation modal after action
498 * @param {CdFormGroup} childFormGroup additional child form group to be passed to confirmation modal
499 * @param {TemplateRef<any>} childFormGroupTemplate template for additional child form group
501 showCriticalConfirmationModal(
502 actionDescription: string,
503 itemDescription: string,
504 templateItemDescription: string,
505 check: (ids: number[]) => Observable<any>,
507 action: (id: number | number[]) => Observable<any>,
508 taskWrapped: boolean = false,
509 childFormGroup?: CdFormGroup,
510 childFormGroupTemplate?: TemplateRef<any>
512 check(this.getSelectedOsdIds()).subscribe((result) => {
513 const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
515 actionDescription: actionDescription,
516 itemDescription: itemDescription,
517 bodyTemplate: this.criticalConfirmationTpl,
519 safeToPerform: result[checkKey],
520 message: result.message,
521 actionDescription: templateItemDescription,
522 osdIds: this.getSelectedOsdIds()
524 childFormGroup: childFormGroup,
525 childFormGroupTemplate: childFormGroupTemplate,
526 submitAction: () => {
527 const observable = observableForkJoin(
528 this.getSelectedOsdIds().map((osd: any) => action.call(this.osdService, osd))
531 observable.subscribe(
537 () => modalRef.hide()
540 observable.subscribe(
545 () => modalRef.hide()
554 configureQosParamsAction() {
555 this.bsModalRef = this.modalService.show(OsdRecvSpeedModalComponent, {});
558 configurePgScrubAction() {
559 this.bsModalRef = this.modalService.show(OsdPgScrubModalComponent, { class: 'modal-lg' });