]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/osd/osd-list/osd-list.component.ts
buildsys: auto-determine current version for makefile
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / cluster / osd / osd-list / osd-list.component.ts
CommitLineData
11fdf7f2
TL
1import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
2
3import { I18n } from '@ngx-translate/i18n-polyfill';
4import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
5import { Observable } from 'rxjs';
6
7import { OsdService } from '../../../../shared/api/osd.service';
8import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component';
9import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
10import { TableComponent } from '../../../../shared/datatable/table/table.component';
11import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
12import { CdTableAction } from '../../../../shared/models/cd-table-action';
13import { CdTableColumn } from '../../../../shared/models/cd-table-column';
14import { CdTableSelection } from '../../../../shared/models/cd-table-selection';
15import { Permissions } from '../../../../shared/models/permissions';
16import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe';
17import { AuthStorageService } from '../../../../shared/services/auth-storage.service';
18import { OsdFlagsModalComponent } from '../osd-flags-modal/osd-flags-modal.component';
81eedcae 19import { OsdPgScrubModalComponent } from '../osd-pg-scrub-modal/osd-pg-scrub-modal.component';
11fdf7f2
TL
20import { OsdRecvSpeedModalComponent } from '../osd-recv-speed-modal/osd-recv-speed-modal.component';
21import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component';
22import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
23
24@Component({
25 selector: 'cd-osd-list',
26 templateUrl: './osd-list.component.html',
27 styleUrls: ['./osd-list.component.scss']
28})
29export class OsdListComponent implements OnInit {
30 @ViewChild('statusColor')
31 statusColor: TemplateRef<any>;
32 @ViewChild('osdUsageTpl')
33 osdUsageTpl: TemplateRef<any>;
34 @ViewChild('markOsdConfirmationTpl')
35 markOsdConfirmationTpl: TemplateRef<any>;
36 @ViewChild('criticalConfirmationTpl')
37 criticalConfirmationTpl: TemplateRef<any>;
38 @ViewChild(TableComponent)
39 tableComponent: TableComponent;
40 @ViewChild('reweightBodyTpl')
41 reweightBodyTpl: TemplateRef<any>;
42 @ViewChild('safeToDestroyBodyTpl')
43 safeToDestroyBodyTpl: TemplateRef<any>;
44
45 permissions: Permissions;
46 tableActions: CdTableAction[];
47 bsModalRef: BsModalRef;
48 columns: CdTableColumn[];
81eedcae 49 advancedTableActions: any[];
11fdf7f2
TL
50
51 osds = [];
52 selection = new CdTableSelection();
53
54 protected static collectStates(osd) {
55 return [osd['in'] ? 'in' : 'out', osd['up'] ? 'up' : 'down'];
56 }
57
58 constructor(
59 private authStorageService: AuthStorageService,
60 private osdService: OsdService,
61 private dimlessBinaryPipe: DimlessBinaryPipe,
62 private modalService: BsModalService,
63 private i18n: I18n
64 ) {
65 this.permissions = this.authStorageService.getPermissions();
66 this.tableActions = [
67 {
68 name: this.i18n('Scrub'),
69 permission: 'update',
70 icon: 'fa-stethoscope',
71 click: () => this.scrubAction(false),
72 disable: () => !this.hasOsdSelected
73 },
74 {
75 name: this.i18n('Deep Scrub'),
76 permission: 'update',
77 icon: 'fa-cog',
78 click: () => this.scrubAction(true),
79 disable: () => !this.hasOsdSelected
80 },
81 {
82 name: this.i18n('Reweight'),
83 permission: 'update',
84 click: () => this.reweight(),
85 disable: () => !this.hasOsdSelected,
86 icon: 'fa-balance-scale'
87 },
88 {
89 name: this.i18n('Mark Out'),
90 permission: 'update',
91 click: () => this.showConfirmationModal(this.i18n('out'), this.osdService.markOut),
92 disable: () => this.isNotSelectedOrInState('out'),
93 icon: 'fa-arrow-left'
94 },
95 {
96 name: this.i18n('Mark In'),
97 permission: 'update',
98 click: () => this.showConfirmationModal(this.i18n('in'), this.osdService.markIn),
99 disable: () => this.isNotSelectedOrInState('in'),
100 icon: 'fa-arrow-right'
101 },
102 {
103 name: this.i18n('Mark Down'),
104 permission: 'update',
105 click: () => this.showConfirmationModal(this.i18n('down'), this.osdService.markDown),
106 disable: () => this.isNotSelectedOrInState('down'),
107 icon: 'fa-arrow-down'
108 },
109 {
110 name: this.i18n('Mark Lost'),
111 permission: 'delete',
112 click: () =>
113 this.showCriticalConfirmationModal(
114 this.i18n('Mark'),
115 this.i18n('OSD lost'),
116 this.i18n('marked lost'),
117 this.osdService.markLost
118 ),
119 disable: () => this.isNotSelectedOrInState('up'),
120 icon: 'fa-unlink'
121 },
122 {
123 name: this.i18n('Purge'),
124 permission: 'delete',
125 click: () =>
126 this.showCriticalConfirmationModal(
127 this.i18n('Purge'),
128 this.i18n('OSD'),
129 this.i18n('purged'),
130 this.osdService.purge
131 ),
132 disable: () => this.isNotSelectedOrInState('up'),
133 icon: 'fa-eraser'
134 },
135 {
136 name: this.i18n('Destroy'),
137 permission: 'delete',
138 click: () =>
139 this.showCriticalConfirmationModal(
140 this.i18n('destroy'),
141 this.i18n('OSD'),
142 this.i18n('destroyed'),
143 this.osdService.destroy
144 ),
145 disable: () => this.isNotSelectedOrInState('up'),
146 icon: 'fa-remove'
147 }
148 ];
81eedcae
TL
149 this.advancedTableActions = [
150 {
151 name: this.i18n('Cluster-wide Flags'),
152 icon: 'fa-flag',
153 click: () => this.configureFlagsAction(),
154 permission: this.permissions.osd.read
155 },
156 {
157 name: this.i18n('Cluster-wide Recovery Priority'),
158 icon: 'fa-cog',
159 click: () => this.configureQosParamsAction(),
160 permission: this.permissions.configOpt.read
161 },
162 {
163 name: this.i18n('PG scrub'),
164 icon: 'fa-stethoscope',
165 click: () => this.configurePgScrubAction(),
166 permission: this.permissions.configOpt.read
167 }
168 ];
11fdf7f2
TL
169 }
170
171 ngOnInit() {
172 this.columns = [
173 { prop: 'host.name', name: this.i18n('Host') },
174 { prop: 'id', name: this.i18n('ID'), cellTransformation: CellTemplate.bold },
175 { prop: 'collectedStates', name: this.i18n('Status'), cellTemplate: this.statusColor },
176 { prop: 'stats.numpg', name: this.i18n('PGs') },
177 { prop: 'stats.stat_bytes', name: this.i18n('Size'), pipe: this.dimlessBinaryPipe },
81eedcae 178 { prop: 'stats.usage', name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
11fdf7f2
TL
179 {
180 prop: 'stats_history.out_bytes',
181 name: this.i18n('Read bytes'),
182 cellTransformation: CellTemplate.sparkline
183 },
184 {
185 prop: 'stats_history.in_bytes',
186 name: this.i18n('Writes bytes'),
187 cellTransformation: CellTemplate.sparkline
188 },
189 {
190 prop: 'stats.op_r',
191 name: this.i18n('Read ops'),
192 cellTransformation: CellTemplate.perSecond
193 },
194 {
195 prop: 'stats.op_w',
196 name: this.i18n('Write ops'),
197 cellTransformation: CellTemplate.perSecond
198 }
199 ];
81eedcae
TL
200
201 this.removeActionsWithNoPermissions();
11fdf7f2
TL
202 }
203
204 get hasOsdSelected() {
205 if (this.selection.hasSelection) {
206 const osdId = this.selection.first().id;
207 const osd = this.osds.filter((o) => o.id === osdId).pop();
208 return !!osd;
209 }
210 return false;
211 }
212
213 updateSelection(selection: CdTableSelection) {
214 this.selection = selection;
215 }
216
217 /**
218 * Returns true if no row is selected or if the selected row is in the given
219 * state. Useful for deactivating the corresponding menu entry.
220 */
221 isNotSelectedOrInState(state: 'in' | 'up' | 'down' | 'out'): boolean {
222 if (!this.hasOsdSelected) {
223 return true;
224 }
225
226 const osdId = this.selection.first().id;
227 const osd = this.osds.filter((o) => o.id === osdId).pop();
228
229 if (!osd) {
230 // `osd` is undefined if the selected OSD has been removed.
231 return true;
232 }
233
234 switch (state) {
235 case 'in':
236 return osd.in === 1;
237 case 'out':
238 return osd.in !== 1;
239 case 'down':
240 return osd.up !== 1;
241 case 'up':
242 return osd.up === 1;
243 }
244 }
245
246 getOsdList() {
247 this.osdService.getList().subscribe((data: any[]) => {
81eedcae 248 this.osds = data.map((osd) => {
11fdf7f2
TL
249 osd.collectedStates = OsdListComponent.collectStates(osd);
250 osd.stats_history.out_bytes = osd.stats_history.op_out_bytes.map((i) => i[1]);
251 osd.stats_history.in_bytes = osd.stats_history.op_in_bytes.map((i) => i[1]);
81eedcae 252 osd.stats.usage = osd.stats.stat_bytes_used / osd.stats.stat_bytes;
11fdf7f2
TL
253 osd.cdIsBinary = true;
254 return osd;
255 });
256 });
257 }
258
259 scrubAction(deep) {
260 if (!this.hasOsdSelected) {
261 return;
262 }
263
264 const initialState = {
265 selected: this.tableComponent.selection.selected,
266 deep: deep
267 };
268
269 this.bsModalRef = this.modalService.show(OsdScrubModalComponent, { initialState });
270 }
271
81eedcae 272 configureFlagsAction() {
11fdf7f2
TL
273 this.bsModalRef = this.modalService.show(OsdFlagsModalComponent, {});
274 }
275
276 showConfirmationModal(markAction: string, onSubmit: (id: number) => Observable<any>) {
277 this.bsModalRef = this.modalService.show(ConfirmationModalComponent, {
278 initialState: {
279 titleText: this.i18n('Mark OSD {{markAction}}', { markAction: markAction }),
280 buttonText: this.i18n('Mark {{markAction}}', { markAction: markAction }),
281 bodyTpl: this.markOsdConfirmationTpl,
282 bodyContext: {
283 markActionDescription: markAction
284 },
285 onSubmit: () => {
286 onSubmit
287 .call(this.osdService, this.selection.first().id)
288 .subscribe(() => this.bsModalRef.hide());
289 }
290 }
291 });
292 }
293
294 reweight() {
295 const selectedOsd = this.osds.filter((o) => o.id === this.selection.first().id).pop();
296 this.modalService.show(OsdReweightModalComponent, {
297 initialState: {
298 currentWeight: selectedOsd.weight,
299 osdId: selectedOsd.id
300 }
301 });
302 }
303
304 showCriticalConfirmationModal(
305 actionDescription: string,
306 itemDescription: string,
307 templateItemDescription: string,
308 action: (id: number) => Observable<any>
309 ): void {
310 this.osdService.safeToDestroy(this.selection.first().id).subscribe((result) => {
311 const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
312 initialState: {
313 actionDescription: actionDescription,
314 itemDescription: itemDescription,
315 bodyTemplate: this.criticalConfirmationTpl,
316 bodyContext: {
317 result: result,
318 actionDescription: templateItemDescription
319 },
320 submitAction: () => {
321 action
322 .call(this.osdService, this.selection.first().id)
323 .subscribe(() => modalRef.hide());
324 }
325 }
326 });
327 });
328 }
329
330 configureQosParamsAction() {
331 this.bsModalRef = this.modalService.show(OsdRecvSpeedModalComponent, {});
332 }
81eedcae
TL
333
334 configurePgScrubAction() {
335 this.bsModalRef = this.modalService.show(OsdPgScrubModalComponent, { class: 'modal-lg' });
336 }
337
338 /**
339 * Removes all actions from 'advancedTableActions' that need a permission the user doesn't have.
340 */
341 private removeActionsWithNoPermissions() {
342 if (!this.permissions) {
343 this.advancedTableActions = [];
344 return;
345 }
346
347 this.advancedTableActions = this.advancedTableActions.filter((action) => action.permission);
348 }
11fdf7f2 349}