]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/OSD.js
update shipped appliance info index
[pve-manager.git] / www / manager6 / ceph / OSD.js
1 Ext.define('PVE.CephCreateOsd', {
2 extend: 'Proxmox.window.Edit',
3 xtype: 'pveCephCreateOsd',
4
5 subject: 'Ceph OSD',
6
7 showProgress: true,
8
9 onlineHelp: 'pve_ceph_osds',
10
11 initComponent: function() {
12 let me = this;
13
14 if (!me.nodename) {
15 throw "no node name specified";
16 }
17
18 me.isCreate = true;
19
20 Proxmox.Utils.API2Request({
21 url: `/nodes/${me.nodename}/ceph/crush`,
22 method: 'GET',
23 failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
24 success: function({ result: { data } }) {
25 let classes = [...new Set(
26 Array.from(
27 data.matchAll(/^device\s[0-9]*\sosd\.[0-9]*\sclass\s(.*)$/gim),
28 m => m[1],
29 ).filter(v => !['hdd', 'ssd', 'nvme'].includes(v)),
30 )].map(v => [v, v]);
31
32 if (classes.length) {
33 let kvField = me.down('field[name=crush-device-class]');
34 kvField.setComboItems([...kvField.comboItems, ...classes]);
35 }
36 },
37 });
38
39 Ext.applyIf(me, {
40 url: "/nodes/" + me.nodename + "/ceph/osd",
41 method: 'POST',
42 items: [
43 {
44 xtype: 'inputpanel',
45 onGetValues: function(values) {
46 Object.keys(values || {}).forEach(function(name) {
47 if (values[name] === '') {
48 delete values[name];
49 }
50 });
51
52 return values;
53 },
54 column1: [
55 {
56 xtype: 'pmxDiskSelector',
57 name: 'dev',
58 nodename: me.nodename,
59 diskType: 'unused',
60 includePartitions: true,
61 fieldLabel: gettext('Disk'),
62 allowBlank: false,
63 },
64 ],
65 column2: [
66 {
67 xtype: 'pmxDiskSelector',
68 name: 'db_dev',
69 nodename: me.nodename,
70 diskType: 'journal_disks',
71 includePartitions: true,
72 fieldLabel: gettext('DB Disk'),
73 value: '',
74 autoSelect: false,
75 allowBlank: true,
76 emptyText: 'use OSD disk',
77 listeners: {
78 change: function(field, val) {
79 me.down('field[name=db_dev_size]').setDisabled(!val);
80 },
81 },
82 },
83 {
84 xtype: 'numberfield',
85 name: 'db_dev_size',
86 fieldLabel: gettext('DB size') + ' (GiB)',
87 minValue: 1,
88 maxValue: 128*1024,
89 decimalPrecision: 2,
90 allowBlank: true,
91 disabled: true,
92 emptyText: gettext('Automatic'),
93 },
94 ],
95 advancedColumn1: [
96 {
97 xtype: 'proxmoxcheckbox',
98 name: 'encrypted',
99 fieldLabel: gettext('Encrypt OSD'),
100 },
101 {
102 xtype: 'proxmoxKVComboBox',
103 comboItems: [
104 ['hdd', 'HDD'],
105 ['ssd', 'SSD'],
106 ['nvme', 'NVMe'],
107 ],
108 name: 'crush-device-class',
109 nodename: me.nodename,
110 fieldLabel: gettext('Device Class'),
111 value: '',
112 autoSelect: false,
113 allowBlank: true,
114 editable: true,
115 emptyText: 'auto detect',
116 deleteEmpty: !me.isCreate,
117 },
118 ],
119 advancedColumn2: [
120 {
121 xtype: 'pmxDiskSelector',
122 name: 'wal_dev',
123 nodename: me.nodename,
124 diskType: 'journal_disks',
125 includePartitions: true,
126 fieldLabel: gettext('WAL Disk'),
127 value: '',
128 autoSelect: false,
129 allowBlank: true,
130 emptyText: 'use OSD/DB disk',
131 listeners: {
132 change: function(field, val) {
133 me.down('field[name=wal_dev_size]').setDisabled(!val);
134 },
135 },
136 },
137 {
138 xtype: 'numberfield',
139 name: 'wal_dev_size',
140 fieldLabel: gettext('WAL size') + ' (GiB)',
141 minValue: 0.5,
142 maxValue: 128*1024,
143 decimalPrecision: 2,
144 allowBlank: true,
145 disabled: true,
146 emptyText: gettext('Automatic'),
147 },
148 ],
149 },
150 {
151 xtype: 'displayfield',
152 padding: '5 0 0 0',
153 userCls: 'pmx-hint',
154 value: 'Note: Ceph is not compatible with disks backed by a hardware ' +
155 'RAID controller. For details see ' +
156 '<a target="_blank" href="' + Proxmox.Utils.get_help_link('chapter_pveceph') + '">the reference documentation</a>.',
157 },
158 ],
159 });
160
161 me.callParent();
162 },
163 });
164
165 Ext.define('PVE.CephRemoveOsd', {
166 extend: 'Proxmox.window.Edit',
167 alias: ['widget.pveCephRemoveOsd'],
168
169 isRemove: true,
170
171 showProgress: true,
172 method: 'DELETE',
173 items: [
174 {
175 xtype: 'proxmoxcheckbox',
176 name: 'cleanup',
177 checked: true,
178 labelWidth: 130,
179 fieldLabel: gettext('Cleanup Disks'),
180 },
181 {
182 xtype: 'displayfield',
183 name: 'osd-flag-hint',
184 userCls: 'pmx-hint',
185 value: gettext('Global flags limiting the self healing of Ceph are enabled.'),
186 hidden: true,
187 },
188 {
189 xtype: 'displayfield',
190 name: 'degraded-objects-hint',
191 userCls: 'pmx-hint',
192 value: gettext('Objects are degraded. Consider waiting until the cluster is healthy.'),
193 hidden: true,
194 },
195 ],
196 initComponent: function() {
197 let me = this;
198
199 if (!me.nodename) {
200 throw "no node name specified";
201 }
202 if (me.osdid === undefined || me.osdid < 0) {
203 throw "no osdid specified";
204 }
205
206 me.isCreate = true;
207
208 me.title = gettext('Destroy') + ': Ceph OSD osd.' + me.osdid.toString();
209
210 Ext.applyIf(me, {
211 url: "/nodes/" + me.nodename + "/ceph/osd/" + me.osdid.toString(),
212 });
213
214 me.callParent();
215
216 if (me.warnings.flags) {
217 me.down('field[name=osd-flag-hint]').setHidden(false);
218 }
219 if (me.warnings.degraded) {
220 me.down('field[name=degraded-objects-hint]').setHidden(false);
221 }
222 },
223 });
224
225 Ext.define('PVE.CephSetFlags', {
226 extend: 'Proxmox.window.Edit',
227 xtype: 'pveCephSetFlags',
228
229 showProgress: true,
230
231 width: 720,
232 layout: 'fit',
233
234 onlineHelp: 'pve_ceph_osds',
235 isCreate: true,
236 title: Ext.String.format(gettext('Manage {0}'), 'Global OSD Flags'),
237 submitText: gettext('Apply'),
238
239 items: [
240 {
241 xtype: 'inputpanel',
242 onGetValues: function(values) {
243 let me = this;
244 let val = {};
245 me.down('#flaggrid').getStore().each((rec) => {
246 val[rec.data.name] = rec.data.value ? 1 : 0;
247 });
248
249 return val;
250 },
251 items: [
252 {
253 xtype: 'grid',
254 itemId: 'flaggrid',
255 store: {
256 listeners: {
257 update: function() {
258 this.commitChanges();
259 },
260 },
261 },
262
263 columns: [
264 {
265 text: gettext('Enable'),
266 xtype: 'checkcolumn',
267 width: 75,
268 dataIndex: 'value',
269 },
270 {
271 text: 'Name',
272 dataIndex: 'name',
273 },
274 {
275 text: 'Description',
276 flex: 1,
277 dataIndex: 'description',
278 },
279 ],
280 },
281 ],
282 },
283 ],
284
285 initComponent: function() {
286 let me = this;
287
288 if (!me.nodename) {
289 throw "no node name specified";
290 }
291
292 Ext.applyIf(me, {
293 url: "/cluster/ceph/flags",
294 method: 'PUT',
295 });
296
297 me.callParent();
298
299 let grid = me.down('#flaggrid');
300 me.load({
301 success: function(response, options) {
302 let data = response.result.data;
303 grid.getStore().setData(data);
304 // re-align after store load, else the window is not centered
305 me.alignTo(Ext.getBody(), 'c-c');
306 },
307 });
308 },
309 });
310
311 Ext.define('PVE.node.CephOsdTree', {
312 extend: 'Ext.tree.Panel',
313 alias: ['widget.pveNodeCephOsdTree'],
314 onlineHelp: 'chapter_pveceph',
315
316 viewModel: {
317 data: {
318 nodename: '',
319 flags: [],
320 maxversion: '0',
321 mixedversions: false,
322 versions: {},
323 isOsd: false,
324 downOsd: false,
325 upOsd: false,
326 inOsd: false,
327 outOsd: false,
328 osdid: '',
329 osdhost: '',
330 },
331 },
332
333 controller: {
334 xclass: 'Ext.app.ViewController',
335
336 reload: function() {
337 let me = this;
338 let view = me.getView();
339 let vm = me.getViewModel();
340 let nodename = vm.get('nodename');
341 let sm = view.getSelectionModel();
342 Proxmox.Utils.API2Request({
343 url: "/nodes/" + nodename + "/ceph/osd",
344 waitMsgTarget: view,
345 method: 'GET',
346 failure: function(response, opts) {
347 let msg = response.htmlStatus;
348 PVE.Utils.showCephInstallOrMask(view, msg, nodename, win =>
349 view.mon(win, 'cephInstallWindowClosed', me.reload),
350 );
351 },
352 success: function(response, opts) {
353 let data = response.result.data;
354 let selected = view.getSelection();
355 let name;
356 if (selected.length) {
357 name = selected[0].data.name;
358 }
359 data.versions = data.versions || {};
360 vm.set('versions', data.versions);
361 // extract max version
362 let maxversion = "0";
363 let mixedversions = false;
364 let traverse;
365 traverse = function(node, fn) {
366 fn(node);
367 if (Array.isArray(node.children)) {
368 node.children.forEach(c => { traverse(c, fn); });
369 }
370 };
371 traverse(data.root, node => {
372 // compatibility for old api call
373 if (node.type === 'host' && !node.version) {
374 node.version = data.versions[node.name];
375 }
376
377 if (node.version === undefined) {
378 return;
379 }
380
381 if (PVE.Utils.compare_ceph_versions(node.version, maxversion) !== 0 && maxversion !== "0") {
382 mixedversions = true;
383 }
384
385 if (PVE.Utils.compare_ceph_versions(node.version, maxversion) > 0) {
386 maxversion = node.version;
387 }
388 });
389 vm.set('maxversion', maxversion);
390 vm.set('mixedversions', mixedversions);
391 sm.deselectAll();
392 view.setRootNode(data.root);
393 view.expandAll();
394 if (name) {
395 let node = view.getRootNode().findChild('name', name, true);
396 if (node) {
397 view.setSelection([node]);
398 }
399 }
400
401 let flags = data.flags.split(',');
402 vm.set('flags', flags);
403 },
404 });
405 },
406
407 osd_cmd: function(comp) {
408 let me = this;
409 let vm = this.getViewModel();
410 let cmd = comp.cmd;
411 let params = comp.params || {};
412 let osdid = vm.get('osdid');
413
414 let doRequest = function() {
415 let targetnode = vm.get('osdhost');
416 // cmds not node specific and need to work if the OSD node is down
417 if (['in', 'out'].includes(cmd)) {
418 targetnode = vm.get('nodename');
419 }
420 Proxmox.Utils.API2Request({
421 url: `/nodes/${targetnode}/ceph/osd/${osdid}/${cmd}`,
422 waitMsgTarget: me.getView(),
423 method: 'POST',
424 params: params,
425 success: () => { me.reload(); },
426 failure: function(response, opts) {
427 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
428 },
429 });
430 };
431
432 if (cmd === 'scrub') {
433 Ext.MessageBox.defaultButton = params.deep === 1 ? 2 : 1;
434 Ext.Msg.show({
435 title: gettext('Confirm'),
436 icon: params.deep === 1 ? Ext.Msg.WARNING : Ext.Msg.QUESTION,
437 msg: params.deep !== 1
438 ? Ext.String.format(gettext("Scrub OSD.{0}"), osdid)
439 : Ext.String.format(gettext("Deep Scrub OSD.{0}"), osdid) +
440 "<br>Caution: This can reduce performance while it is running.",
441 buttons: Ext.Msg.YESNO,
442 callback: function(btn) {
443 if (btn !== 'yes') {
444 return;
445 }
446 doRequest();
447 },
448 });
449 } else {
450 doRequest();
451 }
452 },
453
454 create_osd: function() {
455 let me = this;
456 let vm = this.getViewModel();
457 Ext.create('PVE.CephCreateOsd', {
458 nodename: vm.get('nodename'),
459 taskDone: () => { me.reload(); },
460 }).show();
461 },
462
463 destroy_osd: async function() {
464 let me = this;
465 let vm = this.getViewModel();
466
467 let warnings = {
468 flags: false,
469 degraded: false,
470 };
471
472 let flagsPromise = Proxmox.Async.api2({
473 url: `/cluster/ceph/flags`,
474 method: 'GET',
475 });
476
477 let statusPromise = Proxmox.Async.api2({
478 url: `/cluster/ceph/status`,
479 method: 'GET',
480 });
481
482 me.getView().mask(gettext('Loading...'));
483
484 try {
485 let result = await Promise.all([flagsPromise, statusPromise]);
486
487 let flagsData = result[0].result.data;
488 let statusData = result[1].result.data;
489
490 let flags = Array.from(
491 flagsData.filter(v => v.value),
492 v => v.name,
493 ).filter(v => ['norebalance', 'norecover', 'noout'].includes(v));
494
495 if (flags.length) {
496 warnings.flags = true;
497 }
498 if (Object.keys(statusData.pgmap).includes('degraded_objects')) {
499 warnings.degraded = true;
500 }
501 } catch (error) {
502 Ext.Msg.alert(gettext('Error'), error.htmlStatus);
503 me.getView().unmask();
504 return;
505 }
506
507 me.getView().unmask();
508 Ext.create('PVE.CephRemoveOsd', {
509 nodename: vm.get('osdhost'),
510 osdid: vm.get('osdid'),
511 warnings: warnings,
512 taskDone: () => { me.reload(); },
513 autoShow: true,
514 });
515 },
516
517 set_flags: function() {
518 let me = this;
519 let vm = this.getViewModel();
520 Ext.create('PVE.CephSetFlags', {
521 nodename: vm.get('nodename'),
522 taskDone: () => { me.reload(); },
523 }).show();
524 },
525
526 service_cmd: function(comp) {
527 let me = this;
528 let vm = this.getViewModel();
529 let cmd = comp.cmd || comp;
530
531 let doRequest = function() {
532 Proxmox.Utils.API2Request({
533 url: `/nodes/${vm.get('osdhost')}/ceph/${cmd}`,
534 params: { service: "osd." + vm.get('osdid') },
535 waitMsgTarget: me.getView(),
536 method: 'POST',
537 success: function(response, options) {
538 let upid = response.result.data;
539 let win = Ext.create('Proxmox.window.TaskProgress', {
540 upid: upid,
541 taskDone: () => { me.reload(); },
542 });
543 win.show();
544 },
545 failure: function(response, opts) {
546 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
547 },
548 });
549 };
550
551 if (cmd === "stop") {
552 Proxmox.Utils.API2Request({
553 url: `/nodes/${vm.get('osdhost')}/ceph/cmd-safety`,
554 params: {
555 service: 'osd',
556 id: vm.get('osdid'),
557 action: 'stop',
558 },
559 waitMsgTarget: me.getView(),
560 method: 'GET',
561 success: function({ result: { data } }) {
562 if (!data.safe) {
563 Ext.Msg.show({
564 title: gettext('Warning'),
565 message: data.status,
566 icon: Ext.Msg.WARNING,
567 buttons: Ext.Msg.OKCANCEL,
568 buttonText: { ok: gettext('Stop OSD') },
569 fn: function(selection) {
570 if (selection === 'ok') {
571 doRequest();
572 }
573 },
574 });
575 } else {
576 doRequest();
577 }
578 },
579 failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
580 });
581 } else {
582 doRequest();
583 }
584 },
585
586 set_selection_status: function(tp, selection) {
587 if (selection.length < 1) {
588 return;
589 }
590 let rec = selection[0];
591 let vm = this.getViewModel();
592
593 let isOsd = rec.data.host && rec.data.type === 'osd' && rec.data.id >= 0;
594
595 vm.set('isOsd', isOsd);
596 vm.set('downOsd', isOsd && rec.data.status === 'down');
597 vm.set('upOsd', isOsd && rec.data.status !== 'down');
598 vm.set('inOsd', isOsd && rec.data.in);
599 vm.set('outOsd', isOsd && !rec.data.in);
600 vm.set('osdid', isOsd ? rec.data.id : undefined);
601 vm.set('osdhost', isOsd ? rec.data.host : undefined);
602 },
603
604 render_status: function(value, metaData, rec) {
605 if (!value) {
606 return value;
607 }
608 let inout = rec.data.in ? 'in' : 'out';
609 let updownicon = value === 'up' ? 'good fa-arrow-circle-up'
610 : 'critical fa-arrow-circle-down';
611
612 let inouticon = rec.data.in ? 'good fa-circle'
613 : 'warning fa-circle-o';
614
615 let text = value + ' <i class="fa ' + updownicon + '"></i> / ' +
616 inout + ' <i class="fa ' + inouticon + '"></i>';
617
618 return text;
619 },
620
621 render_wal: function(value, metaData, rec) {
622 if (!value &&
623 rec.data.osdtype === 'bluestore' &&
624 rec.data.type === 'osd') {
625 return 'N/A';
626 }
627 return value;
628 },
629
630 render_version: function(value, metadata, rec) {
631 let vm = this.getViewModel();
632 let versions = vm.get('versions');
633 let icon = "";
634 let version = value || "";
635 let maxversion = vm.get('maxversion');
636 if (value && PVE.Utils.compare_ceph_versions(value, maxversion) !== 0) {
637 let host_version = rec.parentNode?.data?.version || versions[rec.data.host] || "";
638 if (rec.data.type === 'host' || PVE.Utils.compare_ceph_versions(host_version, maxversion) !== 0) {
639 icon = PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE');
640 } else {
641 icon = PVE.Utils.get_ceph_icon_html('HEALTH_OLD');
642 }
643 } else if (value && vm.get('mixedversions')) {
644 icon = PVE.Utils.get_ceph_icon_html('HEALTH_OK');
645 }
646
647 return icon + version;
648 },
649
650 render_osd_val: function(value, metaData, rec) {
651 return rec.data.type === 'osd' ? value : '';
652 },
653 render_osd_weight: function(value, metaData, rec) {
654 if (rec.data.type !== 'osd') {
655 return '';
656 }
657 return Ext.util.Format.number(value, '0.00###');
658 },
659
660 render_osd_latency: function(value, metaData, rec) {
661 if (rec.data.type !== 'osd') {
662 return '';
663 }
664 let commit_ms = rec.data.commit_latency_ms,
665 apply_ms = rec.data.apply_latency_ms;
666 return apply_ms + ' / ' + commit_ms;
667 },
668
669 render_osd_size: function(value, metaData, rec) {
670 return this.render_osd_val(Proxmox.Utils.render_size(value), metaData, rec);
671 },
672
673 control: {
674 '#': {
675 selectionchange: 'set_selection_status',
676 },
677 },
678
679 init: function(view) {
680 let me = this;
681 let vm = this.getViewModel();
682
683 if (!view.pveSelNode.data.node) {
684 throw "no node name specified";
685 }
686
687 vm.set('nodename', view.pveSelNode.data.node);
688
689 me.callParent();
690 me.reload();
691 },
692 },
693
694 stateful: true,
695 stateId: 'grid-ceph-osd',
696 rootVisible: false,
697 useArrows: true,
698
699 columns: [
700 {
701 xtype: 'treecolumn',
702 text: 'Name',
703 dataIndex: 'name',
704 width: 150,
705 },
706 {
707 text: 'Type',
708 dataIndex: 'type',
709 hidden: true,
710 align: 'right',
711 width: 75,
712 },
713 {
714 text: gettext("Class"),
715 dataIndex: 'device_class',
716 align: 'right',
717 width: 75,
718 },
719 {
720 text: "OSD Type",
721 dataIndex: 'osdtype',
722 align: 'right',
723 width: 100,
724 },
725 {
726 text: "Bluestore Device",
727 dataIndex: 'blfsdev',
728 align: 'right',
729 width: 75,
730 hidden: true,
731 },
732 {
733 text: "DB Device",
734 dataIndex: 'dbdev',
735 align: 'right',
736 width: 75,
737 hidden: true,
738 },
739 {
740 text: "WAL Device",
741 dataIndex: 'waldev',
742 align: 'right',
743 renderer: 'render_wal',
744 width: 75,
745 hidden: true,
746 },
747 {
748 text: 'Status',
749 dataIndex: 'status',
750 align: 'right',
751 renderer: 'render_status',
752 width: 120,
753 },
754 {
755 text: gettext('Version'),
756 dataIndex: 'version',
757 align: 'right',
758 renderer: 'render_version',
759 },
760 {
761 text: 'weight',
762 dataIndex: 'crush_weight',
763 align: 'right',
764 renderer: 'render_osd_weight',
765 width: 90,
766 },
767 {
768 text: 'reweight',
769 dataIndex: 'reweight',
770 align: 'right',
771 renderer: 'render_osd_weight',
772 width: 90,
773 },
774 {
775 text: gettext('Used') + ' (%)',
776 dataIndex: 'percent_used',
777 align: 'right',
778 renderer: function(value, metaData, rec) {
779 if (rec.data.type !== 'osd') {
780 return '';
781 }
782 return Ext.util.Format.number(value, '0.00');
783 },
784 width: 100,
785 },
786 {
787 text: gettext('Total'),
788 dataIndex: 'total_space',
789 align: 'right',
790 renderer: 'render_osd_size',
791 width: 100,
792 },
793 {
794 text: 'Apply/Commit<br>Latency (ms)',
795 dataIndex: 'apply_latency_ms',
796 align: 'right',
797 renderer: 'render_osd_latency',
798 width: 120,
799 },
800 ],
801
802
803 tbar: {
804 items: [
805 {
806 text: gettext('Reload'),
807 iconCls: 'fa fa-refresh',
808 handler: 'reload',
809 },
810 '-',
811 {
812 text: gettext('Create') + ': OSD',
813 handler: 'create_osd',
814 },
815 {
816 text: Ext.String.format(gettext('Manage {0}'), 'Global Flags'),
817 handler: 'set_flags',
818 },
819 '->',
820 {
821 xtype: 'tbtext',
822 data: {
823 osd: undefined,
824 },
825 bind: {
826 data: {
827 osd: "{osdid}",
828 },
829 },
830 tpl: [
831 '<tpl if="osd">',
832 'osd.{osd}:',
833 '<tpl else>',
834 gettext('No OSD selected'),
835 '</tpl>',
836 ],
837 },
838 {
839 text: gettext('Start'),
840 iconCls: 'fa fa-play',
841 disabled: true,
842 bind: {
843 disabled: '{!downOsd}',
844 },
845 cmd: 'start',
846 handler: 'service_cmd',
847 },
848 {
849 text: gettext('Stop'),
850 iconCls: 'fa fa-stop',
851 disabled: true,
852 bind: {
853 disabled: '{!upOsd}',
854 },
855 cmd: 'stop',
856 handler: 'service_cmd',
857 },
858 {
859 text: gettext('Restart'),
860 iconCls: 'fa fa-refresh',
861 disabled: true,
862 bind: {
863 disabled: '{!upOsd}',
864 },
865 cmd: 'restart',
866 handler: 'service_cmd',
867 },
868 '-',
869 {
870 text: 'Out',
871 iconCls: 'fa fa-circle-o',
872 disabled: true,
873 bind: {
874 disabled: '{!inOsd}',
875 },
876 cmd: 'out',
877 handler: 'osd_cmd',
878 },
879 {
880 text: 'In',
881 iconCls: 'fa fa-circle',
882 disabled: true,
883 bind: {
884 disabled: '{!outOsd}',
885 },
886 cmd: 'in',
887 handler: 'osd_cmd',
888 },
889 '-',
890 {
891 text: gettext('More'),
892 iconCls: 'fa fa-bars',
893 disabled: true,
894 bind: {
895 disabled: '{!isOsd}',
896 },
897 menu: [
898 {
899 text: gettext('Scrub'),
900 iconCls: 'fa fa-shower',
901 cmd: 'scrub',
902 handler: 'osd_cmd',
903 },
904 {
905 text: gettext('Deep Scrub'),
906 iconCls: 'fa fa-bath',
907 cmd: 'scrub',
908 params: {
909 deep: 1,
910 },
911 handler: 'osd_cmd',
912 },
913 {
914 text: gettext('Destroy'),
915 itemId: 'remove',
916 iconCls: 'fa fa-fw fa-trash-o',
917 bind: {
918 disabled: '{!downOsd}',
919 },
920 handler: 'destroy_osd',
921 },
922 ],
923 },
924 ],
925 },
926
927 fields: [
928 'name', 'type', 'status', 'host', 'in', 'id',
929 { type: 'number', name: 'reweight' },
930 { type: 'number', name: 'percent_used' },
931 { type: 'integer', name: 'bytes_used' },
932 { type: 'integer', name: 'total_space' },
933 { type: 'integer', name: 'apply_latency_ms' },
934 { type: 'integer', name: 'commit_latency_ms' },
935 { type: 'string', name: 'device_class' },
936 { type: 'string', name: 'osdtype' },
937 { type: 'string', name: 'blfsdev' },
938 { type: 'string', name: 'dbdev' },
939 { type: 'string', name: 'waldev' },
940 {
941 type: 'string', name: 'version', calculate: function(data) {
942 return PVE.Utils.parse_ceph_version(data);
943 },
944 },
945 {
946 type: 'string', name: 'iconCls', calculate: function(data) {
947 let iconMap = {
948 host: 'fa-building',
949 osd: 'fa-hdd-o',
950 root: 'fa-server',
951 };
952 return 'fa x-fa-tree ' + iconMap[data.type];
953 },
954 },
955 { type: 'number', name: 'crush_weight' },
956 ],
957 });