]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HDEdit.js
ui: qemu: disk edit: refactor to more declarative style using bindings
[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.cache, 'cache');
94
95 var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr'];
96 Ext.Array.each(names, function(name) {
97 var burst_name = name + '_max';
98 PVE.Utils.propertyStringSet(me.drive, values[name], name);
99 PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name);
100 });
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
155 values.mbps_rd = drive.mbps_rd;
156 values.mbps_wr = drive.mbps_wr;
157 values.iops_rd = drive.iops_rd;
158 values.iops_wr = drive.iops_wr;
159 values.mbps_rd_max = drive.mbps_rd_max;
160 values.mbps_wr_max = drive.mbps_wr_max;
161 values.iops_rd_max = drive.iops_rd_max;
162 values.iops_wr_max = drive.iops_wr_max;
163
164 me.setValues(values);
165 },
166
167 setNodename: function(nodename) {
168 var me = this;
169 me.down('#hdstorage').setNodename(nodename);
170 me.down('#hdimage').setStorage(undefined, nodename);
171 },
172
173 hasAdvanced: true,
174
175 initComponent: function() {
176 var me = this;
177
178 var labelWidth = 140;
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 labelWidth: labelWidth,
261 name: 'ssd',
262 clearOnDisable: true,
263 bind: {
264 disabled: '{!isVirtIO}',
265 },
266 },
267 {
268 xtype: 'proxmoxcheckbox',
269 name: 'iothread',
270 fieldLabel: 'IO thread',
271 labelWidth: labelWidth,
272 clearOnDisable: true,
273 bind: {
274 disabled: '{!isVirtIO && !isSCSI}',
275 },
276 },
277 );
278
279 advancedColumn2.push(
280 {
281 xtype: 'proxmoxcheckbox',
282 fieldLabel: gettext('Backup'),
283 autoEl: {
284 tag: 'div',
285 'data-qtip': gettext('Include volume in backup job'),
286 },
287 labelWidth: labelWidth,
288 name: 'backup',
289 bind: {
290 value: '{isIncludedInBackup}',
291 },
292 },
293 {
294 xtype: 'proxmoxcheckbox',
295 fieldLabel: gettext('Skip replication'),
296 labelWidth: labelWidth,
297 name: 'noreplicate',
298 },
299 );
300
301 let bwColumn1 = [
302 {
303 xtype: 'numberfield',
304 name: 'mbps_rd',
305 minValue: 1,
306 step: 1,
307 fieldLabel: gettext('Read limit') + ' (MB/s)',
308 labelWidth: labelWidth,
309 emptyText: gettext('unlimited'),
310 },
311 {
312 xtype: 'numberfield',
313 name: 'mbps_wr',
314 minValue: 1,
315 step: 1,
316 fieldLabel: gettext('Write limit') + ' (MB/s)',
317 labelWidth: labelWidth,
318 emptyText: gettext('unlimited'),
319 },
320 {
321 xtype: 'proxmoxintegerfield',
322 name: 'iops_rd',
323 minValue: 10,
324 step: 10,
325 fieldLabel: gettext('Read limit') + ' (ops/s)',
326 labelWidth: labelWidth,
327 emptyText: gettext('unlimited'),
328 },
329 {
330 xtype: 'proxmoxintegerfield',
331 name: 'iops_wr',
332 minValue: 10,
333 step: 10,
334 fieldLabel: gettext('Write limit') + ' (ops/s)',
335 labelWidth: labelWidth,
336 emptyText: gettext('unlimited'),
337 },
338 ];
339
340 let bwColumn2 = [
341 {
342 xtype: 'numberfield',
343 name: 'mbps_rd_max',
344 minValue: 1,
345 step: 1,
346 fieldLabel: gettext('Read max burst') + ' (MB)',
347 labelWidth: labelWidth,
348 emptyText: gettext('default'),
349 },
350 {
351 xtype: 'numberfield',
352 name: 'mbps_wr_max',
353 minValue: 1,
354 step: 1,
355 fieldLabel: gettext('Write max burst') + ' (MB)',
356 labelWidth: labelWidth,
357 emptyText: gettext('default'),
358 },
359 {
360 xtype: 'proxmoxintegerfield',
361 name: 'iops_rd_max',
362 minValue: 10,
363 step: 10,
364 fieldLabel: gettext('Read max burst') + ' (ops)',
365 labelWidth: labelWidth,
366 emptyText: gettext('default'),
367 },
368 {
369 xtype: 'proxmoxintegerfield',
370 name: 'iops_wr_max',
371 minValue: 10,
372 step: 10,
373 fieldLabel: gettext('Write max burst') + ' (ops)',
374 labelWidth: labelWidth,
375 emptyText: gettext('default'),
376 },
377 ];
378
379 me.items = [
380 {
381 xtype: 'tabpanel',
382 plain: true,
383 bodyPadding: 10,
384 border: 0,
385 items: [
386 {
387 title: gettext('Disk'),
388 xtype: 'inputpanel',
389 reference: 'diskpanel',
390 column1,
391 column2,
392 advancedColumn1,
393 advancedColumn2,
394 showAdvanced: me.showAdvanced,
395 getValues: () => ({}),
396 },
397 {
398 title: gettext('Bandwidth'),
399 xtype: 'inputpanel',
400 reference: 'bwpanel',
401 column1: bwColumn1,
402 column2: bwColumn2,
403 showAdvanced: me.showAdvanced,
404 getValues: () => ({}),
405 },
406 ],
407 },
408 ];
409
410 me.callParent();
411 },
412
413 setAdvancedVisible: function(visible) {
414 this.lookup('diskpanel').setAdvancedVisible(visible);
415 this.lookup('bwpanel').setAdvancedVisible(visible);
416 },
417 });
418
419 Ext.define('PVE.qemu.HDEdit', {
420 extend: 'Proxmox.window.Edit',
421
422 isAdd: true,
423
424 backgroundDelay: 5,
425
426 width: 600,
427 bodyPadding: 0,
428
429 initComponent: function() {
430 var me = this;
431
432 var nodename = me.pveSelNode.data.node;
433 if (!nodename) {
434 throw "no node name specified";
435 }
436
437 var unused = me.confid && me.confid.match(/^unused\d+$/);
438
439 me.isCreate = me.confid ? unused : true;
440
441 var ipanel = Ext.create('PVE.qemu.HDInputPanel', {
442 confid: me.confid,
443 nodename: nodename,
444 unused: unused,
445 isCreate: me.isCreate,
446 });
447
448 if (unused) {
449 me.subject = gettext('Unused Disk');
450 } else if (me.isCreate) {
451 me.subject = gettext('Hard Disk');
452 } else {
453 me.subject = gettext('Hard Disk') + ' (' + me.confid + ')';
454 }
455
456 me.items = [ipanel];
457
458 me.callParent();
459 /* 'data' is assigned an empty array in same file, and here we
460 * use it like an object
461 */
462 me.load({
463 success: function(response, options) {
464 ipanel.setVMConfig(response.result.data);
465 if (me.confid) {
466 var value = response.result.data[me.confid];
467 var drive = PVE.Parser.parseQemuDrive(me.confid, value);
468 if (!drive) {
469 Ext.Msg.alert(gettext('Error'), 'Unable to parse drive options');
470 me.close();
471 return;
472 }
473 ipanel.setDrive(drive);
474 me.isValid(); // trigger validation
475 }
476 },
477 });
478 },
479 });