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