]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/qemu/HardwareView.js
ui: efi/tpm: propagate disabled to child items
[pve-manager.git] / www / manager6 / qemu / HardwareView.js
CommitLineData
f97670f5 1Ext.define('PVE.qemu.HardwareView', {
bc0eaf58 2 extend: 'Proxmox.grid.PendingObjectGrid',
f97670f5
DM
3 alias: ['widget.PVE.qemu.HardwareView'],
4
ba93a9c6
DC
5 onlineHelp: 'qm_virtual_machines_settings',
6
f97670f5
DM
7 renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
8 var me = this;
9 var rows = me.rows;
10 var rowdef = rows[key] || {};
6a040799
DC
11 var iconCls = rowdef.iconCls;
12 var icon = '';
53e3ea84 13 var txt = rowdef.header || key;
f97670f5
DM
14
15 metaData.tdAttr = "valign=middle";
16
0ea3b849
TL
17 if (rowdef.isOnStorageBus) {
18 var value = me.getObjectValue(key, '', false);
19 if (value === '') {
20 value = me.getObjectValue(key, '', true);
21 }
22 if (value.match(/vm-.*-cloudinit/)) {
23 iconCls = 'cloud';
24 txt = rowdef.cloudheader;
25 } else if (value.match(/media=cdrom/)) {
26 metaData.tdCls = 'pve-itype-icon-cdrom';
27 return rowdef.cdheader;
28 }
29 }
30
f97670f5
DM
31 if (rowdef.tdCls) {
32 metaData.tdCls = rowdef.tdCls;
6a040799
DC
33 } else if (iconCls) {
34 icon = "<i class='pve-grid-fa fa fa-fw fa-" + iconCls + "'></i>";
35 metaData.tdCls += " pve-itype-fa";
f97670f5 36 }
03558174
TL
37
38 // only return icons in grid but not remove dialog
39 if (rowIndex !== undefined) {
40 return icon + txt;
41 } else {
42 return txt;
43 }
f97670f5
DM
44 },
45
8058410f 46 initComponent: function() {
f97670f5 47 var me = this;
f97670f5 48
96da47ba 49 const nodename = me.pveSelNode.data.node;
2a4971d8 50 if (!nodename) {
f97670f5
DM
51 throw "no node name specified";
52 }
53
96da47ba 54 const vmid = me.pveSelNode.data.vmid;
f97670f5
DM
55 if (!vmid) {
56 throw "no VM ID specified";
57 }
58
96da47ba
TL
59 const caps = Ext.state.Manager.get('GuiCap');
60 const diskCap = caps.vms['VM.Config.Disk'];
61 const cdromCap = caps.vms['VM.Config.CDROM'];
f97670f5 62
96da47ba 63 let rows = {
f97670f5
DM
64 memory: {
65 header: gettext('Memory'),
66 editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
67 never_delete: true,
72408db9 68 defaultValue: '512',
a8ea1b68 69 tdCls: 'pmx-itype-icon-memory',
cbe0b4b8 70 group: 2,
1d902252
DC
71 multiKey: ['memory', 'balloon', 'shares'],
72 renderer: function(value, metaData, record, ri, ci, store, pending) {
73 var res = '';
74
75 var max = me.getObjectValue('memory', 512, pending);
8058410f 76 var balloon = me.getObjectValue('balloon', undefined, pending);
1d902252
DC
77 var shares = me.getObjectValue('shares', undefined, pending);
78
8058410f 79 res = Proxmox.Utils.format_size(max*1024*1024);
1d902252
DC
80
81 if (balloon !== undefined && balloon > 0) {
82 res = Proxmox.Utils.format_size(balloon*1024*1024) + "/" + res;
83
84 if (shares) {
85 res += ' [shares=' + shares +']';
86 }
87 } else if (balloon === 0) {
88 res += ' [balloon=0]';
89 }
90 return res;
f6710aac 91 },
f97670f5
DM
92 },
93 sockets: {
94 header: gettext('Processors'),
95 never_delete: true,
53e3ea84 96 editor: caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']
8058410f 97 ? 'PVE.qemu.ProcessorEdit' : undefined,
a8ea1b68 98 tdCls: 'pmx-itype-icon-processor',
cbe0b4b8 99 group: 3,
72408db9 100 defaultValue: '1',
f97670f5
DM
101 multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
102 renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
f97670f5
DM
103 var sockets = me.getObjectValue('sockets', 1, pending);
104 var model = me.getObjectValue('cpu', undefined, pending);
105 var cores = me.getObjectValue('cores', 1, pending);
106 var numa = me.getObjectValue('numa', undefined, pending);
107 var vcpus = me.getObjectValue('vcpus', undefined, pending);
108 var cpulimit = me.getObjectValue('cpulimit', undefined, pending);
109 var cpuunits = me.getObjectValue('cpuunits', undefined, pending);
110
72408db9
EK
111 var res = Ext.String.format('{0} ({1} sockets, {2} cores)',
112 sockets*cores, sockets, cores);
113
f97670f5
DM
114 if (model) {
115 res += ' [' + model + ']';
116 }
117
118 if (numa) {
119 res += ' [numa=' + numa +']';
120 }
121
122 if (vcpus) {
123 res += ' [vcpus=' + vcpus +']';
124 }
125
126 if (cpulimit) {
127 res += ' [cpulimit=' + cpulimit +']';
128 }
129
130 if (cpuunits) {
131 res += ' [cpuunits=' + cpuunits +']';
132 }
133
134 return res;
f6710aac 135 },
f97670f5 136 },
439d7821
DC
137 bios: {
138 header: 'BIOS',
139 group: 4,
140 never_delete: true,
141 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
142 defaultValue: '',
143 iconCls: 'microchip',
f6710aac 144 renderer: PVE.Utils.render_qemu_bios,
439d7821 145 },
f97670f5
DM
146 vga: {
147 header: gettext('Display'),
148 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
149 never_delete: true,
a032a91a 150 iconCls: 'desktop',
8058410f 151 group: 5,
f97670f5 152 defaultValue: '',
f6710aac 153 renderer: PVE.Utils.render_kvm_vga_driver,
f97670f5 154 },
aae2273c
DC
155 machine: {
156 header: gettext('Machine'),
9dbddec7 157 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.MachineEdit' : undefined,
aae2273c
DC
158 iconCls: 'cogs',
159 never_delete: true,
160 group: 6,
161 defaultValue: '',
4a0ccbad
SR
162 renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
163 let ostype = me.getObjectValue('ostype', undefined, pending);
164 if (PVE.Utils.is_windows(ostype) &&
165 (!value || value === 'pc' || value === 'q35')) {
166 return value === 'q35' ? 'pc-q35-5.1' : 'pc-i440fx-5.1';
167 }
168 return PVE.Utils.render_qemu_machine(value);
169 },
aae2273c 170 },
439d7821
DC
171 scsihw: {
172 header: gettext('SCSI Controller'),
173 iconCls: 'database',
174 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined,
175 renderer: PVE.Utils.render_scsihw,
176 group: 7,
177 never_delete: true,
f6710aac 178 defaultValue: '',
439d7821 179 },
23dfddc6 180 vmstate: {
4b07ffdd 181 header: gettext('Hibernation VM State'),
23dfddc6 182 iconCls: 'download',
59bce287
TL
183 del_extra_msg: gettext('The saved VM state will be permanently lost.'),
184 group: 100,
23dfddc6 185 },
f97670f5 186 cores: {
f6710aac 187 visible: false,
f97670f5
DM
188 },
189 cpu: {
f6710aac 190 visible: false,
f97670f5
DM
191 },
192 numa: {
f6710aac 193 visible: false,
f97670f5
DM
194 },
195 balloon: {
f6710aac 196 visible: false,
f97670f5
DM
197 },
198 hotplug: {
f6710aac 199 visible: false,
f97670f5
DM
200 },
201 vcpus: {
f6710aac 202 visible: false,
f97670f5
DM
203 },
204 cpuunits: {
f6710aac 205 visible: false,
f97670f5
DM
206 },
207 cpulimit: {
f6710aac 208 visible: false,
3b37ab6d 209 },
1d902252 210 shares: {
f6710aac
TL
211 visible: false,
212 },
4a0ccbad
SR
213 ostype: {
214 visible: false,
215 },
f97670f5
DM
216 };
217
abe824aa 218 PVE.Utils.forEachBus(undefined, function(type, id) {
f8205155 219 let confid = type + id;
f97670f5 220 rows[confid] = {
8d554444 221 group: 10,
a032a91a 222 iconCls: 'hdd-o',
f97670f5 223 editor: 'PVE.qemu.HDEdit',
0ea3b849 224 isOnStorageBus: true,
f97670f5 225 header: gettext('Hard Disk') + ' (' + confid +')',
b38d0810 226 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')',
f6710aac 227 cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')',
f97670f5 228 };
abe824aa 229 });
f8205155
AL
230 for (let i = 0; i < PVE.Utils.hardware_counts.net; i++) {
231 let confid = "net" + i.toString();
f97670f5 232 rows[confid] = {
8d554444 233 group: 15,
cbe0b4b8 234 order: i,
a032a91a 235 iconCls: 'exchange',
f97670f5 236 editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
ef725143 237 never_delete: !caps.vms['VM.Config.Network'],
f6710aac 238 header: gettext('Network Device') + ' (' + confid +')',
f97670f5
DM
239 };
240 }
194c9b09 241 rows.efidisk0 = {
8d554444 242 group: 20,
a032a91a 243 iconCls: 'hdd-o',
194c9b09 244 editor: null,
ef725143 245 never_delete: !caps.vms['VM.Config.Disk'],
f6710aac 246 header: gettext('EFI Disk'),
194c9b09 247 };
6d084964
SR
248 rows.tpmstate0 = {
249 group: 22,
250 iconCls: 'hdd-o',
251 editor: null,
252 never_delete: !caps.vms['VM.Config.Disk'],
253 header: gettext('TPM State'),
254 };
f8205155
AL
255 for (let i = 0; i < PVE.Utils.hardware_counts.usb; i++) {
256 let confid = "usb" + i.toString();
fa522e48 257 rows[confid] = {
8d554444 258 group: 25,
cbe0b4b8 259 order: i,
fa9cdaa1 260 iconCls: 'usb',
2668ba64 261 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
ef725143 262 never_delete: !caps.nodes['Sys.Console'],
f6710aac 263 header: gettext('USB Device') + ' (' + confid + ')',
fa522e48
DC
264 };
265 }
f8205155
AL
266 for (let i = 0; i < PVE.Utils.hardware_counts.hostpci; i++) {
267 let confid = "hostpci" + i.toString();
ec3d8372 268 rows[confid] = {
8d554444 269 group: 30,
cbe0b4b8 270 order: i,
ec3d8372 271 tdCls: 'pve-itype-icon-pci',
ef725143 272 never_delete: !caps.nodes['Sys.Console'],
25a1063a 273 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
f6710aac 274 header: gettext('PCI Device') + ' (' + confid + ')',
ec3d8372
DC
275 };
276 }
f8205155
AL
277 for (let i = 0; i < PVE.Utils.hardware_counts.serial; i++) {
278 let confid = "serial" + i.toString();
a488da8c 279 rows[confid] = {
8d554444 280 group: 35,
cbe0b4b8 281 order: i,
a488da8c 282 tdCls: 'pve-itype-icon-serial',
ef725143 283 never_delete: !caps.nodes['Sys.Console'],
f6710aac 284 header: gettext('Serial Port') + ' (' + confid + ')',
a488da8c
DC
285 };
286 }
171ca270
AL
287 rows.audio0 = {
288 group: 40,
289 iconCls: 'volume-up',
290 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined,
ef725143 291 never_delete: !caps.vms['VM.Config.HWType'],
f6710aac 292 header: gettext('Audio Device'),
171ca270 293 };
f8205155 294 for (let i = 0; i < 256; i++) {
72408db9 295 rows["unused" + i.toString()] = {
cd86ab24 296 group: 99,
cbe0b4b8 297 order: i,
0ea3b849 298 iconCls: 'hdd-o',
b2a5aa18 299 del_extra_msg: gettext('This will permanently erase all data.'),
f97670f5 300 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
f6710aac 301 header: gettext('Unused Disk') + ' ' + i.toString(),
f97670f5
DM
302 };
303 }
6c1d8ead
SR
304 rows.rng0 = {
305 group: 45,
4ba4c7c6 306 tdCls: 'pve-itype-icon-die',
6c1d8ead 307 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.RNGEdit' : undefined,
ef725143 308 never_delete: !caps.nodes['Sys.Console'],
f6710aac 309 header: gettext("VirtIO RNG"),
6c1d8ead 310 };
f97670f5
DM
311
312 var sorterFn = function(rec1, rec2) {
313 var v1 = rec1.data.key;
314 var v2 = rec2.data.key;
315 var g1 = rows[v1].group || 0;
316 var g2 = rows[v2].group || 0;
cbe0b4b8
DL
317 var order1 = rows[v1].order || 0;
318 var order2 = rows[v2].order || 0;
319
53e3ea84 320 if (g1 - g2 !== 0) {
cbe0b4b8
DL
321 return g1 - g2;
322 }
2a4971d8 323
53e3ea84 324 if (order1 - order2 !== 0) {
cbe0b4b8
DL
325 return order1 - order2;
326 }
327
328 if (v1 > v2) {
329 return 1;
330 } else if (v1 < v2) {
331 return -1;
332 } else {
333 return 0;
334 }
f97670f5
DM
335 };
336
f97670f5
DM
337 var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
338
339 var sm = Ext.create('Ext.selection.RowModel', {});
340
341 var run_editor = function() {
342 var rec = sm.getSelection()[0];
343 if (!rec) {
344 return;
345 }
346
347 var rowdef = rows[rec.data.key];
348 if (!rowdef.editor) {
349 return;
350 }
351
352 var editor = rowdef.editor;
0ea3b849
TL
353 if (rowdef.isOnStorageBus) {
354 var value = me.getObjectValue(rec.data.key, '', true);
ab1ee352
DC
355 if (value.match(/vm-.*-cloudinit/)) {
356 return;
357 } else if (value.match(/media=cdrom/)) {
f97670f5 358 editor = 'PVE.qemu.CDEdit';
10525ed7
TL
359 } else if (!diskCap) {
360 return;
f97670f5
DM
361 }
362 }
363
364 var win;
365
366 if (Ext.isString(editor)) {
367 win = Ext.create(editor, {
368 pveSelNode: me.pveSelNode,
369 confid: rec.data.key,
f6710aac 370 url: '/api2/extjs/' + baseurl,
f97670f5
DM
371 });
372 } else {
373 var config = Ext.apply({
374 pveSelNode: me.pveSelNode,
375 confid: rec.data.key,
f6710aac 376 url: '/api2/extjs/' + baseurl,
f97670f5
DM
377 }, rowdef.editor);
378 win = Ext.createWidget(rowdef.editor.xtype, config);
379 win.load();
380 }
381
382 win.show();
18f57a23 383 win.on('destroy', me.reload, me);
f97670f5
DM
384 };
385
f97670f5
DM
386 var run_resize = function() {
387 var rec = sm.getSelection()[0];
388 if (!rec) {
389 return;
390 }
391
392 var win = Ext.create('PVE.window.HDResize', {
393 disk: rec.data.key,
394 nodename: nodename,
f6710aac 395 vmid: vmid,
f97670f5
DM
396 });
397
398 win.show();
399
18f57a23 400 win.on('destroy', me.reload, me);
f97670f5
DM
401 };
402
f97670f5
DM
403 var run_move = function() {
404 var rec = sm.getSelection()[0];
405 if (!rec) {
406 return;
407 }
408
409 var win = Ext.create('PVE.window.HDMove', {
410 disk: rec.data.key,
411 nodename: nodename,
f6710aac 412 vmid: vmid,
f97670f5
DM
413 });
414
415 win.show();
416
18f57a23 417 win.on('destroy', me.reload, me);
f97670f5
DM
418 };
419
5720fafa 420 var edit_btn = new Proxmox.button.Button({
f97670f5
DM
421 text: gettext('Edit'),
422 selModel: sm,
423 disabled: true,
f6710aac 424 handler: run_editor,
f97670f5
DM
425 });
426
5720fafa 427 var resize_btn = new Proxmox.button.Button({
f97670f5
DM
428 text: gettext('Resize disk'),
429 selModel: sm,
430 disabled: true,
f6710aac 431 handler: run_resize,
f97670f5
DM
432 });
433
5720fafa 434 var move_btn = new Proxmox.button.Button({
f97670f5
DM
435 text: gettext('Move disk'),
436 selModel: sm,
437 disabled: true,
f6710aac 438 handler: run_move,
f97670f5
DM
439 });
440
5720fafa 441 var remove_btn = new Proxmox.button.Button({
f97670f5 442 text: gettext('Remove'),
877fce54
EK
443 defaultText: gettext('Remove'),
444 altText: gettext('Detach'),
f97670f5
DM
445 selModel: sm,
446 disabled: true,
447 dangerous: true,
3b2e557f 448 RESTMethod: 'PUT',
f97670f5 449 confirmMsg: function(rec) {
ff5a8a9b
EK
450 var warn = gettext('Are you sure you want to remove entry {0}');
451 if (this.text === this.altText) {
452 warn = gettext('Are you sure you want to detach entry {0}');
453 }
b2a5aa18
TL
454 var key = rec.data.key;
455 var entry = rows[key];
ff5a8a9b 456
b2a5aa18 457 var rendered = me.renderKey(key, {}, rec);
83b4bfc3 458 var msg = Ext.String.format(warn, "'" + rendered + "'");
ff5a8a9b 459
b2a5aa18
TL
460 if (entry.del_extra_msg) {
461 msg += '<br>' + entry.del_extra_msg;
f97670f5
DM
462 }
463
464 return msg;
465 },
466 handler: function(b, e, rec) {
e7ade592 467 Proxmox.Utils.API2Request({
f97670f5
DM
468 url: '/api2/extjs/' + baseurl,
469 waitMsgTarget: me,
3b2e557f 470 method: b.RESTMethod,
f97670f5 471 params: {
f6710aac 472 'delete': rec.data.key,
f97670f5 473 },
18f57a23 474 callback: () => me.reload(),
8058410f 475 failure: function(response, opts) {
f97670f5 476 Ext.Msg.alert('Error', response.htmlStatus);
3b2e557f
TM
477 },
478 success: function(response, options) {
479 if (b.RESTMethod === 'POST') {
480 var upid = response.result.data;
481 var win = Ext.create('Proxmox.window.TaskProgress', {
482 upid: upid,
483 listeners: {
18f57a23 484 destroy: () => me.reload(),
f6710aac 485 },
3b2e557f
TM
486 });
487 win.show();
488 }
f6710aac 489 },
f97670f5 490 });
877fce54
EK
491 },
492 listeners: {
493 render: function(btn) {
897c1fc8
TL
494 // hack: calculate the max button width on first display to prevent the whole
495 // toolbar to move when we switch between the "Remove" and "Detach" labels
877fce54
EK
496 var def = btn.getSize().width;
497
498 btn.setText(btn.altText);
499 var alt = btn.getSize().width;
500
501 btn.setText(btn.defaultText);
502
503 var optimal = alt > def ? alt : def;
504 btn.setSize({ width: optimal });
f6710aac
TL
505 },
506 },
f97670f5
DM
507 });
508
591efb67 509 var revert_btn = new PVE.button.PendingRevert({
3ee15859 510 apiurl: '/api2/extjs/' + baseurl,
f97670f5
DM
511 });
512
f6710aac 513 var efidisk_menuitem = Ext.create('Ext.menu.Item', {
3b37ab6d 514 text: gettext('EFI Disk'),
a032a91a 515 iconCls: 'fa fa-fw fa-hdd-o black',
3b37ab6d
DC
516 disabled: !caps.vms['VM.Config.Disk'],
517 handler: function() {
c570b6a9
TL
518 let bios = me.rstore.getData().map.bios;
519 let usesEFI = bios && (bios.data.value === 'ovmf' || bios.data.pending === 'ovmf');
1535df62 520
c570b6a9
TL
521 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
522 url: '/api2/extjs/' + baseurl,
523 pveSelNode: me.pveSelNode,
524 usesEFI: usesEFI,
525 });
18f57a23 526 win.on('destroy', me.reload, me);
c570b6a9 527 win.show();
f6710aac 528 },
3b37ab6d
DC
529 });
530
67847d54 531 let counts = {};
53e3ea84 532 let isAtLimit = (type) => counts[type] >= PVE.Utils.hardware_counts[type];
67847d54 533
f97670f5 534 var set_button_status = function() {
f8205155
AL
535 var selection_model = me.getSelectionModel();
536 var rec = selection_model.getSelection()[0];
f97670f5 537
67847d54
DC
538 // en/disable hardwarebuttons
539 counts = {};
b38d0810 540 var hasCloudInit = false;
8058410f 541 me.rstore.getData().items.forEach(function(item) {
67847d54
DC
542 if (!hasCloudInit && (
543 /vm-.*-cloudinit/.test(item.data.value) ||
544 /vm-.*-cloudinit/.test(item.data.pending)
545 )) {
b38d0810 546 hasCloudInit = true;
67847d54
DC
547 return;
548 }
549
550 let match = item.id.match(/^([^\d]+)\d+$/);
551 let type;
552 if (match && PVE.Utils.hardware_counts[match[1]] !== undefined) {
553 type = match[1];
554 } else {
555 return;
b38d0810 556 }
67847d54
DC
557
558 counts[type] = (counts[type] || 0) + 1;
2668ba64 559 });
3097170d
TL
560
561 // heuristic only for disabling some stuff, the backend has the final word.
66abfe6a
AL
562 const noSysConsolePerm = !caps.nodes['Sys.Console'];
563 const noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType'];
564 const noVMConfigNetPerm = !caps.vms['VM.Config.Network'];
90b8c622 565 const noVMConfigDiskPerm = !caps.vms['VM.Config.Disk'];
67847d54 566
67847d54
DC
567 me.down('#addusb').setDisabled(noSysConsolePerm || isAtLimit('usb'));
568 me.down('#addpci').setDisabled(noSysConsolePerm || isAtLimit('hostpci'));
569 me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || isAtLimit('audio'));
570 me.down('#addserial').setDisabled(noVMConfigHWTypePerm || isAtLimit('serial'));
571 me.down('#addnet').setDisabled(noVMConfigNetPerm || isAtLimit('net'));
6c1d8ead 572 me.down('#addrng').setDisabled(noSysConsolePerm || isAtLimit('rng'));
90b8c622 573 efidisk_menuitem.setDisabled(noVMConfigDiskPerm || isAtLimit('efidisk'));
6d084964 574 me.down('#addtpmstate').setDisabled(noSysConsolePerm || isAtLimit('tpmstate'));
3097170d 575 me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit);
3b37ab6d 576
f97670f5
DM
577 if (!rec) {
578 remove_btn.disable();
579 edit_btn.disable();
580 resize_btn.disable();
581 move_btn.disable();
f97670f5
DM
582 revert_btn.disable();
583 return;
584 }
96da47ba
TL
585 const key = rec.data.key;
586 const value = rec.data.value;
587 const row = rows[key];
f97670f5 588
96da47ba
TL
589 const deleted = !!rec.data.delete;
590 const pending = deleted || me.hasPendingChanges(key);
f97670f5 591
96da47ba
TL
592 const isCloudInit = value && value.toString().match(/vm-.*-cloudinit/);
593 const isCDRom = value && !!value.toString().match(/media=cdrom/) && !isCloudInit;
b38d0810 594
96da47ba
TL
595 const isUnusedDisk = key.match(/^unused\d+/);
596 const isUsedDisk = !isUnusedDisk && row.isOnStorageBus && !isCDRom && !isCloudInit;
597 const isDisk = isCloudInit || isUnusedDisk || isUsedDisk;
598 const isEfi = key === 'efidisk0';
6d084964 599 const tpmMoveable = key === 'tpmstate0' && !me.pveSelNode.data.running;
3b37ab6d 600
f87ddae5 601 remove_btn.setDisabled(
a1e25c47
AL
602 deleted ||
603 row.never_delete ||
604 (isCDRom && !cdromCap) ||
605 (isDisk && !diskCap),
f87ddae5 606 );
53e3ea84 607 remove_btn.setText(isUsedDisk && !isCloudInit ? remove_btn.altText : remove_btn.defaultText);
3b2e557f 608 remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT';
f97670f5 609
a1e25c47
AL
610 edit_btn.setDisabled(
611 deleted ||
612 !row.editor ||
613 isCloudInit ||
614 (isCDRom && !cdromCap) ||
615 (isDisk && !diskCap),
616 );
f97670f5 617
d35b5b2a 618 resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
f97670f5 619
6d084964 620 move_btn.setDisabled(pending || !(isUsedDisk || isEfi || tpmMoveable) || !diskCap);
f97670f5 621
f97670f5 622 revert_btn.setDisabled(!pending);
f97670f5
DM
623 };
624
f09dd6c1 625 Ext.apply(me, {
87f53aa8 626 url: `/api2/json/nodes/${nodename}/qemu/${vmid}/pending`,
f97670f5
DM
627 interval: 5000,
628 selModel: sm,
b08ae044 629 run_editor: run_editor,
2a4971d8 630 tbar: [
f97670f5
DM
631 {
632 text: gettext('Add'),
633 menu: new Ext.menu.Menu({
e08c2321 634 cls: 'pve-add-hw-menu',
f97670f5
DM
635 items: [
636 {
637 text: gettext('Hard Disk'),
a032a91a 638 iconCls: 'fa fa-fw fa-hdd-o black',
f97670f5
DM
639 disabled: !caps.vms['VM.Config.Disk'],
640 handler: function() {
96da47ba 641 let win = Ext.create('PVE.qemu.HDEdit', {
f97670f5 642 url: '/api2/extjs/' + baseurl,
f6710aac 643 pveSelNode: me.pveSelNode,
f97670f5 644 });
18f57a23 645 win.on('destroy', me.reload, me);
f97670f5 646 win.show();
f6710aac 647 },
f97670f5
DM
648 },
649 {
650 text: gettext('CD/DVD Drive'),
651 iconCls: 'pve-itype-icon-cdrom',
a1e25c47 652 disabled: !caps.vms['VM.Config.CDROM'],
f97670f5 653 handler: function() {
96da47ba 654 let win = Ext.create('PVE.qemu.CDEdit', {
f97670f5 655 url: '/api2/extjs/' + baseurl,
f6710aac 656 pveSelNode: me.pveSelNode,
f97670f5 657 });
18f57a23 658 win.on('destroy', me.reload, me);
f97670f5 659 win.show();
f6710aac 660 },
f97670f5
DM
661 },
662 {
663 text: gettext('Network Device'),
67847d54 664 itemId: 'addnet',
a032a91a 665 iconCls: 'fa fa-fw fa-exchange black',
f97670f5
DM
666 disabled: !caps.vms['VM.Config.Network'],
667 handler: function() {
668 var win = Ext.create('PVE.qemu.NetworkEdit', {
669 url: '/api2/extjs/' + baseurl,
b2991833 670 pveSelNode: me.pveSelNode,
f6710aac 671 isCreate: true,
f97670f5 672 });
18f57a23 673 win.on('destroy', me.reload, me);
f97670f5 674 win.show();
f6710aac 675 },
3b37ab6d 676 },
2668ba64 677 efidisk_menuitem,
6d084964
SR
678 {
679 text: gettext('TPM State'),
680 itemId: 'addtpmstate',
681 iconCls: 'fa fa-fw fa-hdd-o black',
682 disabled: !caps.vms['VM.Config.Disk'],
683 handler: function() {
684 var win = Ext.create('PVE.qemu.TPMDiskEdit', {
685 url: '/api2/extjs/' + baseurl,
686 pveSelNode: me.pveSelNode,
687 });
688 win.on('destroy', me.reload, me);
689 win.show();
690 },
691 },
2668ba64
DC
692 {
693 text: gettext('USB Device'),
694 itemId: 'addusb',
a032a91a 695 iconCls: 'fa fa-fw fa-usb black',
2668ba64
DC
696 disabled: !caps.nodes['Sys.Console'],
697 handler: function() {
698 var win = Ext.create('PVE.qemu.USBEdit', {
699 url: '/api2/extjs/' + baseurl,
f6710aac 700 pveSelNode: me.pveSelNode,
2668ba64 701 });
18f57a23 702 win.on('destroy', me.reload, me);
2668ba64 703 win.show();
f6710aac 704 },
b38d0810 705 },
25a1063a
DC
706 {
707 text: gettext('PCI Device'),
708 itemId: 'addpci',
709 iconCls: 'pve-itype-icon-pci',
710 disabled: !caps.nodes['Sys.Console'],
711 handler: function() {
712 var win = Ext.create('PVE.qemu.PCIEdit', {
713 url: '/api2/extjs/' + baseurl,
f6710aac 714 pveSelNode: me.pveSelNode,
25a1063a 715 });
18f57a23 716 win.on('destroy', me.reload, me);
25a1063a 717 win.show();
f6710aac 718 },
25a1063a 719 },
37977fa5
TL
720 {
721 text: gettext('Serial Port'),
722 itemId: 'addserial',
723 iconCls: 'pve-itype-icon-serial',
724 disabled: !caps.vms['VM.Config.Options'],
725 handler: function() {
726 var win = Ext.create('PVE.qemu.SerialEdit', {
f6710aac 727 url: '/api2/extjs/' + baseurl,
37977fa5 728 });
18f57a23 729 win.on('destroy', me.reload, me);
37977fa5 730 win.show();
f6710aac 731 },
37977fa5 732 },
b38d0810
DC
733 {
734 text: gettext('CloudInit Drive'),
735 itemId: 'addci',
a032a91a 736 iconCls: 'fa fa-fw fa-cloud black',
b38d0810
DC
737 disabled: !caps.nodes['Sys.Console'],
738 handler: function() {
739 var win = Ext.create('PVE.qemu.CIDriveEdit', {
740 url: '/api2/extjs/' + baseurl,
f6710aac 741 pveSelNode: me.pveSelNode,
b38d0810 742 });
18f57a23 743 win.on('destroy', me.reload, me);
b38d0810 744 win.show();
f6710aac 745 },
171ca270
AL
746 },
747 {
748 text: gettext('Audio Device'),
749 itemId: 'addaudio',
a032a91a 750 iconCls: 'fa fa-fw fa-volume-up black',
171ca270
AL
751 disabled: !caps.vms['VM.Config.HWType'],
752 handler: function() {
753 var win = Ext.create('PVE.qemu.AudioEdit', {
754 url: '/api2/extjs/' + baseurl,
755 isCreate: true,
f6710aac 756 isAdd: true,
171ca270 757 });
18f57a23 758 win.on('destroy', me.reload, me);
171ca270 759 win.show();
f6710aac 760 },
6c1d8ead
SR
761 },
762 {
763 text: gettext("VirtIO RNG"),
764 itemId: 'addrng',
4ba4c7c6 765 iconCls: 'pve-itype-icon-die',
6c1d8ead
SR
766 disabled: !caps.nodes['Sys.Console'],
767 handler: function() {
768 var win = Ext.create('PVE.qemu.RNGEdit', {
769 url: '/api2/extjs/' + baseurl,
770 isCreate: true,
f6710aac 771 isAdd: true,
6c1d8ead
SR
772 });
773 win.on('destroy', me.reload, me);
774 win.show();
f6710aac
TL
775 },
776 },
777 ],
778 }),
fa522e48 779 },
f97670f5
DM
780 remove_btn,
781 edit_btn,
782 resize_btn,
783 move_btn,
f6710aac 784 revert_btn,
f97670f5
DM
785 ],
786 rows: rows,
787 sorterFn: sorterFn,
788 listeners: {
789 itemdblclick: run_editor,
f6710aac
TL
790 selectionchange: set_button_status,
791 },
f97670f5
DM
792 });
793
794 me.callParent();
795
6386068d
TL
796 me.on('activate', me.rstore.startUpdate, me.rstore);
797 me.on('destroy', me.rstore.stopUpdate, me.rstore);
f97670f5 798
6386068d 799 me.mon(me.getStore(), 'datachanged', set_button_status, me);
f6710aac 800 },
f97670f5 801});