]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/qemu/HardwareView.js
Add edit dialog for audio device
[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
17 if (rowdef.tdCls) {
18 metaData.tdCls = rowdef.tdCls;
19 if (rowdef.tdCls == 'pve-itype-icon-storage') {
cdf5bec2
DL
20 var value = me.getObjectValue(key, '', false);
21 if (value === '') {
22 value = me.getObjectValue(key, '', true);
23 }
b38d0810 24 if (value.match(/vm-.*-cloudinit/)) {
7b7ca5e1
TL
25 icon = "<i class='pve-grid-fa fa fa-fw fa-cloud'></i>";
26 metaData.tdCls = " pve-itype-fa";
27 txt = rowdef.cloudheader;
b38d0810 28 } else if (value.match(/media=cdrom/)) {
f97670f5
DM
29 metaData.tdCls = 'pve-itype-icon-cdrom';
30 return rowdef.cdheader;
31 }
32 }
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,
152 tdCls: 'pve-itype-icon-display',
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 },
f97670f5
DM
188 cores: {
189 visible: false
190 },
191 cpu: {
192 visible: false
193 },
194 numa: {
195 visible: false
196 },
197 balloon: {
198 visible: false
199 },
200 hotplug: {
201 visible: false
202 },
203 vcpus: {
204 visible: false
205 },
206 cpuunits: {
207 visible: false
208 },
209 cpulimit: {
210 visible: false
3b37ab6d 211 },
1d902252
DC
212 shares: {
213 visible: false
f97670f5 214 }
f97670f5 215 };
aae2273c 216 /*jslint confusion: false */
f97670f5 217
abe824aa
DC
218 PVE.Utils.forEachBus(undefined, function(type, id) {
219 var confid = type + id;
f97670f5 220 rows[confid] = {
8d554444 221 group: 10,
f97670f5
DM
222 tdCls: 'pve-itype-icon-storage',
223 editor: 'PVE.qemu.HDEdit',
224 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
225 header: gettext('Hard Disk') + ' (' + confid +')',
b38d0810
DC
226 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')',
227 cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')'
f97670f5 228 };
abe824aa 229 });
f97670f5 230 for (i = 0; i < 32; i++) {
72408db9 231 confid = "net" + i.toString();
f97670f5 232 rows[confid] = {
8d554444 233 group: 15,
cbe0b4b8 234 order: i,
f97670f5
DM
235 tdCls: 'pve-itype-icon-network',
236 editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
237 never_delete: caps.vms['VM.Config.Network'] ? false : true,
238 header: gettext('Network Device') + ' (' + confid +')'
239 };
240 }
194c9b09 241 rows.efidisk0 = {
8d554444 242 group: 20,
194c9b09
DC
243 tdCls: 'pve-itype-icon-storage',
244 editor: null,
245 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
246 header: gettext('EFI Disk')
247 };
fa522e48 248 for (i = 0; i < 5; i++) {
72408db9 249 confid = "usb" + i.toString();
fa522e48 250 rows[confid] = {
8d554444 251 group: 25,
cbe0b4b8 252 order: i,
fa522e48 253 tdCls: 'pve-itype-icon-usb',
2668ba64
DC
254 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
255 never_delete: caps.nodes['Sys.Console'] ? false : true,
fa522e48
DC
256 header: gettext('USB Device') + ' (' + confid + ')'
257 };
258 }
ec3d8372 259 for (i = 0; i < 4; i++) {
72408db9 260 confid = "hostpci" + i.toString();
ec3d8372 261 rows[confid] = {
8d554444 262 group: 30,
cbe0b4b8 263 order: i,
ec3d8372
DC
264 tdCls: 'pve-itype-icon-pci',
265 never_delete: caps.nodes['Sys.Console'] ? false : true,
25a1063a 266 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
ec3d8372
DC
267 header: gettext('PCI Device') + ' (' + confid + ')'
268 };
269 }
a488da8c
DC
270 for (i = 0; i < 4; i++) {
271 confid = "serial" + i.toString();
272 rows[confid] = {
8d554444 273 group: 35,
cbe0b4b8 274 order: i,
a488da8c
DC
275 tdCls: 'pve-itype-icon-serial',
276 never_delete: caps.nodes['Sys.Console'] ? false : true,
277 header: gettext('Serial Port') + ' (' + confid + ')'
278 };
279 }
afa19975 280 for (i = 0; i < 256; i++) {
72408db9 281 rows["unused" + i.toString()] = {
cd86ab24 282 group: 99,
cbe0b4b8 283 order: i,
f97670f5
DM
284 tdCls: 'pve-itype-icon-storage',
285 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
72408db9 286 header: gettext('Unused Disk') + ' ' + i.toString()
f97670f5
DM
287 };
288 }
289
290 var sorterFn = function(rec1, rec2) {
291 var v1 = rec1.data.key;
292 var v2 = rec2.data.key;
293 var g1 = rows[v1].group || 0;
294 var g2 = rows[v2].group || 0;
cbe0b4b8
DL
295 var order1 = rows[v1].order || 0;
296 var order2 = rows[v2].order || 0;
297
298 if ((g1 - g2) !== 0) {
299 return g1 - g2;
300 }
f97670f5 301
cbe0b4b8
DL
302 if ((order1 - order2) !== 0) {
303 return order1 - order2;
304 }
305
306 if (v1 > v2) {
307 return 1;
308 } else if (v1 < v2) {
309 return -1;
310 } else {
311 return 0;
312 }
f97670f5
DM
313 };
314
315 var reload = function() {
316 me.rstore.load();
317 };
318
319 var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
320
321 var sm = Ext.create('Ext.selection.RowModel', {});
322
323 var run_editor = function() {
324 var rec = sm.getSelection()[0];
325 if (!rec) {
326 return;
327 }
328
329 var rowdef = rows[rec.data.key];
330 if (!rowdef.editor) {
331 return;
332 }
333
334 var editor = rowdef.editor;
335 if (rowdef.tdCls == 'pve-itype-icon-storage') {
336 var value = me.getObjectValue(rec.data.key, '', true);
ab1ee352
DC
337 if (value.match(/vm-.*-cloudinit/)) {
338 return;
339 } else if (value.match(/media=cdrom/)) {
f97670f5 340 editor = 'PVE.qemu.CDEdit';
10525ed7
TL
341 } else if (!diskCap) {
342 return;
f97670f5
DM
343 }
344 }
345
346 var win;
347
348 if (Ext.isString(editor)) {
349 win = Ext.create(editor, {
350 pveSelNode: me.pveSelNode,
351 confid: rec.data.key,
352 url: '/api2/extjs/' + baseurl
353 });
354 } else {
355 var config = Ext.apply({
356 pveSelNode: me.pveSelNode,
357 confid: rec.data.key,
358 url: '/api2/extjs/' + baseurl
359 }, rowdef.editor);
360 win = Ext.createWidget(rowdef.editor.xtype, config);
361 win.load();
362 }
363
364 win.show();
365 win.on('destroy', reload);
366 };
367
f97670f5
DM
368 var run_resize = function() {
369 var rec = sm.getSelection()[0];
370 if (!rec) {
371 return;
372 }
373
374 var win = Ext.create('PVE.window.HDResize', {
375 disk: rec.data.key,
376 nodename: nodename,
377 vmid: vmid
378 });
379
380 win.show();
381
382 win.on('destroy', reload);
383 };
384
f97670f5
DM
385 var run_move = function() {
386 var rec = sm.getSelection()[0];
387 if (!rec) {
388 return;
389 }
390
391 var win = Ext.create('PVE.window.HDMove', {
392 disk: rec.data.key,
393 nodename: nodename,
394 vmid: vmid
395 });
396
397 win.show();
398
399 win.on('destroy', reload);
400 };
401
5720fafa 402 var edit_btn = new Proxmox.button.Button({
f97670f5
DM
403 text: gettext('Edit'),
404 selModel: sm,
405 disabled: true,
406 handler: run_editor
407 });
408
5720fafa 409 var resize_btn = new Proxmox.button.Button({
f97670f5
DM
410 text: gettext('Resize disk'),
411 selModel: sm,
412 disabled: true,
413 handler: run_resize
414 });
415
5720fafa 416 var move_btn = new Proxmox.button.Button({
f97670f5
DM
417 text: gettext('Move disk'),
418 selModel: sm,
419 disabled: true,
420 handler: run_move
421 });
422
5720fafa 423 var remove_btn = new Proxmox.button.Button({
f97670f5 424 text: gettext('Remove'),
877fce54
EK
425 defaultText: gettext('Remove'),
426 altText: gettext('Detach'),
f97670f5
DM
427 selModel: sm,
428 disabled: true,
429 dangerous: true,
3b2e557f 430 RESTMethod: 'PUT',
f97670f5 431 confirmMsg: function(rec) {
ff5a8a9b
EK
432 var warn = gettext('Are you sure you want to remove entry {0}');
433 if (this.text === this.altText) {
434 warn = gettext('Are you sure you want to detach entry {0}');
435 }
436
437 var entry = rec.data.key;
83b4bfc3
TL
438 var rendered = me.renderKey(entry, {}, rec);
439 var msg = Ext.String.format(warn, "'" + rendered + "'");
ff5a8a9b
EK
440
441 if (entry.match(/^unused\d+$/)) {
16152937 442 msg += " " + gettext('This will permanently erase all data.');
f97670f5
DM
443 }
444
445 return msg;
446 },
447 handler: function(b, e, rec) {
e7ade592 448 Proxmox.Utils.API2Request({
f97670f5
DM
449 url: '/api2/extjs/' + baseurl,
450 waitMsgTarget: me,
3b2e557f 451 method: b.RESTMethod,
f97670f5
DM
452 params: {
453 'delete': rec.data.key
454 },
455 callback: function() {
456 reload();
457 },
458 failure: function (response, opts) {
459 Ext.Msg.alert('Error', response.htmlStatus);
3b2e557f
TM
460 },
461 success: function(response, options) {
462 if (b.RESTMethod === 'POST') {
463 var upid = response.result.data;
464 var win = Ext.create('Proxmox.window.TaskProgress', {
465 upid: upid,
466 listeners: {
467 destroy: function () {
468 me.reload();
469 }
470 }
471 });
472 win.show();
473 }
f97670f5
DM
474 }
475 });
877fce54
EK
476 },
477 listeners: {
478 render: function(btn) {
479 // hack: calculate an optimal button width on first display
480 // to prevent the whole toolbar to move when we switch
481 // between the "Remove" and "Detach" labels
482 var def = btn.getSize().width;
483
484 btn.setText(btn.altText);
485 var alt = btn.getSize().width;
486
487 btn.setText(btn.defaultText);
488
489 var optimal = alt > def ? alt : def;
490 btn.setSize({ width: optimal });
491 }
f97670f5
DM
492 }
493 });
494
5720fafa 495 var revert_btn = new Proxmox.button.Button({
f97670f5
DM
496 text: gettext('Revert'),
497 selModel: sm,
498 disabled: true,
499 handler: function(b, e, rec) {
500 var rowdef = me.rows[rec.data.key] || {};
501 var keys = rowdef.multiKey || [ rec.data.key ];
502 var revert = keys.join(',');
e7ade592 503 Proxmox.Utils.API2Request({
f97670f5
DM
504 url: '/api2/extjs/' + baseurl,
505 waitMsgTarget: me,
506 method: 'PUT',
507 params: {
508 'revert': revert
509 },
510 callback: function() {
511 reload();
512 },
513 failure: function (response, opts) {
514 Ext.Msg.alert('Error',response.htmlStatus);
515 }
516 });
517 }
518 });
519
3b37ab6d
DC
520 var efidisk_menuitem = Ext.create('Ext.menu.Item',{
521 text: gettext('EFI Disk'),
522 iconCls: 'pve-itype-icon-storage',
523 disabled: !caps.vms['VM.Config.Disk'],
524 handler: function() {
1535df62
DC
525
526 var rstoredata = me.rstore.getData().map;
527 // check if ovmf is configured
528 if (rstoredata.bios && rstoredata.bios.data.value === 'ovmf') {
529 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
530 url: '/api2/extjs/' + baseurl,
531 pveSelNode: me.pveSelNode
532 });
533 win.on('destroy', reload);
534 win.show();
535 } else {
536 Ext.Msg.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.'));
537 }
538
3b37ab6d
DC
539 }
540 });
541
f97670f5
DM
542 var set_button_status = function() {
543 var sm = me.getSelectionModel();
544 var rec = sm.getSelection()[0];
545
1535df62
DC
546 // disable button when we have an efidisk already
547 // disable is ok in this case, because you can instantly
548 // see that there is already one
549 efidisk_menuitem.setDisabled(me.rstore.getData().map.efidisk0 !== undefined);
2668ba64 550 // en/disable usb add button
25a1063a
DC
551 var usbcount = 0;
552 var pcicount = 0;
b38d0810 553 var hasCloudInit = false;
2668ba64
DC
554 me.rstore.getData().items.forEach(function(item){
555 if (/^usb\d+/.test(item.id)) {
25a1063a
DC
556 usbcount++;
557 } else if (/^hostpci\d+/.test(item.id)) {
558 pcicount++;
2668ba64 559 }
b38d0810
DC
560 if (!hasCloudInit && /vm-.*-cloudinit/.test(item.data.value)) {
561 hasCloudInit = true;
562 }
2668ba64 563 });
3097170d
TL
564
565 // heuristic only for disabling some stuff, the backend has the final word.
566 var noSysConsolePerm = !caps.nodes['Sys.Console'];
567
25a1063a
DC
568 me.down('#addusb').setDisabled(noSysConsolePerm || (usbcount >= 5));
569 me.down('#addpci').setDisabled(noSysConsolePerm || (pcicount >= 4));
3097170d 570 me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit);
3b37ab6d 571
f97670f5
DM
572 if (!rec) {
573 remove_btn.disable();
574 edit_btn.disable();
575 resize_btn.disable();
576 move_btn.disable();
f97670f5
DM
577 revert_btn.disable();
578 return;
579 }
580 var key = rec.data.key;
581 var value = rec.data.value;
582 var rowdef = rows[key];
583
584 var pending = rec.data['delete'] || me.hasPendingChanges(key);
2e1a8291 585 var isCDRom = (value && !!value.toString().match(/media=cdrom/));
3b2e557f
TM
586 var isUnusedDisk = key.match(/^unused\d+/);
587 var isUsedDisk = !isUnusedDisk &&
25e0bc2d 588 rowdef.tdCls == 'pve-itype-icon-storage' &&
10525ed7 589 !isCDRom;
f97670f5 590
ab1ee352 591 var isCloudInit = (value && value.toString().match(/vm-.*-cloudinit/));
b38d0810 592
3b37ab6d
DC
593 var isEfi = (key === 'efidisk0');
594
d35b5b2a 595 remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true) || (isUnusedDisk && !diskCap));
ab1ee352 596 remove_btn.setText((isUsedDisk && !isCloudInit) ? remove_btn.altText : remove_btn.defaultText);
3b2e557f 597 remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT';
f97670f5 598
10525ed7 599 edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor || isCloudInit || (!isCDRom && !diskCap));
f97670f5 600
d35b5b2a 601 resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
f97670f5 602
d35b5b2a 603 move_btn.setDisabled(pending || !isUsedDisk || !diskCap);
f97670f5 604
f97670f5
DM
605 revert_btn.setDisabled(!pending);
606
607 };
608
f09dd6c1 609 Ext.apply(me, {
f97670f5
DM
610 url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
611 interval: 5000,
612 selModel: sm,
b08ae044 613 run_editor: run_editor,
f97670f5
DM
614 tbar: [
615 {
616 text: gettext('Add'),
617 menu: new Ext.menu.Menu({
618 items: [
619 {
620 text: gettext('Hard Disk'),
621 iconCls: 'pve-itype-icon-storage',
622 disabled: !caps.vms['VM.Config.Disk'],
623 handler: function() {
624 var win = Ext.create('PVE.qemu.HDEdit', {
625 url: '/api2/extjs/' + baseurl,
626 pveSelNode: me.pveSelNode
627 });
628 win.on('destroy', reload);
629 win.show();
630 }
631 },
632 {
633 text: gettext('CD/DVD Drive'),
634 iconCls: 'pve-itype-icon-cdrom',
635 disabled: !caps.vms['VM.Config.Disk'],
636 handler: function() {
637 var win = Ext.create('PVE.qemu.CDEdit', {
638 url: '/api2/extjs/' + baseurl,
639 pveSelNode: me.pveSelNode
640 });
641 win.on('destroy', reload);
642 win.show();
643 }
644 },
645 {
646 text: gettext('Network Device'),
647 iconCls: 'pve-itype-icon-network',
648 disabled: !caps.vms['VM.Config.Network'],
649 handler: function() {
650 var win = Ext.create('PVE.qemu.NetworkEdit', {
651 url: '/api2/extjs/' + baseurl,
b2991833
CE
652 pveSelNode: me.pveSelNode,
653 isCreate: true
f97670f5
DM
654 });
655 win.on('destroy', reload);
656 win.show();
657 }
3b37ab6d 658 },
2668ba64
DC
659 efidisk_menuitem,
660 {
661 text: gettext('USB Device'),
662 itemId: 'addusb',
663 iconCls: 'pve-itype-icon-usb',
664 disabled: !caps.nodes['Sys.Console'],
665 handler: function() {
666 var win = Ext.create('PVE.qemu.USBEdit', {
667 url: '/api2/extjs/' + baseurl,
668 pveSelNode: me.pveSelNode
669 });
670 win.on('destroy', reload);
671 win.show();
672 }
b38d0810 673 },
25a1063a
DC
674 {
675 text: gettext('PCI Device'),
676 itemId: 'addpci',
677 iconCls: 'pve-itype-icon-pci',
678 disabled: !caps.nodes['Sys.Console'],
679 handler: function() {
680 var win = Ext.create('PVE.qemu.PCIEdit', {
681 url: '/api2/extjs/' + baseurl,
682 pveSelNode: me.pveSelNode
683 });
684 win.on('destroy', reload);
685 win.show();
686 }
687 },
37977fa5
TL
688 {
689 text: gettext('Serial Port'),
690 itemId: 'addserial',
691 iconCls: 'pve-itype-icon-serial',
692 disabled: !caps.vms['VM.Config.Options'],
693 handler: function() {
694 var win = Ext.create('PVE.qemu.SerialEdit', {
695 url: '/api2/extjs/' + baseurl
696 });
697 win.on('destroy', reload);
698 win.show();
699 }
700 },
b38d0810
DC
701 {
702 text: gettext('CloudInit Drive'),
703 itemId: 'addci',
704 iconCls: 'pve-itype-icon-cloud',
705 disabled: !caps.nodes['Sys.Console'],
706 handler: function() {
707 var win = Ext.create('PVE.qemu.CIDriveEdit', {
708 url: '/api2/extjs/' + baseurl,
709 pveSelNode: me.pveSelNode
710 });
711 win.on('destroy', reload);
712 win.show();
713 }
2668ba64 714 }
f97670f5
DM
715 ]
716 })
fa522e48 717 },
f97670f5
DM
718 remove_btn,
719 edit_btn,
720 resize_btn,
721 move_btn,
f97670f5
DM
722 revert_btn
723 ],
724 rows: rows,
725 sorterFn: sorterFn,
726 listeners: {
727 itemdblclick: run_editor,
728 selectionchange: set_button_status
729 }
730 });
731
732 me.callParent();
733
f09dd6c1 734 me.on('activate', me.rstore.startUpdate);
f97670f5
DM
735 me.on('destroy', me.rstore.stopUpdate);
736
f09dd6c1 737 me.mon(me.rstore, 'refresh', function() {
f97670f5
DM
738 set_button_status();
739 });
740 }
741});