]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/ceph/Pool.js
update shipped appliance info index
[pve-manager.git] / www / manager6 / ceph / Pool.js
CommitLineData
4618a869
AA
1Ext.define('PVE.CephPoolInputPanel', {
2 extend: 'Proxmox.panel.InputPanel',
3 xtype: 'pveCephPoolInputPanel',
4 mixins: ['Proxmox.Mixin.CBind'],
024be9c7 5
7244f4bf 6 showProgress: true,
35085f4a 7 onlineHelp: 'pve_ceph_pools',
7244f4bf 8
024be9c7 9 subject: 'Ceph Pool',
b555a412
AL
10
11 defaultSize: undefined,
12 defaultMinSize: undefined,
13
cfcb74ab
AL
14 controller: {
15 xclass: 'Ext.app.ViewController',
16
17 init: function(view) {
18 let vm = this.getViewModel();
19 vm.set('size', Number(view.defaultSize));
20 vm.set('minSize', Number(view.defaultMinSize));
21 },
22 sizeChange: function(field, val) {
23 let vm = this.getViewModel();
24 let minSize = Math.round(val / 2);
25 if (minSize > 1) {
26 vm.set('minSize', minSize);
27 }
28 vm.set('size', val); // bind does not work in a pmxDisplayEditField, update manually
29 },
30 },
31
32 viewModel: {
33 data: {
34 minSize: null,
35 size: null,
36 },
37 formulas: {
38 minSizeLabel: (get) => {
39 if (get('showMinSizeOneWarning') || get('showMinSizeHalfWarning')) {
40 return `${gettext('Min. Size')} <i class="fa fa-exclamation-triangle warning"></i>`;
41 }
42 return gettext('Min. Size');
43 },
44 showMinSizeOneWarning: (get) => get('minSize') === 1,
45 showMinSizeHalfWarning: (get) => {
46 let minSize = get('minSize');
47 let size = get('size');
48 if (minSize === 1) {
49 return false;
50 }
51 return minSize < (size / 2) && minSize !== size;
52 },
53 },
54 },
55
4618a869 56 column1: [
c449c89a 57 {
4618a869 58 xtype: 'pmxDisplayEditField',
c449c89a 59 fieldLabel: gettext('Name'),
4618a869
AA
60 cbind: {
61 editable: '{isCreate}',
62 value: '{pool_name}',
4618a869 63 },
c449c89a 64 name: 'name',
f6710aac 65 allowBlank: false,
c449c89a
DC
66 },
67 {
1a042025
AL
68 xtype: 'pmxDisplayEditField',
69 cbind: {
70 editable: '{!isErasure}',
71 },
c449c89a
DC
72 fieldLabel: gettext('Size'),
73 name: 'size',
1a042025
AL
74 editConfig: {
75 xtype: 'proxmoxintegerfield',
b555a412
AL
76 cbind: {
77 value: (get) => get('defaultSize'),
78 },
1a042025
AL
79 minValue: 2,
80 maxValue: 7,
81 allowBlank: false,
82 listeners: {
cfcb74ab 83 change: 'sizeChange',
4618a869
AA
84 },
85 },
86 },
87 ],
88 column2: [
89 {
90 xtype: 'proxmoxKVComboBox',
91 fieldLabel: 'PG Autoscale Mode',
92 name: 'pg_autoscale_mode',
93 comboItems: [
94 ['warn', 'warn'],
95 ['on', 'on'],
96 ['off', 'off'],
97 ],
5da1b6d0 98 value: 'on', // FIXME: check ceph version and only default to on on octopus and newer
4618a869
AA
99 allowBlank: false,
100 autoSelect: false,
101 labelWidth: 140,
c449c89a 102 },
4618a869
AA
103 {
104 xtype: 'proxmoxcheckbox',
105 fieldLabel: gettext('Add as Storage'),
106 cbind: {
107 value: '{isCreate}',
108 hidden: '{!isCreate}',
109 },
110 name: 'add_storages',
111 labelWidth: 140,
112 autoEl: {
113 tag: 'div',
114 'data-qtip': gettext('Add the new pool to the cluster storage configuration.'),
115 },
116 },
117 ],
118 advancedColumn1: [
c449c89a 119 {
bf96f60d 120 xtype: 'proxmoxintegerfield',
cfcb74ab
AL
121 bind: {
122 fieldLabel: '{minSizeLabel}',
123 value: '{minSize}',
124 },
c449c89a 125 name: 'min_size',
4618a869 126 cbind: {
b555a412
AL
127 value: (get) => get('defaultMinSize'),
128 minValue: (get) => {
129 if (Number(get('defaultMinSize')) === 1) {
130 return 1;
131 } else {
132 return get('isCreate') ? 2 : 1;
133 }
134 },
4618a869 135 },
eef1979c 136 maxValue: 7,
f6710aac 137 allowBlank: false,
4618a869
AA
138 },
139 {
140 xtype: 'displayfield',
cfcb74ab
AL
141 bind: {
142 hidden: '{!showMinSizeHalfWarning}',
143 },
144 hidden: true,
4618a869 145 userCls: 'pmx-hint',
7748b41f 146 value: gettext('min_size < size/2 can lead to data loss, incomplete PGs or unfound objects.'),
cfcb74ab
AL
147 },
148 {
149 xtype: 'displayfield',
150 bind: {
151 hidden: '{!showMinSizeOneWarning}',
152 },
4618a869 153 hidden: true,
cfcb74ab
AL
154 userCls: 'pmx-hint',
155 value: gettext('a min_size of 1 is not recommended and can lead to data loss'),
c449c89a
DC
156 },
157 {
1a042025 158 xtype: 'pmxDisplayEditField',
cd99757b 159 cbind: {
1a042025 160 editable: '{!isErasure}',
cd99757b
TL
161 nodename: '{nodename}',
162 isCreate: '{isCreate}',
163 },
1a042025
AL
164 fieldLabel: 'Crush Rule', // do not localize
165 name: 'crush_rule',
166 editConfig: {
167 xtype: 'pveCephRuleSelector',
168 allowBlank: false,
169 },
c449c89a
DC
170 },
171 {
bf96f60d 172 xtype: 'proxmoxintegerfield',
4618a869 173 fieldLabel: '# of PGs',
c449c89a 174 name: 'pg_num',
6ad70a2b 175 value: 128,
4618a869 176 minValue: 1,
c449c89a 177 maxValue: 32768,
4618a869
AA
178 allowBlank: false,
179 emptyText: 128,
180 },
181 ],
182 advancedColumn2: [
183 {
184 xtype: 'numberfield',
4d7acd65 185 fieldLabel: gettext('Target Ratio'),
4618a869 186 name: 'target_size_ratio',
4618a869
AA
187 minValue: 0,
188 decimalPrecision: 3,
23b316a3 189 allowBlank: true,
4618a869 190 emptyText: '0.0',
4d7acd65
TL
191 autoEl: {
192 tag: 'div',
193 'data-qtip': gettext('The ratio of storage amount this pool will consume compared to other pools with ratios. Used for auto-scaling.'),
194 },
5947248f
DC
195 },
196 {
18a5845e 197 xtype: 'pveSizeField',
4618a869 198 name: 'target_size',
4d7acd65 199 fieldLabel: gettext('Target Size'),
18a5845e 200 unit: 'GiB',
4618a869
AA
201 minValue: 0,
202 allowBlank: true,
86a5da83 203 allowZero: true,
4618a869 204 emptyText: '0',
d7eb3e22 205 emptyValue: 0,
4d7acd65
TL
206 autoEl: {
207 tag: 'div',
208 'data-qtip': gettext('The amount of data eventually stored in this pool. Used for auto-scaling.'),
209 },
4618a869
AA
210 },
211 {
212 xtype: 'displayfield',
213 userCls: 'pmx-hint',
4d7acd65 214 value: Ext.String.format(gettext('{0} takes precedence.'), gettext('Target Ratio')), // FIXME: tooltip?
f6710aac 215 },
ae793232
AA
216 {
217 xtype: 'proxmoxintegerfield',
218 fieldLabel: 'Min. # of PGs',
219 name: 'pg_num_min',
ae793232
AA
220 minValue: 0,
221 allowBlank: true,
222 emptyText: '0',
223 },
c449c89a 224 ],
024be9c7 225
4618a869
AA
226 onGetValues: function(values) {
227 Object.keys(values || {}).forEach(function(name) {
228 if (values[name] === '') {
229 delete values[name];
230 }
231 });
232
4618a869
AA
233 return values;
234 },
4618a869
AA
235});
236
519d9f3f 237Ext.define('PVE.Ceph.PoolEdit', {
4618a869
AA
238 extend: 'Proxmox.window.Edit',
239 alias: 'widget.pveCephPoolEdit',
4618a869
AA
240 mixins: ['Proxmox.Mixin.CBind'],
241
242 cbindData: {
243 pool_name: '',
244 isCreate: (cfg) => !cfg.pool_name,
b555a412
AL
245 defaultSize: undefined,
246 defaultMinSize: undefined,
4618a869
AA
247 },
248
249 cbind: {
250 autoLoad: get => !get('isCreate'),
251 url: get => get('isCreate')
b5bbcb4c
AL
252 ? `/nodes/${get('nodename')}/ceph/pool`
253 : `/nodes/${get('nodename')}/ceph/pool/${get('pool_name')}`,
254 loadUrl: get => `/nodes/${get('nodename')}/ceph/pool/${get('pool_name')}/status`,
4618a869
AA
255 method: get => get('isCreate') ? 'POST' : 'PUT',
256 },
257
cc0734fc
DC
258 showProgress: true,
259
4618a869
AA
260 subject: gettext('Ceph Pool'),
261
262 items: [{
263 xtype: 'pveCephPoolInputPanel',
264 cbind: {
265 nodename: '{nodename}',
266 pool_name: '{pool_name}',
1a042025 267 isErasure: '{isErasure}',
4618a869 268 isCreate: '{isCreate}',
b555a412
AL
269 defaultSize: '{defaultSize}',
270 defaultMinSize: '{defaultMinSize}',
4618a869
AA
271 },
272 }],
024be9c7
DM
273});
274
519d9f3f 275Ext.define('PVE.node.Ceph.PoolList', {
024be9c7 276 extend: 'Ext.grid.GridPanel',
9ad28182 277 alias: 'widget.pveNodeCephPoolList',
024be9c7 278
ba93a9c6 279 onlineHelp: 'chapter_pveceph',
5b7b4b76 280
361aafd0
DC
281 stateful: true,
282 stateId: 'grid-ceph-pools',
c449c89a 283 bufferedRenderer: false,
5b7b4b76 284
8058410f 285 features: [{ ftype: 'summary' }],
5b7b4b76 286
c449c89a 287 columns: [
79007cfc
AL
288 {
289 text: gettext('Pool #'),
290 minWidth: 70,
291 flex: 1,
292 align: 'right',
293 sortable: true,
294 dataIndex: 'pool',
295 },
c449c89a 296 {
11ce9e1a
AA
297 text: gettext('Name'),
298 minWidth: 120,
299 flex: 2,
c449c89a 300 sortable: true,
f6710aac 301 dataIndex: 'pool_name',
c449c89a 302 },
a73f844d
AL
303 {
304 text: gettext('Type'),
305 minWidth: 100,
306 flex: 1,
307 dataIndex: 'type',
308 hidden: true,
309 },
c449c89a 310 {
11ce9e1a
AA
311 text: gettext('Size') + '/min',
312 minWidth: 100,
313 flex: 1,
5b7b4b76 314 align: 'right',
1bd7bcdb 315 renderer: (v, meta, rec) => `${v}/${rec.data.min_size}`,
f6710aac 316 dataIndex: 'size',
c449c89a
DC
317 },
318 {
11ce9e1a
AA
319 text: '# of Placement Groups',
320 flex: 1,
0e0f861a 321 minWidth: 100,
11ce9e1a
AA
322 align: 'right',
323 dataIndex: 'pg_num',
c449c89a
DC
324 },
325 {
11ce9e1a
AA
326 text: gettext('Optimal # of PGs'),
327 flex: 1,
0e0f861a 328 minWidth: 100,
11ce9e1a
AA
329 align: 'right',
330 dataIndex: 'pg_num_final',
331 renderer: function(value, metaData) {
332 if (!value) {
333 value = '<i class="fa fa-info-circle faded"></i> n/a';
334 metaData.tdAttr = 'data-qtip="Needs pg_autoscaler module enabled."';
335 }
336 return value;
337 },
c9508b5d 338 },
ae793232
AA
339 {
340 text: gettext('Min. # of PGs'),
341 flex: 1,
0e0f861a 342 minWidth: 100,
ae793232
AA
343 align: 'right',
344 dataIndex: 'pg_num_min',
345 hidden: true,
346 },
c449c89a 347 {
0e0f861a 348 text: gettext('Target Ratio'),
11ce9e1a 349 flex: 1,
0e0f861a 350 minWidth: 100,
11ce9e1a
AA
351 align: 'right',
352 dataIndex: 'target_size_ratio',
353 renderer: Ext.util.Format.numberRenderer('0.0000'),
354 hidden: true,
355 },
356 {
357 text: gettext('Target Size'),
358 flex: 1,
0e0f861a 359 minWidth: 100,
11ce9e1a
AA
360 align: 'right',
361 dataIndex: 'target_size',
362 hidden: true,
363 renderer: function(v, metaData, rec) {
ad33cfd5 364 let value = Proxmox.Utils.render_size(v);
11ce9e1a
AA
365 if (rec.data.target_size_ratio > 0) {
366 value = '<i class="fa fa-info-circle faded"></i> ' + value;
367 metaData.tdAttr = 'data-qtip="Target Size Ratio takes precedence over Target Size."';
368 }
369 return value;
370 },
371 },
372 {
373 text: gettext('Autoscale Mode'),
374 flex: 1,
0e0f861a 375 minWidth: 100,
11ce9e1a
AA
376 align: 'right',
377 dataIndex: 'pg_autoscale_mode',
378 },
379 {
380 text: 'CRUSH Rule (ID)',
381 flex: 1,
382 align: 'right',
383 minWidth: 150,
0e0f861a 384 renderer: (v, meta, rec) => `${v} (${rec.data.crush_rule})`,
11ce9e1a
AA
385 dataIndex: 'crush_rule_name',
386 },
387 {
388 text: gettext('Used') + ' (%)',
389 flex: 1,
0e0f861a 390 minWidth: 150,
11ce9e1a
AA
391 sortable: true,
392 align: 'right',
393 dataIndex: 'bytes_used',
394 summaryType: 'sum',
1bd7bcdb 395 summaryRenderer: Proxmox.Utils.render_size,
11ce9e1a
AA
396 renderer: function(v, meta, rec) {
397 let percentage = Ext.util.Format.percent(rec.data.percent_used, '0.00');
1bd7bcdb
DC
398 let used = Proxmox.Utils.render_size(v);
399 return `${used} (${percentage})`;
11ce9e1a 400 },
f6710aac 401 },
c449c89a 402 ],
024be9c7
DM
403 initComponent: function() {
404 var me = this;
405
406 var nodename = me.pveSelNode.data.node;
407 if (!nodename) {
408 throw "no node name specified";
409 }
410
411 var sm = Ext.create('Ext.selection.RowModel', {});
412
0c7c0d6b 413 var rstore = Ext.create('Proxmox.data.UpdateStore', {
024be9c7 414 interval: 3000,
d5066d84 415 storeid: 'ceph-pool-list' + nodename,
024be9c7
DM
416 model: 'ceph-pool-list',
417 proxy: {
261bb633 418 type: 'proxmox',
b5bbcb4c 419 url: `/api2/json/nodes/${nodename}/ceph/pool`,
f6710aac 420 },
024be9c7 421 });
ec455ed0
TL
422 let store = Ext.create('Proxmox.data.DiffStore', { rstore: rstore });
423
13786fb0
TL
424 // manages the "install ceph?" overlay
425 PVE.Utils.monitor_ceph_installed(me, rstore, nodename);
024be9c7 426
4618a869 427 var run_editor = function() {
ec455ed0
TL
428 let rec = sm.getSelection()[0];
429 if (!rec || !rec.data.pool_name) {
4618a869
AA
430 return;
431 }
519d9f3f 432 Ext.create('PVE.Ceph.PoolEdit', {
4618a869
AA
433 title: gettext('Edit') + ': Ceph Pool',
434 nodename: nodename,
435 pool_name: rec.data.pool_name,
1a042025 436 isErasure: rec.data.type === 'erasure',
ec455ed0
TL
437 autoShow: true,
438 listeners: {
439 destroy: () => rstore.load(),
440 },
4618a869 441 });
4618a869
AA
442 };
443
024be9c7
DM
444 Ext.apply(me, {
445 store: store,
446 selModel: sm,
ec455ed0
TL
447 tbar: [
448 {
449 text: gettext('Create'),
450 handler: function() {
b555a412
AL
451 let keys = [
452 'global:osd-pool-default-min-size',
453 'global:osd-pool-default-size',
454 ];
455 let params = {
456 'config-keys': keys.join(';'),
457 };
458
459 Proxmox.Utils.API2Request({
460 url: '/nodes/localhost/ceph/cfg/value',
461 method: 'GET',
462 params,
463 waitMsgTarget: me.getView(),
464 failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
465 success: function({ result: { data } }) {
466 let global = data.global;
467 let defaultSize = global?.['osd-pool-default-size'] ?? 3;
468 let defaultMinSize = global?.['osd-pool-default-min-size'] ?? 2;
469
470 Ext.create('PVE.Ceph.PoolEdit', {
471 title: gettext('Create') + ': Ceph Pool',
472 isCreate: true,
473 isErasure: false,
474 defaultSize,
475 defaultMinSize,
476 nodename: nodename,
477 autoShow: true,
478 listeners: {
479 destroy: () => rstore.load(),
480 },
481 });
ec455ed0
TL
482 },
483 });
484 },
485 },
486 {
487 xtype: 'proxmoxButton',
488 text: gettext('Edit'),
489 selModel: sm,
490 disabled: true,
491 handler: run_editor,
492 },
493 {
494 xtype: 'proxmoxButton',
495 text: gettext('Destroy'),
496 selModel: sm,
497 disabled: true,
498 handler: function() {
499 let rec = sm.getSelection()[0];
500 if (!rec || !rec.data.pool_name) {
501 return;
502 }
503 let poolName = rec.data.pool_name;
b9635c9b 504 Ext.create('Proxmox.window.SafeDestroy', {
ec455ed0 505 showProgress: true,
b5bbcb4c 506 url: `/nodes/${nodename}/ceph/pool/${poolName}`,
ec455ed0
TL
507 params: {
508 remove_storages: 1,
509 },
510 item: {
511 type: 'CephPool',
512 id: poolName,
513 },
b9635c9b 514 taskName: 'cephdestroypool',
ec455ed0
TL
515 autoShow: true,
516 listeners: {
517 destroy: () => rstore.load(),
518 },
519 });
520 },
521 },
522 ],
024be9c7 523 listeners: {
6386068d
TL
524 activate: () => rstore.startUpdate(),
525 destroy: () => rstore.stopUpdate(),
4618a869 526 itemdblclick: run_editor,
f6710aac 527 },
024be9c7
DM
528 });
529
530 me.callParent();
f6710aac 531 },
024be9c7 532}, function() {
024be9c7
DM
533 Ext.define('ceph-pool-list', {
534 extend: 'Ext.data.Model',
8058410f
TL
535 fields: ['pool_name',
536 { name: 'pool', type: 'integer' },
537 { name: 'size', type: 'integer' },
538 { name: 'min_size', type: 'integer' },
539 { name: 'pg_num', type: 'integer' },
ae793232 540 { name: 'pg_num_min', type: 'integer' },
8058410f
TL
541 { name: 'bytes_used', type: 'integer' },
542 { name: 'percent_used', type: 'number' },
543 { name: 'crush_rule', type: 'integer' },
544 { name: 'crush_rule_name', type: 'string' },
4618a869
AA
545 { name: 'pg_autoscale_mode', type: 'string' },
546 { name: 'pg_num_final', type: 'integer' },
547 { name: 'target_size_ratio', type: 'number' },
548 { name: 'target_size', type: 'integer' },
024be9c7 549 ],
f6710aac 550 idProperty: 'pool_name',
024be9c7
DM
551 });
552});
d2692b86
DC
553
554Ext.define('PVE.form.CephRuleSelector', {
555 extend: 'Ext.form.field.ComboBox',
556 alias: 'widget.pveCephRuleSelector',
557
558 allowBlank: false,
559 valueField: 'name',
560 displayField: 'name',
561 editable: false,
562 queryMode: 'local',
563
564 initComponent: function() {
cd99757b 565 let me = this;
d2692b86
DC
566
567 if (!me.nodename) {
568 throw "no nodename given";
569 }
c2cdef89
DC
570
571 me.originalAllowBlank = me.allowBlank;
572 me.allowBlank = true;
573
d2692b86 574 Ext.apply(me, {
261bb633
TL
575 store: {
576 fields: ['name'],
577 sorters: 'name',
578 proxy: {
579 type: 'proxmox',
580 url: `/api2/json/nodes/${me.nodename}/ceph/rules`,
581 },
c2cdef89 582 autoLoad: {
261bb633 583 callback: (records, op, success) => {
c2cdef89 584 if (me.isCreate && success && records.length > 0) {
261bb633
TL
585 me.select(records[0]);
586 }
c2cdef89
DC
587
588 me.allowBlank = me.originalAllowBlank;
589 delete me.originalAllowBlank;
590 me.validate();
261bb633 591 },
c2cdef89 592 },
261bb633 593 },
d2692b86
DC
594 });
595
596 me.callParent();
f6710aac 597 },
d2692b86
DC
598
599});