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