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