]> 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: use download.ceph.com to download source tar ball
[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';
19import { OsdRecvSpeedModalComponent } from '../osd-recv-speed-modal/osd-recv-speed-modal.component';
20import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component';
21import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
22
23@Component({
24 selector: 'cd-osd-list',
25 templateUrl: './osd-list.component.html',
26 styleUrls: ['./osd-list.component.scss']
27})
28export class OsdListComponent implements OnInit {
29 @ViewChild('statusColor')
30 statusColor: TemplateRef<any>;
31 @ViewChild('osdUsageTpl')
32 osdUsageTpl: TemplateRef<any>;
33 @ViewChild('markOsdConfirmationTpl')
34 markOsdConfirmationTpl: TemplateRef<any>;
35 @ViewChild('criticalConfirmationTpl')
36 criticalConfirmationTpl: TemplateRef<any>;
37 @ViewChild(TableComponent)
38 tableComponent: TableComponent;
39 @ViewChild('reweightBodyTpl')
40 reweightBodyTpl: TemplateRef<any>;
41 @ViewChild('safeToDestroyBodyTpl')
42 safeToDestroyBodyTpl: TemplateRef<any>;
43
44 permissions: Permissions;
45 tableActions: CdTableAction[];
46 bsModalRef: BsModalRef;
47 columns: CdTableColumn[];
48
49 osds = [];
50 selection = new CdTableSelection();
51
52 protected static collectStates(osd) {
53 return [osd['in'] ? 'in' : 'out', osd['up'] ? 'up' : 'down'];
54 }
55
56 constructor(
57 private authStorageService: AuthStorageService,
58 private osdService: OsdService,
59 private dimlessBinaryPipe: DimlessBinaryPipe,
60 private modalService: BsModalService,
61 private i18n: I18n
62 ) {
63 this.permissions = this.authStorageService.getPermissions();
64 this.tableActions = [
65 {
66 name: this.i18n('Scrub'),
67 permission: 'update',
68 icon: 'fa-stethoscope',
69 click: () => this.scrubAction(false),
70 disable: () => !this.hasOsdSelected
71 },
72 {
73 name: this.i18n('Deep Scrub'),
74 permission: 'update',
75 icon: 'fa-cog',
76 click: () => this.scrubAction(true),
77 disable: () => !this.hasOsdSelected
78 },
79 {
80 name: this.i18n('Reweight'),
81 permission: 'update',
82 click: () => this.reweight(),
83 disable: () => !this.hasOsdSelected,
84 icon: 'fa-balance-scale'
85 },
86 {
87 name: this.i18n('Mark Out'),
88 permission: 'update',
89 click: () => this.showConfirmationModal(this.i18n('out'), this.osdService.markOut),
90 disable: () => this.isNotSelectedOrInState('out'),
91 icon: 'fa-arrow-left'
92 },
93 {
94 name: this.i18n('Mark In'),
95 permission: 'update',
96 click: () => this.showConfirmationModal(this.i18n('in'), this.osdService.markIn),
97 disable: () => this.isNotSelectedOrInState('in'),
98 icon: 'fa-arrow-right'
99 },
100 {
101 name: this.i18n('Mark Down'),
102 permission: 'update',
103 click: () => this.showConfirmationModal(this.i18n('down'), this.osdService.markDown),
104 disable: () => this.isNotSelectedOrInState('down'),
105 icon: 'fa-arrow-down'
106 },
107 {
108 name: this.i18n('Mark Lost'),
109 permission: 'delete',
110 click: () =>
111 this.showCriticalConfirmationModal(
112 this.i18n('Mark'),
113 this.i18n('OSD lost'),
114 this.i18n('marked lost'),
115 this.osdService.markLost
116 ),
117 disable: () => this.isNotSelectedOrInState('up'),
118 icon: 'fa-unlink'
119 },
120 {
121 name: this.i18n('Purge'),
122 permission: 'delete',
123 click: () =>
124 this.showCriticalConfirmationModal(
125 this.i18n('Purge'),
126 this.i18n('OSD'),
127 this.i18n('purged'),
128 this.osdService.purge
129 ),
130 disable: () => this.isNotSelectedOrInState('up'),
131 icon: 'fa-eraser'
132 },
133 {
134 name: this.i18n('Destroy'),
135 permission: 'delete',
136 click: () =>
137 this.showCriticalConfirmationModal(
138 this.i18n('destroy'),
139 this.i18n('OSD'),
140 this.i18n('destroyed'),
141 this.osdService.destroy
142 ),
143 disable: () => this.isNotSelectedOrInState('up'),
144 icon: 'fa-remove'
145 }
146 ];
147 }
148
149 ngOnInit() {
150 this.columns = [
151 { prop: 'host.name', name: this.i18n('Host') },
152 { prop: 'id', name: this.i18n('ID'), cellTransformation: CellTemplate.bold },
153 { prop: 'collectedStates', name: this.i18n('Status'), cellTemplate: this.statusColor },
154 { prop: 'stats.numpg', name: this.i18n('PGs') },
155 { prop: 'stats.stat_bytes', name: this.i18n('Size'), pipe: this.dimlessBinaryPipe },
156 { name: this.i18n('Usage'), cellTemplate: this.osdUsageTpl },
157 {
158 prop: 'stats_history.out_bytes',
159 name: this.i18n('Read bytes'),
160 cellTransformation: CellTemplate.sparkline
161 },
162 {
163 prop: 'stats_history.in_bytes',
164 name: this.i18n('Writes bytes'),
165 cellTransformation: CellTemplate.sparkline
166 },
167 {
168 prop: 'stats.op_r',
169 name: this.i18n('Read ops'),
170 cellTransformation: CellTemplate.perSecond
171 },
172 {
173 prop: 'stats.op_w',
174 name: this.i18n('Write ops'),
175 cellTransformation: CellTemplate.perSecond
176 }
177 ];
178 }
179
180 get hasOsdSelected() {
181 if (this.selection.hasSelection) {
182 const osdId = this.selection.first().id;
183 const osd = this.osds.filter((o) => o.id === osdId).pop();
184 return !!osd;
185 }
186 return false;
187 }
188
189 updateSelection(selection: CdTableSelection) {
190 this.selection = selection;
191 }
192
193 /**
194 * Returns true if no row is selected or if the selected row is in the given
195 * state. Useful for deactivating the corresponding menu entry.
196 */
197 isNotSelectedOrInState(state: 'in' | 'up' | 'down' | 'out'): boolean {
198 if (!this.hasOsdSelected) {
199 return true;
200 }
201
202 const osdId = this.selection.first().id;
203 const osd = this.osds.filter((o) => o.id === osdId).pop();
204
205 if (!osd) {
206 // `osd` is undefined if the selected OSD has been removed.
207 return true;
208 }
209
210 switch (state) {
211 case 'in':
212 return osd.in === 1;
213 case 'out':
214 return osd.in !== 1;
215 case 'down':
216 return osd.up !== 1;
217 case 'up':
218 return osd.up === 1;
219 }
220 }
221
222 getOsdList() {
223 this.osdService.getList().subscribe((data: any[]) => {
224 this.osds = data;
225 data.map((osd) => {
226 osd.collectedStates = OsdListComponent.collectStates(osd);
227 osd.stats_history.out_bytes = osd.stats_history.op_out_bytes.map((i) => i[1]);
228 osd.stats_history.in_bytes = osd.stats_history.op_in_bytes.map((i) => i[1]);
229 osd.cdIsBinary = true;
230 return osd;
231 });
232 });
233 }
234
235 scrubAction(deep) {
236 if (!this.hasOsdSelected) {
237 return;
238 }
239
240 const initialState = {
241 selected: this.tableComponent.selection.selected,
242 deep: deep
243 };
244
245 this.bsModalRef = this.modalService.show(OsdScrubModalComponent, { initialState });
246 }
247
248 configureClusterAction() {
249 this.bsModalRef = this.modalService.show(OsdFlagsModalComponent, {});
250 }
251
252 showConfirmationModal(markAction: string, onSubmit: (id: number) => Observable<any>) {
253 this.bsModalRef = this.modalService.show(ConfirmationModalComponent, {
254 initialState: {
255 titleText: this.i18n('Mark OSD {{markAction}}', { markAction: markAction }),
256 buttonText: this.i18n('Mark {{markAction}}', { markAction: markAction }),
257 bodyTpl: this.markOsdConfirmationTpl,
258 bodyContext: {
259 markActionDescription: markAction
260 },
261 onSubmit: () => {
262 onSubmit
263 .call(this.osdService, this.selection.first().id)
264 .subscribe(() => this.bsModalRef.hide());
265 }
266 }
267 });
268 }
269
270 reweight() {
271 const selectedOsd = this.osds.filter((o) => o.id === this.selection.first().id).pop();
272 this.modalService.show(OsdReweightModalComponent, {
273 initialState: {
274 currentWeight: selectedOsd.weight,
275 osdId: selectedOsd.id
276 }
277 });
278 }
279
280 showCriticalConfirmationModal(
281 actionDescription: string,
282 itemDescription: string,
283 templateItemDescription: string,
284 action: (id: number) => Observable<any>
285 ): void {
286 this.osdService.safeToDestroy(this.selection.first().id).subscribe((result) => {
287 const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
288 initialState: {
289 actionDescription: actionDescription,
290 itemDescription: itemDescription,
291 bodyTemplate: this.criticalConfirmationTpl,
292 bodyContext: {
293 result: result,
294 actionDescription: templateItemDescription
295 },
296 submitAction: () => {
297 action
298 .call(this.osdService, this.selection.first().id)
299 .subscribe(() => modalRef.hide());
300 }
301 }
302 });
303 });
304 }
305
306 configureQosParamsAction() {
307 this.bsModalRef = this.modalService.show(OsdRecvSpeedModalComponent, {});
308 }
309}