]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/ceph/Pool.js
ui: Utils: use render functions from widget-toolkit
[pve-manager.git] / www / manager6 / ceph / Pool.js
1 Ext.define('PVE.CephPoolInputPanel', {
2 extend: 'Proxmox.panel.InputPanel',
3 xtype: 'pveCephPoolInputPanel',
4 mixins: ['Proxmox.Mixin.CBind'],
5
6 showProgress: true,
7 onlineHelp: 'pve_ceph_pools',
8
9 subject: 'Ceph Pool',
10 column1: [
11 {
12 xtype: 'pmxDisplayEditField',
13 fieldLabel: gettext('Name'),
14 cbind: {
15 editable: '{isCreate}',
16 value: '{pool_name}',
17 disabled: '{!isCreate}',
18 },
19 name: 'name',
20 allowBlank: false,
21 },
22 {
23 xtype: 'proxmoxintegerfield',
24 fieldLabel: gettext('Size'),
25 name: 'size',
26 value: 3,
27 minValue: 2,
28 maxValue: 7,
29 allowBlank: false,
30 listeners: {
31 change: function(field, val) {
32 let size = Math.round((val + 1) / 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,
54 },
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: [
71 {
72 xtype: 'proxmoxintegerfield',
73 fieldLabel: gettext('Min. Size'),
74 name: 'min_size',
75 value: 2,
76 cbind: {
77 minValue: (get) => get('isCreate') ? 2 : 1,
78 },
79 maxValue: 7,
80 allowBlank: false,
81 listeners: {
82 change: function(field, minSize) {
83 let panel = field.up('inputpanel');
84 let size = panel.down('field[name=size]').getValue();
85
86 let showWarning = minSize <= size / 2 && minSize !== size;
87
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);
94 },
95 },
96 },
97 {
98 xtype: 'displayfield',
99 name: 'min_size-warning',
100 userCls: 'pmx-hint',
101 value: gettext('min_size <= size/2 can lead to data loss, incomplete PGs or unfound objects.'),
102 hidden: true,
103 },
104 {
105 xtype: 'pveCephRuleSelector',
106 fieldLabel: 'Crush Rule', // do not localize
107 cbind: { nodename: '{nodename}' },
108 name: 'crush_rule',
109 allowBlank: false,
110 },
111 {
112 xtype: 'proxmoxintegerfield',
113 fieldLabel: '# of PGs',
114 name: 'pg_num',
115 value: 128,
116 minValue: 1,
117 maxValue: 32768,
118 allowBlank: false,
119 emptyText: 128,
120 },
121 ],
122 advancedColumn2: [
123 {
124 xtype: 'numberfield',
125 fieldLabel: gettext('Target Ratio'),
126 name: 'target_size_ratio',
127 minValue: 0,
128 decimalPrecision: 3,
129 allowBlank: true,
130 allowZero: true,
131 emptyText: '0.0',
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 },
136 },
137 {
138 xtype: 'pveSizeField',
139 name: 'target_size',
140 fieldLabel: gettext('Target Size'),
141 unit: 'GiB',
142 minValue: 0,
143 allowBlank: true,
144 allowZero: true,
145 emptyText: '0',
146 autoEl: {
147 tag: 'div',
148 'data-qtip': gettext('The amount of data eventually stored in this pool. Used for auto-scaling.'),
149 },
150 },
151 {
152 xtype: 'displayfield',
153 userCls: 'pmx-hint',
154 value: Ext.String.format(gettext('{0} takes precedence.'), gettext('Target Ratio')), // FIXME: tooltip?
155 },
156 {
157 xtype: 'proxmoxintegerfield',
158 fieldLabel: 'Min. # of PGs',
159 name: 'pg_num_min',
160 minValue: 0,
161 allowBlank: true,
162 emptyText: '0',
163 },
164 ],
165
166 onGetValues: function(values) {
167 Object.keys(values || {}).forEach(function(name) {
168 if (values[name] === '') {
169 delete values[name];
170 }
171 });
172
173 return values;
174 },
175 });
176
177 Ext.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
196 showProgress: true,
197
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 }],
208 });
209
210 Ext.define('PVE.node.CephPoolList', {
211 extend: 'Ext.grid.GridPanel',
212 alias: 'widget.pveNodeCephPoolList',
213
214 onlineHelp: 'chapter_pveceph',
215
216 stateful: true,
217 stateId: 'grid-ceph-pools',
218 bufferedRenderer: false,
219
220 features: [{ ftype: 'summary' }],
221
222 columns: [
223 {
224 text: gettext('Name'),
225 minWidth: 120,
226 flex: 2,
227 sortable: true,
228 dataIndex: 'pool_name',
229 },
230 {
231 text: gettext('Size') + '/min',
232 minWidth: 100,
233 flex: 1,
234 align: 'right',
235 renderer: (v, meta, rec) => `${v}/${rec.data.min_size}`,
236 dataIndex: 'size',
237 },
238 {
239 text: '# of Placement Groups',
240 flex: 1,
241 minWidth: 100,
242 align: 'right',
243 dataIndex: 'pg_num',
244 },
245 {
246 text: gettext('Optimal # of PGs'),
247 flex: 1,
248 minWidth: 100,
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 },
258 },
259 {
260 text: gettext('Min. # of PGs'),
261 flex: 1,
262 minWidth: 100,
263 align: 'right',
264 dataIndex: 'pg_num_min',
265 hidden: true,
266 },
267 {
268 text: gettext('Target Ratio'),
269 flex: 1,
270 minWidth: 100,
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,
279 minWidth: 100,
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,
295 minWidth: 100,
296 align: 'right',
297 dataIndex: 'pg_autoscale_mode',
298 },
299 {
300 text: 'CRUSH Rule (ID)',
301 flex: 1,
302 align: 'right',
303 minWidth: 150,
304 renderer: (v, meta, rec) => `${v} (${rec.data.crush_rule})`,
305 dataIndex: 'crush_rule_name',
306 },
307 {
308 text: gettext('Used') + ' (%)',
309 flex: 1,
310 minWidth: 150,
311 sortable: true,
312 align: 'right',
313 dataIndex: 'bytes_used',
314 summaryType: 'sum',
315 summaryRenderer: Proxmox.Utils.render_size,
316 renderer: function(v, meta, rec) {
317 let percentage = Ext.util.Format.percent(rec.data.percent_used, '0.00');
318 let used = Proxmox.Utils.render_size(v);
319 return `${used} (${percentage})`;
320 },
321 },
322 ],
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
333 var rstore = Ext.create('Proxmox.data.UpdateStore', {
334 interval: 3000,
335 storeid: 'ceph-pool-list' + nodename,
336 model: 'ceph-pool-list',
337 proxy: {
338 type: 'proxmox',
339 url: "/api2/json/nodes/" + nodename + "/ceph/pools",
340 },
341 });
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());
352 });
353 },
354 );
355
356 var run_editor = function() {
357 let rec = sm.getSelection()[0];
358 if (!rec || !rec.data.pool_name) {
359 return;
360 }
361 Ext.create('PVE.CephPoolEdit', {
362 title: gettext('Edit') + ': Ceph Pool',
363 nodename: nodename,
364 pool_name: rec.data.pool_name,
365 autoShow: true,
366 listeners: {
367 destroy: () => rstore.load(),
368 },
369 });
370 };
371
372 Ext.apply(me, {
373 store: store,
374 selModel: sm,
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;
408 Ext.create('Proxmox.window.SafeDestroy', {
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 },
418 taskName: 'cephdestroypool',
419 autoShow: true,
420 listeners: {
421 destroy: () => rstore.load(),
422 },
423 });
424 },
425 },
426 ],
427 listeners: {
428 activate: () => rstore.startUpdate(),
429 destroy: () => rstore.stopUpdate(),
430 itemdblclick: run_editor,
431 },
432 });
433
434 me.callParent();
435 },
436 }, function() {
437 Ext.define('ceph-pool-list', {
438 extend: 'Ext.data.Model',
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' },
444 { name: 'pg_num_min', type: 'integer' },
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' },
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' },
453 ],
454 idProperty: 'pool_name',
455 });
456 });
457
458 Ext.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: {
479 type: 'proxmox',
480 url: `/api2/json/nodes/${me.nodename}/ceph/rules`,
481 },
482 });
483
484 Ext.apply(me, {
485 store: store,
486 });
487
488 me.callParent();
489
490 store.load({
491 callback: function(rec, op, success) {
492 if (success && rec.length > 0) {
493 me.select(rec[0]);
494 }
495 },
496 });
497 },
498
499 });