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