]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HDEdit.js
ui: qemu: disk edit: drop label widths from advanced columns
[pve-manager.git] / www / manager6 / qemu / HDEdit.js
1 /* 'change' property is assigned a string and then a function */
2 Ext.define('PVE.qemu.HDInputPanel', {
3 extend: 'Proxmox.panel.InputPanel',
4 alias: 'widget.pveQemuHDInputPanel',
5 onlineHelp: 'qm_hard_disk',
6
7 insideWizard: false,
8
9 unused: false, // ADD usused disk imaged
10
11 vmconfig: {}, // used to select usused disks
12
13 viewModel: {
14 data: {
15 isScsi: false,
16 isVirtIO: false,
17 },
18 },
19
20 controller: {
21 xclass: 'Ext.app.ViewController',
22
23 onControllerChange: function(field) {
24 let me = this;
25 let vm = this.getViewModel();
26
27 let value = field.getValue();
28 vm.set('isSCSI', value.match(/^scsi/));
29 vm.set('isVirtIO', value.match(/^virtio/));
30
31 me.fireIdChange();
32 },
33
34 fireIdChange: function() {
35 let view = this.getView();
36 view.fireEvent('diskidchange', view, view.bussel.getConfId());
37 },
38
39 control: {
40 'field[name=controller]': {
41 change: 'onControllerChange',
42 afterrender: 'onControllerChange',
43 },
44 'field[name=deviceid]': {
45 change: 'fireIdChange',
46 },
47 'field[name=iothread]': {
48 change: function(f, value) {
49 if (!this.getView().insideWizard) {
50 return;
51 }
52 var vmScsiType = value ? 'virtio-scsi-single': 'virtio-scsi-pci';
53 this.lookupReference('scsiController').setValue(vmScsiType);
54 },
55 },
56 },
57
58 init: function(view) {
59 var vm = this.getViewModel();
60 if (view.isCreate) {
61 vm.set('isIncludedInBackup', true);
62 }
63 if (view.confid) {
64 vm.set('isSCSI', view.confid.match(/^scsi/));
65 vm.set('isVirtIO', view.confid.match(/^virtio/));
66 }
67 },
68 },
69
70 onGetValues: function(values) {
71 var me = this;
72
73 var params = {};
74 var confid = me.confid || values.controller + values.deviceid;
75
76 if (me.unused) {
77 me.drive.file = me.vmconfig[values.unusedId];
78 confid = values.controller + values.deviceid;
79 } else if (me.isCreate) {
80 if (values.hdimage) {
81 me.drive.file = values.hdimage;
82 } else {
83 me.drive.file = values.hdstorage + ":" + values.disksize;
84 }
85 me.drive.format = values.diskformat;
86 }
87
88 PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0');
89 PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no');
90 PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on');
91 PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on');
92 PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on');
93 PVE.Utils.propertyStringSet(me.drive, values.readOnly, 'ro', 'on');
94 PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache');
95 PVE.Utils.propertyStringSet(me.drive, values.aio, 'aio');
96
97 ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'].forEach(name => {
98 let burst_name = `${name}_max`;
99 PVE.Utils.propertyStringSet(me.drive, values[name], name);
100 PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name);
101 });
102
103 params[confid] = PVE.Parser.printQemuDrive(me.drive);
104
105 return params;
106 },
107
108 updateVMConfig: function(vmconfig) {
109 var me = this;
110 me.vmconfig = vmconfig;
111 me.bussel?.updateVMConfig(vmconfig);
112 },
113
114 setVMConfig: function(vmconfig) {
115 var me = this;
116
117 me.vmconfig = vmconfig;
118
119 if (me.bussel) {
120 me.bussel.setVMConfig(vmconfig);
121 me.scsiController.setValue(vmconfig.scsihw);
122 }
123 if (me.unusedDisks) {
124 var disklist = [];
125 Ext.Object.each(vmconfig, function(key, value) {
126 if (key.match(/^unused\d+$/)) {
127 disklist.push([key, value]);
128 }
129 });
130 me.unusedDisks.store.loadData(disklist);
131 me.unusedDisks.setValue(me.confid);
132 }
133 },
134
135 setDrive: function(drive) {
136 var me = this;
137
138 me.drive = drive;
139
140 var values = {};
141 var match = drive.file.match(/^([^:]+):/);
142 if (match) {
143 values.hdstorage = match[1];
144 }
145
146 values.hdimage = drive.file;
147 values.backup = PVE.Parser.parseBoolean(drive.backup, 1);
148 values.noreplicate = !PVE.Parser.parseBoolean(drive.replicate, 1);
149 values.diskformat = drive.format || 'raw';
150 values.cache = drive.cache || '__default__';
151 values.discard = drive.discard === 'on';
152 values.ssd = PVE.Parser.parseBoolean(drive.ssd);
153 values.iothread = PVE.Parser.parseBoolean(drive.iothread);
154 values.readOnly = PVE.Parser.parseBoolean(drive.ro);
155 values.aio = drive.aio || '__default__';
156
157 values.mbps_rd = drive.mbps_rd;
158 values.mbps_wr = drive.mbps_wr;
159 values.iops_rd = drive.iops_rd;
160 values.iops_wr = drive.iops_wr;
161 values.mbps_rd_max = drive.mbps_rd_max;
162 values.mbps_wr_max = drive.mbps_wr_max;
163 values.iops_rd_max = drive.iops_rd_max;
164 values.iops_wr_max = drive.iops_wr_max;
165
166 me.setValues(values);
167 },
168
169 setNodename: function(nodename) {
170 var me = this;
171 me.down('#hdstorage').setNodename(nodename);
172 me.down('#hdimage').setStorage(undefined, nodename);
173 },
174
175 hasAdvanced: true,
176
177 initComponent: function() {
178 var me = this;
179
180 me.drive = {};
181
182 let column1 = [];
183 let column2 = [];
184
185 let advancedColumn1 = [];
186 let advancedColumn2 = [];
187
188 if (!me.confid || me.unused) {
189 me.bussel = Ext.create('PVE.form.ControllerSelector', {
190 vmconfig: me.vmconfig,
191 selectFree: true,
192 });
193 column1.push(me.bussel);
194
195 me.scsiController = Ext.create('Ext.form.field.Display', {
196 fieldLabel: gettext('SCSI Controller'),
197 reference: 'scsiController',
198 bind: me.insideWizard ? {
199 value: '{current.scsihw}',
200 visible: '{isSCSI}',
201 } : {
202 visible: '{isSCSI}',
203 },
204 renderer: PVE.Utils.render_scsihw,
205 submitValue: false,
206 hidden: true,
207 });
208 column1.push(me.scsiController);
209 }
210
211 if (me.unused) {
212 me.unusedDisks = Ext.create('Proxmox.form.KVComboBox', {
213 name: 'unusedId',
214 fieldLabel: gettext('Disk image'),
215 matchFieldWidth: false,
216 listConfig: {
217 width: 350,
218 },
219 data: [],
220 allowBlank: false,
221 });
222 column1.push(me.unusedDisks);
223 } else if (me.isCreate) {
224 column1.push({
225 xtype: 'pveDiskStorageSelector',
226 storageContent: 'images',
227 name: 'disk',
228 nodename: me.nodename,
229 autoSelect: me.insideWizard,
230 });
231 } else {
232 column1.push({
233 xtype: 'textfield',
234 disabled: true,
235 submitValue: false,
236 fieldLabel: gettext('Disk image'),
237 name: 'hdimage',
238 });
239 }
240
241 column2.push(
242 {
243 xtype: 'CacheTypeSelector',
244 name: 'cache',
245 value: '__default__',
246 fieldLabel: gettext('Cache'),
247 },
248 {
249 xtype: 'proxmoxcheckbox',
250 fieldLabel: gettext('Discard'),
251 reference: 'discard',
252 name: 'discard',
253 },
254 );
255
256 advancedColumn1.push(
257 {
258 xtype: 'proxmoxcheckbox',
259 fieldLabel: gettext('SSD emulation'),
260 name: 'ssd',
261 clearOnDisable: true,
262 bind: {
263 disabled: '{isVirtIO}',
264 },
265 },
266 {
267 xtype: 'proxmoxcheckbox',
268 name: 'iothread',
269 fieldLabel: 'IO thread',
270 clearOnDisable: true,
271 bind: {
272 disabled: '{!isVirtIO && !isSCSI}',
273 },
274 },
275 {
276 xtype: 'proxmoxcheckbox',
277 name: 'readOnly', // `ro` in the config, we map in get/set values
278 defaultValue: 0,
279 fieldLabel: gettext('Read-only'),
280 clearOnDisable: true,
281 bind: {
282 disabled: '{!isVirtIO && !isSCSI}',
283 },
284 },
285 );
286
287 advancedColumn2.push(
288 {
289 xtype: 'proxmoxcheckbox',
290 fieldLabel: gettext('Backup'),
291 autoEl: {
292 tag: 'div',
293 'data-qtip': gettext('Include volume in backup job'),
294 },
295 name: 'backup',
296 bind: {
297 value: '{isIncludedInBackup}',
298 },
299 },
300 {
301 xtype: 'proxmoxcheckbox',
302 fieldLabel: gettext('Skip replication'),
303 name: 'noreplicate',
304 },
305 {
306 xtype: 'proxmoxKVComboBox',
307 name: 'aio',
308 fieldLabel: gettext('Async IO'),
309 allowBlank: false,
310 value: '__default__',
311 comboItems: [
312 ['__default__', Proxmox.Utils.defaultText + ' (io_uring)'],
313 ['io_uring', 'io_uring'],
314 ['native', 'native'],
315 ['threads', 'threads'],
316 ],
317 },
318 );
319
320 let labelWidth = 140;
321
322 let bwColumn1 = [
323 {
324 xtype: 'numberfield',
325 name: 'mbps_rd',
326 minValue: 1,
327 step: 1,
328 fieldLabel: gettext('Read limit') + ' (MB/s)',
329 labelWidth: labelWidth,
330 emptyText: gettext('unlimited'),
331 },
332 {
333 xtype: 'numberfield',
334 name: 'mbps_wr',
335 minValue: 1,
336 step: 1,
337 fieldLabel: gettext('Write limit') + ' (MB/s)',
338 labelWidth: labelWidth,
339 emptyText: gettext('unlimited'),
340 },
341 {
342 xtype: 'proxmoxintegerfield',
343 name: 'iops_rd',
344 minValue: 10,
345 step: 10,
346 fieldLabel: gettext('Read limit') + ' (ops/s)',
347 labelWidth: labelWidth,
348 emptyText: gettext('unlimited'),
349 },
350 {
351 xtype: 'proxmoxintegerfield',
352 name: 'iops_wr',
353 minValue: 10,
354 step: 10,
355 fieldLabel: gettext('Write limit') + ' (ops/s)',
356 labelWidth: labelWidth,
357 emptyText: gettext('unlimited'),
358 },
359 ];
360
361 let bwColumn2 = [
362 {
363 xtype: 'numberfield',
364 name: 'mbps_rd_max',
365 minValue: 1,
366 step: 1,
367 fieldLabel: gettext('Read max burst') + ' (MB)',
368 labelWidth: labelWidth,
369 emptyText: gettext('default'),
370 },
371 {
372 xtype: 'numberfield',
373 name: 'mbps_wr_max',
374 minValue: 1,
375 step: 1,
376 fieldLabel: gettext('Write max burst') + ' (MB)',
377 labelWidth: labelWidth,
378 emptyText: gettext('default'),
379 },
380 {
381 xtype: 'proxmoxintegerfield',
382 name: 'iops_rd_max',
383 minValue: 10,
384 step: 10,
385 fieldLabel: gettext('Read max burst') + ' (ops)',
386 labelWidth: labelWidth,
387 emptyText: gettext('default'),
388 },
389 {
390 xtype: 'proxmoxintegerfield',
391 name: 'iops_wr_max',
392 minValue: 10,
393 step: 10,
394 fieldLabel: gettext('Write max burst') + ' (ops)',
395 labelWidth: labelWidth,
396 emptyText: gettext('default'),
397 },
398 ];
399
400 me.items = [
401 {
402 xtype: 'tabpanel',
403 plain: true,
404 bodyPadding: 10,
405 border: 0,
406 items: [
407 {
408 title: gettext('Disk'),
409 xtype: 'inputpanel',
410 reference: 'diskpanel',
411 column1,
412 column2,
413 advancedColumn1,
414 advancedColumn2,
415 showAdvanced: me.showAdvanced,
416 getValues: () => ({}),
417 },
418 {
419 title: gettext('Bandwidth'),
420 xtype: 'inputpanel',
421 reference: 'bwpanel',
422 column1: bwColumn1,
423 column2: bwColumn2,
424 showAdvanced: me.showAdvanced,
425 getValues: () => ({}),
426 },
427 ],
428 },
429 ];
430
431 me.callParent();
432 },
433
434 setAdvancedVisible: function(visible) {
435 this.lookup('diskpanel').setAdvancedVisible(visible);
436 this.lookup('bwpanel').setAdvancedVisible(visible);
437 },
438 });
439
440 Ext.define('PVE.qemu.HDEdit', {
441 extend: 'Proxmox.window.Edit',
442
443 isAdd: true,
444
445 backgroundDelay: 5,
446
447 width: 600,
448 bodyPadding: 0,
449
450 initComponent: function() {
451 var me = this;
452
453 var nodename = me.pveSelNode.data.node;
454 if (!nodename) {
455 throw "no node name specified";
456 }
457
458 var unused = me.confid && me.confid.match(/^unused\d+$/);
459
460 me.isCreate = me.confid ? unused : true;
461
462 var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
463 confid: me.confid,
464 nodename: nodename,
465 unused: unused,
466 isCreate: me.isCreate,
467 });
468
469 if (unused) {
470 me.subject = gettext('Unused Disk');
471 } else if (me.isCreate) {
472 me.subject = gettext('Hard Disk');
473 } else {
474 me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
475 }
476
477 me.items = [ipanel];
478
479 me.callParent();
480 /* 'data' is assigned an empty array in same file, and here we
481 * use it like an object
482 */
483 me.load({
484 success: function(response, options) {
485 ipanel.setVMConfig(response.result.data);
486 if (me.confid) {
487 var value = response.result.data[me.confid];
488 var drive = PVE.Parser.parseQemuDrive(me.confid, value);
489 if (!drive) {
490 Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
491 me.close();
492 return;
493 }
494 ipanel.setDrive(drive);
495 me.isValid(); // trigger validation
496 }
497 },
498 });
499 },
500 });