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