]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; |
2 | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill'; | |
4 | import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; | |
5 | import { Observable } from 'rxjs'; | |
6 | ||
7 | import { OsdService } from '../../../../shared/api/osd.service'; | |
8 | import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component'; | |
9 | import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; | |
10 | import { TableComponent } from '../../../../shared/datatable/table/table.component'; | |
11 | import { CellTemplate } from '../../../../shared/enum/cell-template.enum'; | |
12 | import { CdTableAction } from '../../../../shared/models/cd-table-action'; | |
13 | import { CdTableColumn } from '../../../../shared/models/cd-table-column'; | |
14 | import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; | |
15 | import { Permissions } from '../../../../shared/models/permissions'; | |
16 | import { DimlessBinaryPipe } from '../../../../shared/pipes/dimless-binary.pipe'; | |
17 | import { AuthStorageService } from '../../../../shared/services/auth-storage.service'; | |
18 | import { OsdFlagsModalComponent } from '../osd-flags-modal/osd-flags-modal.component'; | |
81eedcae | 19 | import { OsdPgScrubModalComponent } from '../osd-pg-scrub-modal/osd-pg-scrub-modal.component'; |
11fdf7f2 TL |
20 | import { OsdRecvSpeedModalComponent } from '../osd-recv-speed-modal/osd-recv-speed-modal.component'; |
21 | import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component'; | |
22 | import { 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 | }) | |
29 | export 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 | } |