]>
Commit | Line | Data |
---|---|---|
4618a869 AA |
1 | Ext.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', |
4618a869 | 10 | column1: [ |
c449c89a | 11 | { |
4618a869 | 12 | xtype: 'pmxDisplayEditField', |
c449c89a | 13 | fieldLabel: gettext('Name'), |
4618a869 AA |
14 | cbind: { |
15 | editable: '{isCreate}', | |
16 | value: '{pool_name}', | |
17 | disabled: '{!isCreate}', | |
18 | }, | |
c449c89a | 19 | name: 'name', |
f6710aac | 20 | allowBlank: false, |
c449c89a DC |
21 | }, |
22 | { | |
bf96f60d | 23 | xtype: 'proxmoxintegerfield', |
c449c89a DC |
24 | fieldLabel: gettext('Size'), |
25 | name: 'size', | |
f441a266 | 26 | value: 3, |
4618a869 | 27 | minValue: 2, |
eef1979c | 28 | maxValue: 7, |
f6710aac | 29 | allowBlank: false, |
4618a869 AA |
30 | listeners: { |
31 | change: function(field, val) { | |
32 | let size = Math.round(val / 2); | |
33 | if (size > 1) { | |
34 | field.up('inputpanel').down('field[name=min_size]').setValue(size); | |
35 | } | |
36 | }, | |
37 | }, | |
38 | }, | |
39 | ], | |
40 | column2: [ | |
41 | { | |
42 | xtype: 'proxmoxKVComboBox', | |
43 | fieldLabel: 'PG Autoscale Mode', | |
44 | name: 'pg_autoscale_mode', | |
45 | comboItems: [ | |
46 | ['warn', 'warn'], | |
47 | ['on', 'on'], | |
48 | ['off', 'off'], | |
49 | ], | |
50 | value: 'warn', | |
51 | allowBlank: false, | |
52 | autoSelect: false, | |
53 | labelWidth: 140, | |
c449c89a | 54 | }, |
4618a869 AA |
55 | { |
56 | xtype: 'proxmoxcheckbox', | |
57 | fieldLabel: gettext('Add as Storage'), | |
58 | cbind: { | |
59 | value: '{isCreate}', | |
60 | hidden: '{!isCreate}', | |
61 | }, | |
62 | name: 'add_storages', | |
63 | labelWidth: 140, | |
64 | autoEl: { | |
65 | tag: 'div', | |
66 | 'data-qtip': gettext('Add the new pool to the cluster storage configuration.'), | |
67 | }, | |
68 | }, | |
69 | ], | |
70 | advancedColumn1: [ | |
c449c89a | 71 | { |
bf96f60d | 72 | xtype: 'proxmoxintegerfield', |
c449c89a DC |
73 | fieldLabel: gettext('Min. Size'), |
74 | name: 'min_size', | |
f441a266 | 75 | value: 2, |
4618a869 AA |
76 | cbind: { |
77 | minValue: (get) => get('isCreate') ? 2 : 1, | |
78 | }, | |
eef1979c | 79 | maxValue: 7, |
f6710aac | 80 | allowBlank: false, |
4618a869 AA |
81 | listeners: { |
82 | change: function(field, val) { | |
83 | let warn = true; | |
84 | let warn_text = gettext('Min. Size'); | |
85 | ||
86 | if (val < 2) { | |
87 | warn = false; | |
88 | warn_text = gettext('Min. Size') + ' <i class="fa fa-exclamation-triangle warning"></i>'; | |
89 | } | |
90 | ||
91 | field.up().down('field[name=min_size-warning]').setHidden(warn); | |
92 | field.setFieldLabel(warn_text); | |
93 | }, | |
94 | }, | |
95 | }, | |
96 | { | |
97 | xtype: 'displayfield', | |
98 | name: 'min_size-warning', | |
99 | userCls: 'pmx-hint', | |
100 | value: 'A pool with min_size=1 could lead to data loss, incomplete PGs or unfound objects.', | |
101 | hidden: true, | |
c449c89a DC |
102 | }, |
103 | { | |
d2692b86 DC |
104 | xtype: 'pveCephRuleSelector', |
105 | fieldLabel: 'Crush Rule', // do not localize | |
4618a869 | 106 | cbind: { nodename: '{nodename}' }, |
d2692b86 | 107 | name: 'crush_rule', |
f6710aac | 108 | allowBlank: false, |
c449c89a DC |
109 | }, |
110 | { | |
bf96f60d | 111 | xtype: 'proxmoxintegerfield', |
4618a869 | 112 | fieldLabel: '# of PGs', |
c449c89a | 113 | name: 'pg_num', |
6ad70a2b | 114 | value: 128, |
4618a869 | 115 | minValue: 1, |
c449c89a | 116 | maxValue: 32768, |
4618a869 AA |
117 | allowBlank: false, |
118 | emptyText: 128, | |
119 | }, | |
120 | ], | |
121 | advancedColumn2: [ | |
122 | { | |
123 | xtype: 'numberfield', | |
124 | fieldLabel: gettext('Target Size Ratio'), | |
125 | name: 'target_size_ratio', | |
126 | labelWidth: 140, | |
127 | minValue: 0, | |
128 | decimalPrecision: 3, | |
23b316a3 | 129 | allowBlank: true, |
4618a869 | 130 | emptyText: '0.0', |
5947248f DC |
131 | }, |
132 | { | |
4618a869 AA |
133 | xtype: 'numberfield', |
134 | fieldLabel: gettext('Target Size') + ' (GiB)', | |
135 | name: 'target_size', | |
136 | labelWidth: 140, | |
137 | minValue: 0, | |
138 | allowBlank: true, | |
139 | emptyText: '0', | |
140 | }, | |
141 | { | |
142 | xtype: 'displayfield', | |
143 | userCls: 'pmx-hint', | |
144 | value: 'Target Size Ratio takes precedence.', | |
f6710aac | 145 | }, |
ae793232 AA |
146 | { |
147 | xtype: 'proxmoxintegerfield', | |
148 | fieldLabel: 'Min. # of PGs', | |
149 | name: 'pg_num_min', | |
150 | labelWidth: 140, | |
151 | minValue: 0, | |
152 | allowBlank: true, | |
153 | emptyText: '0', | |
154 | }, | |
c449c89a | 155 | ], |
024be9c7 | 156 | |
4618a869 AA |
157 | onGetValues: function(values) { |
158 | Object.keys(values || {}).forEach(function(name) { | |
159 | if (values[name] === '') { | |
160 | delete values[name]; | |
161 | } | |
162 | }); | |
163 | ||
17b90534 DC |
164 | let target_size = Number.parseFloat(values.target_size); |
165 | ||
166 | if (Ext.isNumber(target_size) && target_size !== 0) { | |
167 | values.target_size = (target_size*1024*1024*1024).toFixed(0); | |
024be9c7 | 168 | } |
17b90534 | 169 | |
4618a869 AA |
170 | return values; |
171 | }, | |
024be9c7 | 172 | |
4618a869 | 173 | setValues: function(values) { |
17b90534 DC |
174 | let target_size = Number.parseFloat(values.target_size); |
175 | ||
176 | if (Ext.isNumber(target_size) && target_size !== 0) { | |
177 | values.target_size = target_size/1024/1024/1024; | |
4618a869 | 178 | } |
024be9c7 | 179 | |
4618a869 | 180 | this.callParent([values]); |
f6710aac | 181 | }, |
4618a869 AA |
182 | |
183 | }); | |
184 | ||
185 | Ext.define('PVE.CephPoolEdit', { | |
186 | extend: 'Proxmox.window.Edit', | |
187 | alias: 'widget.pveCephPoolEdit', | |
188 | xtype: 'pveCephPoolEdit', | |
189 | mixins: ['Proxmox.Mixin.CBind'], | |
190 | ||
191 | cbindData: { | |
192 | pool_name: '', | |
193 | isCreate: (cfg) => !cfg.pool_name, | |
194 | }, | |
195 | ||
196 | cbind: { | |
197 | autoLoad: get => !get('isCreate'), | |
198 | url: get => get('isCreate') | |
199 | ? `/nodes/${get('nodename')}/ceph/pools` | |
200 | : `/nodes/${get('nodename')}/ceph/pools/${get('pool_name')}`, | |
201 | method: get => get('isCreate') ? 'POST' : 'PUT', | |
202 | }, | |
203 | ||
cc0734fc DC |
204 | showProgress: true, |
205 | ||
4618a869 AA |
206 | subject: gettext('Ceph Pool'), |
207 | ||
208 | items: [{ | |
209 | xtype: 'pveCephPoolInputPanel', | |
210 | cbind: { | |
211 | nodename: '{nodename}', | |
212 | pool_name: '{pool_name}', | |
213 | isCreate: '{isCreate}', | |
214 | }, | |
215 | }], | |
024be9c7 DM |
216 | }); |
217 | ||
218 | Ext.define('PVE.node.CephPoolList', { | |
219 | extend: 'Ext.grid.GridPanel', | |
9ad28182 | 220 | alias: 'widget.pveNodeCephPoolList', |
024be9c7 | 221 | |
ba93a9c6 | 222 | onlineHelp: 'chapter_pveceph', |
5b7b4b76 | 223 | |
361aafd0 DC |
224 | stateful: true, |
225 | stateId: 'grid-ceph-pools', | |
c449c89a | 226 | bufferedRenderer: false, |
5b7b4b76 | 227 | |
8058410f | 228 | features: [{ ftype: 'summary' }], |
5b7b4b76 | 229 | |
c449c89a DC |
230 | columns: [ |
231 | { | |
11ce9e1a AA |
232 | text: gettext('Name'), |
233 | minWidth: 120, | |
234 | flex: 2, | |
c449c89a | 235 | sortable: true, |
f6710aac | 236 | dataIndex: 'pool_name', |
c449c89a DC |
237 | }, |
238 | { | |
11ce9e1a AA |
239 | text: gettext('Size') + '/min', |
240 | minWidth: 100, | |
241 | flex: 1, | |
5b7b4b76 | 242 | align: 'right', |
c449c89a DC |
243 | renderer: function(v, meta, rec) { |
244 | return v + '/' + rec.data.min_size; | |
245 | }, | |
f6710aac | 246 | dataIndex: 'size', |
c449c89a DC |
247 | }, |
248 | { | |
11ce9e1a AA |
249 | text: '# of Placement Groups', |
250 | flex: 1, | |
251 | minWidth: 150, | |
252 | align: 'right', | |
253 | dataIndex: 'pg_num', | |
c449c89a DC |
254 | }, |
255 | { | |
11ce9e1a AA |
256 | text: gettext('Optimal # of PGs'), |
257 | flex: 1, | |
258 | minWidth: 140, | |
259 | align: 'right', | |
260 | dataIndex: 'pg_num_final', | |
261 | renderer: function(value, metaData) { | |
262 | if (!value) { | |
263 | value = '<i class="fa fa-info-circle faded"></i> n/a'; | |
264 | metaData.tdAttr = 'data-qtip="Needs pg_autoscaler module enabled."'; | |
265 | } | |
266 | return value; | |
267 | }, | |
c9508b5d | 268 | }, |
ae793232 AA |
269 | { |
270 | text: gettext('Min. # of PGs'), | |
271 | flex: 1, | |
272 | minWidth: 140, | |
273 | align: 'right', | |
274 | dataIndex: 'pg_num_min', | |
275 | hidden: true, | |
276 | }, | |
c449c89a | 277 | { |
11ce9e1a AA |
278 | text: gettext('Target Size Ratio'), |
279 | flex: 1, | |
280 | minWidth: 140, | |
281 | align: 'right', | |
282 | dataIndex: 'target_size_ratio', | |
283 | renderer: Ext.util.Format.numberRenderer('0.0000'), | |
284 | hidden: true, | |
285 | }, | |
286 | { | |
287 | text: gettext('Target Size'), | |
288 | flex: 1, | |
289 | minWidth: 140, | |
290 | align: 'right', | |
291 | dataIndex: 'target_size', | |
292 | hidden: true, | |
293 | renderer: function(v, metaData, rec) { | |
294 | let value = PVE.Utils.render_size(v); | |
295 | if (rec.data.target_size_ratio > 0) { | |
296 | value = '<i class="fa fa-info-circle faded"></i> ' + value; | |
297 | metaData.tdAttr = 'data-qtip="Target Size Ratio takes precedence over Target Size."'; | |
298 | } | |
299 | return value; | |
300 | }, | |
301 | }, | |
302 | { | |
303 | text: gettext('Autoscale Mode'), | |
304 | flex: 1, | |
305 | minWidth: 140, | |
306 | align: 'right', | |
307 | dataIndex: 'pg_autoscale_mode', | |
308 | }, | |
309 | { | |
310 | text: 'CRUSH Rule (ID)', | |
311 | flex: 1, | |
312 | align: 'right', | |
313 | minWidth: 150, | |
314 | renderer: function(v, meta, rec) { | |
315 | return v + ' (' + rec.data.crush_rule + ')'; | |
316 | }, | |
317 | dataIndex: 'crush_rule_name', | |
318 | }, | |
319 | { | |
320 | text: gettext('Used') + ' (%)', | |
321 | flex: 1, | |
322 | minWidth: 180, | |
323 | sortable: true, | |
324 | align: 'right', | |
325 | dataIndex: 'bytes_used', | |
326 | summaryType: 'sum', | |
327 | summaryRenderer: PVE.Utils.render_size, | |
328 | renderer: function(v, meta, rec) { | |
329 | let percentage = Ext.util.Format.percent(rec.data.percent_used, '0.00'); | |
330 | let used = PVE.Utils.render_size(v); | |
331 | return used + ' (' + percentage + ')'; | |
332 | }, | |
f6710aac | 333 | }, |
c449c89a | 334 | ], |
024be9c7 DM |
335 | initComponent: function() { |
336 | var me = this; | |
337 | ||
338 | var nodename = me.pveSelNode.data.node; | |
339 | if (!nodename) { | |
340 | throw "no node name specified"; | |
341 | } | |
342 | ||
343 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
344 | ||
0c7c0d6b | 345 | var rstore = Ext.create('Proxmox.data.UpdateStore', { |
024be9c7 | 346 | interval: 3000, |
d5066d84 | 347 | storeid: 'ceph-pool-list' + nodename, |
024be9c7 DM |
348 | model: 'ceph-pool-list', |
349 | proxy: { | |
56a353b9 | 350 | type: 'proxmox', |
f6710aac TL |
351 | url: "/api2/json/nodes/" + nodename + "/ceph/pools", |
352 | }, | |
024be9c7 | 353 | }); |
ec455ed0 TL |
354 | let store = Ext.create('Proxmox.data.DiffStore', { rstore: rstore }); |
355 | ||
356 | PVE.Utils.handleStoreErrorOrMask( | |
357 | me, | |
358 | rstore, | |
359 | /not (installed|initialized)/i, | |
360 | (_, error) => { | |
361 | rstore.stopUpdate(); | |
362 | PVE.Utils.showCephInstallOrMask(me, error.statusText, nodename, win => { | |
363 | me.mon(win, 'cephInstallWindowClosed', () => rstore.startUpdate()); | |
024be9c7 | 364 | }); |
f6710aac | 365 | }, |
ec455ed0 | 366 | ); |
024be9c7 | 367 | |
4618a869 | 368 | var run_editor = function() { |
ec455ed0 TL |
369 | let rec = sm.getSelection()[0]; |
370 | if (!rec || !rec.data.pool_name) { | |
4618a869 AA |
371 | return; |
372 | } | |
ec455ed0 | 373 | Ext.create('PVE.CephPoolEdit', { |
4618a869 AA |
374 | title: gettext('Edit') + ': Ceph Pool', |
375 | nodename: nodename, | |
376 | pool_name: rec.data.pool_name, | |
ec455ed0 TL |
377 | autoShow: true, |
378 | listeners: { | |
379 | destroy: () => rstore.load(), | |
380 | }, | |
4618a869 | 381 | }); |
4618a869 AA |
382 | }; |
383 | ||
024be9c7 DM |
384 | Ext.apply(me, { |
385 | store: store, | |
386 | selModel: sm, | |
ec455ed0 TL |
387 | tbar: [ |
388 | { | |
389 | text: gettext('Create'), | |
390 | handler: function() { | |
391 | Ext.create('PVE.CephPoolEdit', { | |
392 | title: gettext('Create') + ': Ceph Pool', | |
393 | isCreate: true, | |
394 | nodename: nodename, | |
395 | autoShow: true, | |
396 | listeners: { | |
397 | destroy: () => rstore.load(), | |
398 | }, | |
399 | }); | |
400 | }, | |
401 | }, | |
402 | { | |
403 | xtype: 'proxmoxButton', | |
404 | text: gettext('Edit'), | |
405 | selModel: sm, | |
406 | disabled: true, | |
407 | handler: run_editor, | |
408 | }, | |
409 | { | |
410 | xtype: 'proxmoxButton', | |
411 | text: gettext('Destroy'), | |
412 | selModel: sm, | |
413 | disabled: true, | |
414 | handler: function() { | |
415 | let rec = sm.getSelection()[0]; | |
416 | if (!rec || !rec.data.pool_name) { | |
417 | return; | |
418 | } | |
419 | let poolName = rec.data.pool_name; | |
420 | Ext.create('PVE.window.SafeDestroy', { | |
421 | showProgress: true, | |
422 | url: `/nodes/${nodename}/ceph/pools/${poolName}`, | |
423 | params: { | |
424 | remove_storages: 1, | |
425 | }, | |
426 | item: { | |
427 | type: 'CephPool', | |
428 | id: poolName, | |
429 | }, | |
430 | autoShow: true, | |
431 | listeners: { | |
432 | destroy: () => rstore.load(), | |
433 | }, | |
434 | }); | |
435 | }, | |
436 | }, | |
437 | ], | |
024be9c7 | 438 | listeners: { |
6386068d TL |
439 | activate: () => rstore.startUpdate(), |
440 | destroy: () => rstore.stopUpdate(), | |
4618a869 | 441 | itemdblclick: run_editor, |
f6710aac | 442 | }, |
024be9c7 DM |
443 | }); |
444 | ||
445 | me.callParent(); | |
f6710aac | 446 | }, |
024be9c7 | 447 | }, function() { |
024be9c7 DM |
448 | Ext.define('ceph-pool-list', { |
449 | extend: 'Ext.data.Model', | |
8058410f TL |
450 | fields: ['pool_name', |
451 | { name: 'pool', type: 'integer' }, | |
452 | { name: 'size', type: 'integer' }, | |
453 | { name: 'min_size', type: 'integer' }, | |
454 | { name: 'pg_num', type: 'integer' }, | |
ae793232 | 455 | { name: 'pg_num_min', type: 'integer' }, |
8058410f TL |
456 | { name: 'bytes_used', type: 'integer' }, |
457 | { name: 'percent_used', type: 'number' }, | |
458 | { name: 'crush_rule', type: 'integer' }, | |
459 | { name: 'crush_rule_name', type: 'string' }, | |
4618a869 AA |
460 | { name: 'pg_autoscale_mode', type: 'string' }, |
461 | { name: 'pg_num_final', type: 'integer' }, | |
462 | { name: 'target_size_ratio', type: 'number' }, | |
463 | { name: 'target_size', type: 'integer' }, | |
024be9c7 | 464 | ], |
f6710aac | 465 | idProperty: 'pool_name', |
024be9c7 DM |
466 | }); |
467 | }); | |
d2692b86 DC |
468 | |
469 | Ext.define('PVE.form.CephRuleSelector', { | |
470 | extend: 'Ext.form.field.ComboBox', | |
471 | alias: 'widget.pveCephRuleSelector', | |
472 | ||
473 | allowBlank: false, | |
474 | valueField: 'name', | |
475 | displayField: 'name', | |
476 | editable: false, | |
477 | queryMode: 'local', | |
478 | ||
479 | initComponent: function() { | |
480 | var me = this; | |
481 | ||
482 | if (!me.nodename) { | |
483 | throw "no nodename given"; | |
484 | } | |
485 | ||
486 | var store = Ext.create('Ext.data.Store', { | |
487 | fields: ['name'], | |
488 | sorters: 'name', | |
489 | proxy: { | |
56a353b9 | 490 | type: 'proxmox', |
ec455ed0 | 491 | url: `/api2/json/nodes/${me.nodename}/ceph/rules`, |
f6710aac | 492 | }, |
d2692b86 DC |
493 | }); |
494 | ||
495 | Ext.apply(me, { | |
f6710aac | 496 | store: store, |
d2692b86 DC |
497 | }); |
498 | ||
499 | me.callParent(); | |
500 | ||
501 | store.load({ | |
8058410f | 502 | callback: function(rec, op, success) { |
d2692b86 DC |
503 | if (success && rec.length > 0) { |
504 | me.select(rec[0]); | |
505 | } | |
f6710aac | 506 | }, |
d2692b86 | 507 | }); |
f6710aac | 508 | }, |
d2692b86 DC |
509 | |
510 | }); |