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