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