]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
ui: qemu: switch processor icon to SVG
[pve-manager.git] / www / manager6 / qemu / HardwareView.js
1 Ext.define('PVE.qemu.HardwareView', {
2 extend: 'Proxmox.grid.PendingObjectGrid',
3 alias: ['widget.PVE.qemu.HardwareView'],
4
5 onlineHelp: 'qm_virtual_machines_settings',
6
7 renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
8 var me = this;
9 var rows = me.rows;
10 var rowdef = rows[key] || {};
11 var iconCls = rowdef.iconCls;
12 var icon = '';
13 var txt = rowdef.header || key;
14
15 metaData.tdAttr = "valign=middle";
16
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
31 if (rowdef.tdCls) {
32 metaData.tdCls = rowdef.tdCls;
33 } else if (iconCls) {
34 icon = "<i class='pve-grid-fa fa fa-fw fa-" + iconCls + "'></i>";
35 metaData.tdCls += " pve-itype-fa";
36 }
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 }
44 },
45
46 initComponent: function() {
47 var me = this;
48
49 const nodename = me.pveSelNode.data.node;
50 if (!nodename) {
51 throw "no node name specified";
52 }
53
54 const vmid = me.pveSelNode.data.vmid;
55 if (!vmid) {
56 throw "no VM ID specified";
57 }
58
59 const caps = Ext.state.Manager.get('GuiCap');
60 const diskCap = caps.vms['VM.Config.Disk'];
61 const cdromCap = caps.vms['VM.Config.CDROM'];
62
63 let rows = {
64 memory: {
65 header: gettext('Memory'),
66 editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
67 never_delete: true,
68 defaultValue: '512',
69 tdCls: 'pmx-itype-icon-memory',
70 group: 2,
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);
76 var balloon = me.getObjectValue('balloon', undefined, pending);
77 var shares = me.getObjectValue('shares', undefined, pending);
78
79 res = Proxmox.Utils.format_size(max*1024*1024);
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;
91 },
92 },
93 sockets: {
94 header: gettext('Processors'),
95 never_delete: true,
96 editor: caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']
97 ? 'PVE.qemu.ProcessorEdit' : undefined,
98 tdCls: 'pve-itype-icon-cpu',
99 group: 3,
100 defaultValue: '1',
101 multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
102 renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
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
111 var res = Ext.String.format('{0} ({1} sockets, {2} cores)',
112 sockets*cores, sockets, cores);
113
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;
135 },
136 },
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',
144 renderer: PVE.Utils.render_qemu_bios,
145 },
146 vga: {
147 header: gettext('Display'),
148 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
149 never_delete: true,
150 iconCls: 'desktop',
151 group: 5,
152 defaultValue: '',
153 renderer: PVE.Utils.render_kvm_vga_driver,
154 },
155 machine: {
156 header: gettext('Machine'),
157 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.MachineEdit' : undefined,
158 iconCls: 'cogs',
159 never_delete: true,
160 group: 6,
161 defaultValue: '',
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 },
170 },
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,
178 defaultValue: '',
179 },
180 vmstate: {
181 header: gettext('Hibernation VM State'),
182 iconCls: 'download',
183 del_extra_msg: gettext('The saved VM state will be permanently lost.'),
184 group: 100,
185 },
186 cores: {
187 visible: false,
188 },
189 cpu: {
190 visible: false,
191 },
192 numa: {
193 visible: false,
194 },
195 balloon: {
196 visible: false,
197 },
198 hotplug: {
199 visible: false,
200 },
201 vcpus: {
202 visible: false,
203 },
204 cpuunits: {
205 visible: false,
206 },
207 cpulimit: {
208 visible: false,
209 },
210 shares: {
211 visible: false,
212 },
213 ostype: {
214 visible: false,
215 },
216 };
217
218 PVE.Utils.forEachBus(undefined, function(type, id) {
219 let confid = type + id;
220 rows[confid] = {
221 group: 10,
222 iconCls: 'hdd-o',
223 editor: 'PVE.qemu.HDEdit',
224 isOnStorageBus: true,
225 header: gettext('Hard Disk') + ' (' + confid +')',
226 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')',
227 cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')',
228 };
229 });
230 for (let i = 0; i < PVE.Utils.hardware_counts.net; i++) {
231 let confid = "net" + i.toString();
232 rows[confid] = {
233 group: 15,
234 order: i,
235 iconCls: 'exchange',
236 editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
237 never_delete: !caps.vms['VM.Config.Network'],
238 header: gettext('Network Device') + ' (' + confid +')',
239 };
240 }
241 rows.efidisk0 = {
242 group: 20,
243 iconCls: 'hdd-o',
244 editor: null,
245 never_delete: !caps.vms['VM.Config.Disk'],
246 header: gettext('EFI Disk'),
247 };
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 };
255 for (let i = 0; i < PVE.Utils.hardware_counts.usb; i++) {
256 let confid = "usb" + i.toString();
257 rows[confid] = {
258 group: 25,
259 order: i,
260 iconCls: 'usb',
261 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
262 never_delete: !caps.nodes['Sys.Console'],
263 header: gettext('USB Device') + ' (' + confid + ')',
264 };
265 }
266 for (let i = 0; i < PVE.Utils.hardware_counts.hostpci; i++) {
267 let confid = "hostpci" + i.toString();
268 rows[confid] = {
269 group: 30,
270 order: i,
271 tdCls: 'pve-itype-icon-pci',
272 never_delete: !caps.nodes['Sys.Console'],
273 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
274 header: gettext('PCI Device') + ' (' + confid + ')',
275 };
276 }
277 for (let i = 0; i < PVE.Utils.hardware_counts.serial; i++) {
278 let confid = "serial" + i.toString();
279 rows[confid] = {
280 group: 35,
281 order: i,
282 tdCls: 'pve-itype-icon-serial',
283 never_delete: !caps.nodes['Sys.Console'],
284 header: gettext('Serial Port') + ' (' + confid + ')',
285 };
286 }
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'],
292 header: gettext('Audio Device'),
293 };
294 for (let i = 0; i < 256; i++) {
295 rows["unused" + i.toString()] = {
296 group: 99,
297 order: i,
298 iconCls: 'hdd-o',
299 del_extra_msg: gettext('This will permanently erase all data.'),
300 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
301 header: gettext('Unused Disk') + ' ' + i.toString(),
302 };
303 }
304 rows.rng0 = {
305 group: 45,
306 tdCls: 'pve-itype-icon-die',
307 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.RNGEdit' : undefined,
308 never_delete: !caps.nodes['Sys.Console'],
309 header: gettext("VirtIO RNG"),
310 };
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;
317 var order1 = rows[v1].order || 0;
318 var order2 = rows[v2].order || 0;
319
320 if (g1 - g2 !== 0) {
321 return g1 - g2;
322 }
323
324 if (order1 - order2 !== 0) {
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 }
335 };
336
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;
353 if (rowdef.isOnStorageBus) {
354 var value = me.getObjectValue(rec.data.key, '', true);
355 if (value.match(/vm-.*-cloudinit/)) {
356 return;
357 } else if (value.match(/media=cdrom/)) {
358 editor = 'PVE.qemu.CDEdit';
359 } else if (!diskCap) {
360 return;
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,
370 url: '/api2/extjs/' + baseurl,
371 });
372 } else {
373 var config = Ext.apply({
374 pveSelNode: me.pveSelNode,
375 confid: rec.data.key,
376 url: '/api2/extjs/' + baseurl,
377 }, rowdef.editor);
378 win = Ext.createWidget(rowdef.editor.xtype, config);
379 win.load();
380 }
381
382 win.show();
383 win.on('destroy', me.reload, me);
384 };
385
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,
395 vmid: vmid,
396 });
397
398 win.show();
399
400 win.on('destroy', me.reload, me);
401 };
402
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,
412 vmid: vmid,
413 });
414
415 win.show();
416
417 win.on('destroy', me.reload, me);
418 };
419
420 var edit_btn = new Proxmox.button.Button({
421 text: gettext('Edit'),
422 selModel: sm,
423 disabled: true,
424 handler: run_editor,
425 });
426
427 var resize_btn = new Proxmox.button.Button({
428 text: gettext('Resize disk'),
429 selModel: sm,
430 disabled: true,
431 handler: run_resize,
432 });
433
434 var move_btn = new Proxmox.button.Button({
435 text: gettext('Move disk'),
436 selModel: sm,
437 disabled: true,
438 handler: run_move,
439 });
440
441 var remove_btn = new Proxmox.button.Button({
442 text: gettext('Remove'),
443 defaultText: gettext('Remove'),
444 altText: gettext('Detach'),
445 selModel: sm,
446 disabled: true,
447 dangerous: true,
448 RESTMethod: 'PUT',
449 confirmMsg: function(rec) {
450 let 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 }
454 let rendered = me.renderKey(rec.data.key, {}, rec);
455 let msg = Ext.String.format(warn, `'${rendered}'`);
456
457 if (rows[rec.data.key].del_extra_msg) {
458 msg += '<br>' + rows[rec.data.key].del_extra_msg;
459 }
460 return msg;
461 },
462 handler: function(b, e, rec) {
463 Proxmox.Utils.API2Request({
464 url: '/api2/extjs/' + baseurl,
465 waitMsgTarget: me,
466 method: b.RESTMethod,
467 params: {
468 'delete': rec.data.key,
469 },
470 callback: () => me.reload(),
471 failure: function(response, opts) {
472 Ext.Msg.alert('Error', response.htmlStatus);
473 },
474 success: function(response, options) {
475 if (b.RESTMethod === 'POST') {
476 var upid = response.result.data;
477 var win = Ext.create('Proxmox.window.TaskProgress', {
478 upid: upid,
479 listeners: {
480 destroy: () => me.reload(),
481 },
482 });
483 win.show();
484 }
485 },
486 });
487 },
488 listeners: {
489 render: function(btn) {
490 // hack: calculate the max button width on first display to prevent the whole
491 // toolbar to move when we switch between the "Remove" and "Detach" labels
492 var def = btn.getSize().width;
493
494 btn.setText(btn.altText);
495 var alt = btn.getSize().width;
496
497 btn.setText(btn.defaultText);
498
499 var optimal = alt > def ? alt : def;
500 btn.setSize({ width: optimal });
501 },
502 },
503 });
504
505 var revert_btn = new PVE.button.PendingRevert({
506 apiurl: '/api2/extjs/' + baseurl,
507 });
508
509 var efidisk_menuitem = Ext.create('Ext.menu.Item', {
510 text: gettext('EFI Disk'),
511 iconCls: 'fa fa-fw fa-hdd-o black',
512 disabled: !caps.vms['VM.Config.Disk'],
513 handler: function() {
514 let bios = me.rstore.getData().map.bios;
515 let usesEFI = bios && (bios.data.value === 'ovmf' || bios.data.pending === 'ovmf');
516
517 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
518 url: '/api2/extjs/' + baseurl,
519 pveSelNode: me.pveSelNode,
520 usesEFI: usesEFI,
521 });
522 win.on('destroy', me.reload, me);
523 win.show();
524 },
525 });
526
527 let counts = {};
528 let isAtLimit = (type) => counts[type] >= PVE.Utils.hardware_counts[type];
529
530 var set_button_status = function() {
531 var selection_model = me.getSelectionModel();
532 var rec = selection_model.getSelection()[0];
533
534 // en/disable hardwarebuttons
535 counts = {};
536 var hasCloudInit = false;
537 me.rstore.getData().items.forEach(function(item) {
538 if (!hasCloudInit && (
539 /vm-.*-cloudinit/.test(item.data.value) ||
540 /vm-.*-cloudinit/.test(item.data.pending)
541 )) {
542 hasCloudInit = true;
543 return;
544 }
545
546 let match = item.id.match(/^([^\d]+)\d+$/);
547 let type;
548 if (match && PVE.Utils.hardware_counts[match[1]] !== undefined) {
549 type = match[1];
550 } else {
551 return;
552 }
553
554 counts[type] = (counts[type] || 0) + 1;
555 });
556
557 // heuristic only for disabling some stuff, the backend has the final word.
558 const noSysConsolePerm = !caps.nodes['Sys.Console'];
559 const noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType'];
560 const noVMConfigNetPerm = !caps.vms['VM.Config.Network'];
561 const noVMConfigDiskPerm = !caps.vms['VM.Config.Disk'];
562
563 me.down('#addusb').setDisabled(noSysConsolePerm || isAtLimit('usb'));
564 me.down('#addpci').setDisabled(noSysConsolePerm || isAtLimit('hostpci'));
565 me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || isAtLimit('audio'));
566 me.down('#addserial').setDisabled(noVMConfigHWTypePerm || isAtLimit('serial'));
567 me.down('#addnet').setDisabled(noVMConfigNetPerm || isAtLimit('net'));
568 me.down('#addrng').setDisabled(noSysConsolePerm || isAtLimit('rng'));
569 efidisk_menuitem.setDisabled(noVMConfigDiskPerm || isAtLimit('efidisk'));
570 me.down('#addtpmstate').setDisabled(noSysConsolePerm || isAtLimit('tpmstate'));
571 me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit);
572
573 if (!rec) {
574 remove_btn.disable();
575 edit_btn.disable();
576 resize_btn.disable();
577 move_btn.disable();
578 revert_btn.disable();
579 return;
580 }
581 const key = rec.data.key;
582 const value = rec.data.value;
583 const row = rows[key];
584
585 const deleted = !!rec.data.delete;
586 const pending = deleted || me.hasPendingChanges(key);
587
588 const isCloudInit = value && value.toString().match(/vm-.*-cloudinit/);
589 const isCDRom = value && !!value.toString().match(/media=cdrom/) && !isCloudInit;
590
591 const isUnusedDisk = key.match(/^unused\d+/);
592 const isUsedDisk = !isUnusedDisk && row.isOnStorageBus && !isCDRom && !isCloudInit;
593 const isDisk = isCloudInit || isUnusedDisk || isUsedDisk;
594 const isEfi = key === 'efidisk0';
595 const tpmMoveable = key === 'tpmstate0' && !me.pveSelNode.data.running;
596
597 remove_btn.setDisabled(
598 deleted ||
599 row.never_delete ||
600 (isCDRom && !cdromCap) ||
601 (isDisk && !diskCap),
602 );
603 remove_btn.setText(isUsedDisk && !isCloudInit ? remove_btn.altText : remove_btn.defaultText);
604 remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT';
605
606 edit_btn.setDisabled(
607 deleted ||
608 !row.editor ||
609 isCloudInit ||
610 (isCDRom && !cdromCap) ||
611 (isDisk && !diskCap),
612 );
613
614 resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
615
616 move_btn.setDisabled(pending || !(isUsedDisk || isEfi || tpmMoveable) || !diskCap);
617
618 revert_btn.setDisabled(!pending);
619 };
620
621 Ext.apply(me, {
622 url: `/api2/json/nodes/${nodename}/qemu/${vmid}/pending`,
623 interval: 5000,
624 selModel: sm,
625 run_editor: run_editor,
626 tbar: [
627 {
628 text: gettext('Add'),
629 menu: new Ext.menu.Menu({
630 cls: 'pve-add-hw-menu',
631 items: [
632 {
633 text: gettext('Hard Disk'),
634 iconCls: 'fa fa-fw fa-hdd-o black',
635 disabled: !caps.vms['VM.Config.Disk'],
636 handler: function() {
637 let win = Ext.create('PVE.qemu.HDEdit', {
638 url: '/api2/extjs/' + baseurl,
639 pveSelNode: me.pveSelNode,
640 });
641 win.on('destroy', me.reload, me);
642 win.show();
643 },
644 },
645 {
646 text: gettext('CD/DVD Drive'),
647 iconCls: 'pve-itype-icon-cdrom',
648 disabled: !caps.vms['VM.Config.CDROM'],
649 handler: function() {
650 let win = Ext.create('PVE.qemu.CDEdit', {
651 url: '/api2/extjs/' + baseurl,
652 pveSelNode: me.pveSelNode,
653 });
654 win.on('destroy', me.reload, me);
655 win.show();
656 },
657 },
658 {
659 text: gettext('Network Device'),
660 itemId: 'addnet',
661 iconCls: 'fa fa-fw fa-exchange black',
662 disabled: !caps.vms['VM.Config.Network'],
663 handler: function() {
664 var win = Ext.create('PVE.qemu.NetworkEdit', {
665 url: '/api2/extjs/' + baseurl,
666 pveSelNode: me.pveSelNode,
667 isCreate: true,
668 });
669 win.on('destroy', me.reload, me);
670 win.show();
671 },
672 },
673 efidisk_menuitem,
674 {
675 text: gettext('TPM State'),
676 itemId: 'addtpmstate',
677 iconCls: 'fa fa-fw fa-hdd-o black',
678 disabled: !caps.vms['VM.Config.Disk'],
679 handler: function() {
680 var win = Ext.create('PVE.qemu.TPMDiskEdit', {
681 url: '/api2/extjs/' + baseurl,
682 pveSelNode: me.pveSelNode,
683 });
684 win.on('destroy', me.reload, me);
685 win.show();
686 },
687 },
688 {
689 text: gettext('USB Device'),
690 itemId: 'addusb',
691 iconCls: 'fa fa-fw fa-usb black',
692 disabled: !caps.nodes['Sys.Console'],
693 handler: function() {
694 var win = Ext.create('PVE.qemu.USBEdit', {
695 url: '/api2/extjs/' + baseurl,
696 pveSelNode: me.pveSelNode,
697 });
698 win.on('destroy', me.reload, me);
699 win.show();
700 },
701 },
702 {
703 text: gettext('PCI Device'),
704 itemId: 'addpci',
705 iconCls: 'pve-itype-icon-pci',
706 disabled: !caps.nodes['Sys.Console'],
707 handler: function() {
708 var win = Ext.create('PVE.qemu.PCIEdit', {
709 url: '/api2/extjs/' + baseurl,
710 pveSelNode: me.pveSelNode,
711 });
712 win.on('destroy', me.reload, me);
713 win.show();
714 },
715 },
716 {
717 text: gettext('Serial Port'),
718 itemId: 'addserial',
719 iconCls: 'pve-itype-icon-serial',
720 disabled: !caps.vms['VM.Config.Options'],
721 handler: function() {
722 var win = Ext.create('PVE.qemu.SerialEdit', {
723 url: '/api2/extjs/' + baseurl,
724 });
725 win.on('destroy', me.reload, me);
726 win.show();
727 },
728 },
729 {
730 text: gettext('CloudInit Drive'),
731 itemId: 'addci',
732 iconCls: 'fa fa-fw fa-cloud black',
733 disabled: !caps.nodes['Sys.Console'],
734 handler: function() {
735 var win = Ext.create('PVE.qemu.CIDriveEdit', {
736 url: '/api2/extjs/' + baseurl,
737 pveSelNode: me.pveSelNode,
738 });
739 win.on('destroy', me.reload, me);
740 win.show();
741 },
742 },
743 {
744 text: gettext('Audio Device'),
745 itemId: 'addaudio',
746 iconCls: 'fa fa-fw fa-volume-up black',
747 disabled: !caps.vms['VM.Config.HWType'],
748 handler: function() {
749 var win = Ext.create('PVE.qemu.AudioEdit', {
750 url: '/api2/extjs/' + baseurl,
751 isCreate: true,
752 isAdd: true,
753 });
754 win.on('destroy', me.reload, me);
755 win.show();
756 },
757 },
758 {
759 text: gettext("VirtIO RNG"),
760 itemId: 'addrng',
761 iconCls: 'pve-itype-icon-die',
762 disabled: !caps.nodes['Sys.Console'],
763 handler: function() {
764 var win = Ext.create('PVE.qemu.RNGEdit', {
765 url: '/api2/extjs/' + baseurl,
766 isCreate: true,
767 isAdd: true,
768 });
769 win.on('destroy', me.reload, me);
770 win.show();
771 },
772 },
773 ],
774 }),
775 },
776 remove_btn,
777 edit_btn,
778 resize_btn,
779 move_btn,
780 revert_btn,
781 ],
782 rows: rows,
783 sorterFn: sorterFn,
784 listeners: {
785 itemdblclick: run_editor,
786 selectionchange: set_button_status,
787 },
788 });
789
790 me.callParent();
791
792 me.on('activate', me.rstore.startUpdate, me.rstore);
793 me.on('destroy', me.rstore.stopUpdate, me.rstore);
794
795 me.mon(me.getStore(), 'datachanged', set_button_status, me);
796 },
797 });