]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
ui/hardware: extend machine renderer to show pinned Windows versions
[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: 'pve-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-processor',
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 for (let i = 0; i < PVE.Utils.hardware_counts.usb; i++) {
249 let confid = "usb" + i.toString();
250 rows[confid] = {
251 group: 25,
252 order: i,
253 iconCls: 'usb',
254 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
255 never_delete: !caps.nodes['Sys.Console'],
256 header: gettext('USB Device') + ' (' + confid + ')',
257 };
258 }
259 for (let i = 0; i < PVE.Utils.hardware_counts.hostpci; i++) {
260 let confid = "hostpci" + i.toString();
261 rows[confid] = {
262 group: 30,
263 order: i,
264 tdCls: 'pve-itype-icon-pci',
265 never_delete: !caps.nodes['Sys.Console'],
266 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
267 header: gettext('PCI Device') + ' (' + confid + ')',
268 };
269 }
270 for (let i = 0; i < PVE.Utils.hardware_counts.serial; i++) {
271 let confid = "serial" + i.toString();
272 rows[confid] = {
273 group: 35,
274 order: i,
275 tdCls: 'pve-itype-icon-serial',
276 never_delete: !caps.nodes['Sys.Console'],
277 header: gettext('Serial Port') + ' (' + confid + ')',
278 };
279 }
280 rows.audio0 = {
281 group: 40,
282 iconCls: 'volume-up',
283 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined,
284 never_delete: !caps.vms['VM.Config.HWType'],
285 header: gettext('Audio Device'),
286 };
287 for (let i = 0; i < 256; i++) {
288 rows["unused" + i.toString()] = {
289 group: 99,
290 order: i,
291 iconCls: 'hdd-o',
292 del_extra_msg: gettext('This will permanently erase all data.'),
293 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
294 header: gettext('Unused Disk') + ' ' + i.toString(),
295 };
296 }
297 rows.rng0 = {
298 group: 45,
299 tdCls: 'pve-itype-icon-die',
300 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.RNGEdit' : undefined,
301 never_delete: !caps.nodes['Sys.Console'],
302 header: gettext("VirtIO RNG"),
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;
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 }
316
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 }
328 };
329
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;
346 if (rowdef.isOnStorageBus) {
347 var value = me.getObjectValue(rec.data.key, '', true);
348 if (value.match(/vm-.*-cloudinit/)) {
349 return;
350 } else if (value.match(/media=cdrom/)) {
351 editor = 'PVE.qemu.CDEdit';
352 } else if (!diskCap) {
353 return;
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();
376 win.on('destroy', me.reload, me);
377 };
378
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
393 win.on('destroy', me.reload, me);
394 };
395
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
410 win.on('destroy', me.reload, me);
411 };
412
413 var edit_btn = new Proxmox.button.Button({
414 text: gettext('Edit'),
415 selModel: sm,
416 disabled: true,
417 handler: run_editor,
418 });
419
420 var resize_btn = new Proxmox.button.Button({
421 text: gettext('Resize disk'),
422 selModel: sm,
423 disabled: true,
424 handler: run_resize,
425 });
426
427 var move_btn = new Proxmox.button.Button({
428 text: gettext('Move disk'),
429 selModel: sm,
430 disabled: true,
431 handler: run_move,
432 });
433
434 var remove_btn = new Proxmox.button.Button({
435 text: gettext('Remove'),
436 defaultText: gettext('Remove'),
437 altText: gettext('Detach'),
438 selModel: sm,
439 disabled: true,
440 dangerous: true,
441 RESTMethod: 'PUT',
442 confirmMsg: function(rec) {
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 }
447 var key = rec.data.key;
448 var entry = rows[key];
449
450 var rendered = me.renderKey(key, {}, rec);
451 var msg = Ext.String.format(warn, "'" + rendered + "'");
452
453 if (entry.del_extra_msg) {
454 msg += '<br>' + entry.del_extra_msg;
455 }
456
457 return msg;
458 },
459 handler: function(b, e, rec) {
460 Proxmox.Utils.API2Request({
461 url: '/api2/extjs/' + baseurl,
462 waitMsgTarget: me,
463 method: b.RESTMethod,
464 params: {
465 'delete': rec.data.key,
466 },
467 callback: () => me.reload(),
468 failure: function(response, opts) {
469 Ext.Msg.alert('Error', response.htmlStatus);
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: {
477 destroy: () => me.reload(),
478 },
479 });
480 win.show();
481 }
482 },
483 });
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 },
500 },
501 });
502
503 var revert_btn = new PVE.button.PendingRevert({
504 apiurl: '/api2/extjs/' + baseurl,
505 });
506
507 var efidisk_menuitem = Ext.create('Ext.menu.Item', {
508 text: gettext('EFI Disk'),
509 iconCls: 'fa fa-fw fa-hdd-o black',
510 disabled: !caps.vms['VM.Config.Disk'],
511 handler: function() {
512 let bios = me.rstore.getData().map.bios;
513 let usesEFI = bios && (bios.data.value === 'ovmf' || bios.data.pending === 'ovmf');
514
515 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
516 url: '/api2/extjs/' + baseurl,
517 pveSelNode: me.pveSelNode,
518 usesEFI: usesEFI,
519 });
520 win.on('destroy', me.reload, me);
521 win.show();
522 },
523 });
524
525 let counts = {};
526 let isAtLimit = (type) => counts[type] >= PVE.Utils.hardware_counts[type];
527
528 var set_button_status = function() {
529 var selection_model = me.getSelectionModel();
530 var rec = selection_model.getSelection()[0];
531
532 // en/disable hardwarebuttons
533 counts = {};
534 var hasCloudInit = false;
535 me.rstore.getData().items.forEach(function(item) {
536 if (!hasCloudInit && (
537 /vm-.*-cloudinit/.test(item.data.value) ||
538 /vm-.*-cloudinit/.test(item.data.pending)
539 )) {
540 hasCloudInit = true;
541 return;
542 }
543
544 let match = item.id.match(/^([^\d]+)\d+$/);
545 let type;
546 if (match && PVE.Utils.hardware_counts[match[1]] !== undefined) {
547 type = match[1];
548 } else {
549 return;
550 }
551
552 counts[type] = (counts[type] || 0) + 1;
553 });
554
555 // heuristic only for disabling some stuff, the backend has the final word.
556 const noSysConsolePerm = !caps.nodes['Sys.Console'];
557 const noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType'];
558 const noVMConfigNetPerm = !caps.vms['VM.Config.Network'];
559 const noVMConfigDiskPerm = !caps.vms['VM.Config.Disk'];
560
561 me.down('#addusb').setDisabled(noSysConsolePerm || isAtLimit('usb'));
562 me.down('#addpci').setDisabled(noSysConsolePerm || isAtLimit('hostpci'));
563 me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || isAtLimit('audio'));
564 me.down('#addserial').setDisabled(noVMConfigHWTypePerm || isAtLimit('serial'));
565 me.down('#addnet').setDisabled(noVMConfigNetPerm || isAtLimit('net'));
566 me.down('#addrng').setDisabled(noSysConsolePerm || isAtLimit('rng'));
567 efidisk_menuitem.setDisabled(noVMConfigDiskPerm || isAtLimit('efidisk'));
568 me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit);
569
570 if (!rec) {
571 remove_btn.disable();
572 edit_btn.disable();
573 resize_btn.disable();
574 move_btn.disable();
575 revert_btn.disable();
576 return;
577 }
578 const key = rec.data.key;
579 const value = rec.data.value;
580 const row = rows[key];
581
582 const deleted = !!rec.data.delete;
583 const pending = deleted || me.hasPendingChanges(key);
584
585 const isCloudInit = value && value.toString().match(/vm-.*-cloudinit/);
586 const isCDRom = value && !!value.toString().match(/media=cdrom/) && !isCloudInit;
587
588 const isUnusedDisk = key.match(/^unused\d+/);
589 const isUsedDisk = !isUnusedDisk && row.isOnStorageBus && !isCDRom && !isCloudInit;
590 const isDisk = isCloudInit || isUnusedDisk || isUsedDisk;
591 const isEfi = key === 'efidisk0';
592
593 remove_btn.setDisabled(
594 deleted ||
595 row.never_delete ||
596 (isCDRom && !cdromCap) ||
597 (isDisk && !diskCap),
598 );
599 remove_btn.setText(isUsedDisk && !isCloudInit ? remove_btn.altText : remove_btn.defaultText);
600 remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT';
601
602 edit_btn.setDisabled(
603 deleted ||
604 !row.editor ||
605 isCloudInit ||
606 (isCDRom && !cdromCap) ||
607 (isDisk && !diskCap),
608 );
609
610 resize_btn.setDisabled(pending || !isUsedDisk || !diskCap);
611
612 move_btn.setDisabled(pending || !(isUsedDisk || isEfi) || !diskCap);
613
614 revert_btn.setDisabled(!pending);
615 };
616
617 Ext.apply(me, {
618 url: `/api2/json/nodes/${nodename}/qemu/${vmid}/pending`,
619 interval: 5000,
620 selModel: sm,
621 run_editor: run_editor,
622 tbar: [
623 {
624 text: gettext('Add'),
625 menu: new Ext.menu.Menu({
626 cls: 'pve-add-hw-menu',
627 items: [
628 {
629 text: gettext('Hard Disk'),
630 iconCls: 'fa fa-fw fa-hdd-o black',
631 disabled: !caps.vms['VM.Config.Disk'],
632 handler: function() {
633 let win = Ext.create('PVE.qemu.HDEdit', {
634 url: '/api2/extjs/' + baseurl,
635 pveSelNode: me.pveSelNode,
636 });
637 win.on('destroy', me.reload, me);
638 win.show();
639 },
640 },
641 {
642 text: gettext('CD/DVD Drive'),
643 iconCls: 'pve-itype-icon-cdrom',
644 disabled: !caps.vms['VM.Config.CDROM'],
645 handler: function() {
646 let win = Ext.create('PVE.qemu.CDEdit', {
647 url: '/api2/extjs/' + baseurl,
648 pveSelNode: me.pveSelNode,
649 });
650 win.on('destroy', me.reload, me);
651 win.show();
652 },
653 },
654 {
655 text: gettext('Network Device'),
656 itemId: 'addnet',
657 iconCls: 'fa fa-fw fa-exchange black',
658 disabled: !caps.vms['VM.Config.Network'],
659 handler: function() {
660 var win = Ext.create('PVE.qemu.NetworkEdit', {
661 url: '/api2/extjs/' + baseurl,
662 pveSelNode: me.pveSelNode,
663 isCreate: true,
664 });
665 win.on('destroy', me.reload, me);
666 win.show();
667 },
668 },
669 efidisk_menuitem,
670 {
671 text: gettext('USB Device'),
672 itemId: 'addusb',
673 iconCls: 'fa fa-fw fa-usb black',
674 disabled: !caps.nodes['Sys.Console'],
675 handler: function() {
676 var win = Ext.create('PVE.qemu.USBEdit', {
677 url: '/api2/extjs/' + baseurl,
678 pveSelNode: me.pveSelNode,
679 });
680 win.on('destroy', me.reload, me);
681 win.show();
682 },
683 },
684 {
685 text: gettext('PCI Device'),
686 itemId: 'addpci',
687 iconCls: 'pve-itype-icon-pci',
688 disabled: !caps.nodes['Sys.Console'],
689 handler: function() {
690 var win = Ext.create('PVE.qemu.PCIEdit', {
691 url: '/api2/extjs/' + baseurl,
692 pveSelNode: me.pveSelNode,
693 });
694 win.on('destroy', me.reload, me);
695 win.show();
696 },
697 },
698 {
699 text: gettext('Serial Port'),
700 itemId: 'addserial',
701 iconCls: 'pve-itype-icon-serial',
702 disabled: !caps.vms['VM.Config.Options'],
703 handler: function() {
704 var win = Ext.create('PVE.qemu.SerialEdit', {
705 url: '/api2/extjs/' + baseurl,
706 });
707 win.on('destroy', me.reload, me);
708 win.show();
709 },
710 },
711 {
712 text: gettext('CloudInit Drive'),
713 itemId: 'addci',
714 iconCls: 'fa fa-fw fa-cloud black',
715 disabled: !caps.nodes['Sys.Console'],
716 handler: function() {
717 var win = Ext.create('PVE.qemu.CIDriveEdit', {
718 url: '/api2/extjs/' + baseurl,
719 pveSelNode: me.pveSelNode,
720 });
721 win.on('destroy', me.reload, me);
722 win.show();
723 },
724 },
725 {
726 text: gettext('Audio Device'),
727 itemId: 'addaudio',
728 iconCls: 'fa fa-fw fa-volume-up black',
729 disabled: !caps.vms['VM.Config.HWType'],
730 handler: function() {
731 var win = Ext.create('PVE.qemu.AudioEdit', {
732 url: '/api2/extjs/' + baseurl,
733 isCreate: true,
734 isAdd: true,
735 });
736 win.on('destroy', me.reload, me);
737 win.show();
738 },
739 },
740 {
741 text: gettext("VirtIO RNG"),
742 itemId: 'addrng',
743 iconCls: 'pve-itype-icon-die',
744 disabled: !caps.nodes['Sys.Console'],
745 handler: function() {
746 var win = Ext.create('PVE.qemu.RNGEdit', {
747 url: '/api2/extjs/' + baseurl,
748 isCreate: true,
749 isAdd: true,
750 });
751 win.on('destroy', me.reload, me);
752 win.show();
753 },
754 },
755 ],
756 }),
757 },
758 remove_btn,
759 edit_btn,
760 resize_btn,
761 move_btn,
762 revert_btn,
763 ],
764 rows: rows,
765 sorterFn: sorterFn,
766 listeners: {
767 itemdblclick: run_editor,
768 selectionchange: set_button_status,
769 },
770 });
771
772 me.callParent();
773
774 me.on('activate', me.rstore.startUpdate, me.rstore);
775 me.on('destroy', me.rstore.stopUpdate, me.rstore);
776
777 me.mon(me.getStore(), 'datachanged', set_button_status, me);
778 },
779 });