]>
git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
1 Ext
.define('PVE.qemu.HardwareView', {
2 extend
: 'Proxmox.grid.PendingObjectGrid',
3 alias
: ['widget.PVE.qemu.HardwareView'],
5 onlineHelp
: 'qm_virtual_machines_settings',
7 renderKey: function(key
, metaData
, rec
, rowIndex
, colIndex
, store
) {
10 var rowdef
= rows
[key
] || {};
11 var iconCls
= rowdef
.iconCls
;
13 var txt
= (rowdef
.header
|| key
);
15 metaData
.tdAttr
= "valign=middle";
17 if (rowdef
.isOnStorageBus
) {
18 var value
= me
.getObjectValue(key
, '', false);
20 value
= me
.getObjectValue(key
, '', true);
22 if (value
.match(/vm-.*-cloudinit/)) {
24 txt
= rowdef
.cloudheader
;
25 } else if (value
.match(/media=cdrom/)) {
26 metaData
.tdCls
= 'pve-itype-icon-cdrom';
27 return rowdef
.cdheader
;
32 metaData
.tdCls
= rowdef
.tdCls
;
34 icon
= "<i class='pve-grid-fa fa fa-fw fa-" + iconCls
+ "'></i>";
35 metaData
.tdCls
+= " pve-itype-fa";
38 // only return icons in grid but not remove dialog
39 if (rowIndex
!== undefined) {
46 initComponent : function() {
50 var nodename
= me
.pveSelNode
.data
.node
;
52 throw "no node name specified";
55 var vmid
= me
.pveSelNode
.data
.vmid
;
57 throw "no VM ID specified";
60 var caps
= Ext
.state
.Manager
.get('GuiCap');
61 var diskCap
= caps
.vms
['VM.Config.Disk'];
65 header
: gettext('Memory'),
66 editor
: caps
.vms
['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
69 tdCls
: 'pve-itype-icon-memory',
71 multiKey
: ['memory', 'balloon', 'shares'],
72 renderer: function(value
, metaData
, record
, ri
, ci
, store
, pending
) {
75 var max
= me
.getObjectValue('memory', 512, pending
);
76 var balloon
= me
.getObjectValue('balloon', undefined, pending
);
77 var shares
= me
.getObjectValue('shares', undefined, pending
);
79 res
= Proxmox
.Utils
.format_size(max
*1024*1024);
81 if (balloon
!== undefined && balloon
> 0) {
82 res
= Proxmox
.Utils
.format_size(balloon
*1024*1024) + "/" + res
;
85 res
+= ' [shares=' + shares
+']';
87 } else if (balloon
=== 0) {
88 res
+= ' [balloon=0]';
94 header
: gettext('Processors'),
96 editor
: (caps
.vms
['VM.Config.CPU'] || caps
.vms
['VM.Config.HWType']) ?
97 'PVE.qemu.ProcessorEdit' : undefined,
98 tdCls
: 'pve-itype-icon-processor',
101 multiKey
: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
102 renderer: function(value
, metaData
, record
, rowIndex
, colIndex
, store
, pending
) {
104 var sockets
= me
.getObjectValue('sockets', 1, pending
);
105 var model
= me
.getObjectValue('cpu', undefined, pending
);
106 var cores
= me
.getObjectValue('cores', 1, pending
);
107 var numa
= me
.getObjectValue('numa', undefined, pending
);
108 var vcpus
= me
.getObjectValue('vcpus', undefined, pending
);
109 var cpulimit
= me
.getObjectValue('cpulimit', undefined, pending
);
110 var cpuunits
= me
.getObjectValue('cpuunits', undefined, pending
);
112 var res
= Ext
.String
.format('{0} ({1} sockets, {2} cores)',
113 sockets
*cores
, sockets
, cores
);
116 res
+= ' [' + model
+ ']';
120 res
+= ' [numa=' + numa
+']';
124 res
+= ' [vcpus=' + vcpus
+']';
128 res
+= ' [cpulimit=' + cpulimit
+']';
132 res
+= ' [cpuunits=' + cpuunits
+']';
142 editor
: caps
.vms
['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
144 iconCls
: 'microchip',
145 renderer
: PVE
.Utils
.render_qemu_bios
148 header
: gettext('Display'),
149 editor
: caps
.vms
['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
154 renderer
: PVE
.Utils
.render_kvm_vga_driver
157 header
: gettext('Machine'),
158 editor
: caps
.vms
['VM.Config.HWType'] ? {
159 xtype
: 'proxmoxWindowEdit',
160 subject
: gettext('Machine'),
163 xtype
: 'proxmoxKVComboBox',
165 value
: '__default__',
166 fieldLabel
: gettext('Machine'),
168 ['__default__', PVE
.Utils
.render_qemu_machine('')],
176 renderer
: PVE
.Utils
.render_qemu_machine
179 header
: gettext('SCSI Controller'),
181 editor
: caps
.vms
['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined,
182 renderer
: PVE
.Utils
.render_scsihw
,
188 header
: gettext('Hibernation VM State'),
190 del_extra_msg
: gettext('The saved VM state will be permanently lost.'),
222 PVE
.Utils
.forEachBus(undefined, function(type
, id
) {
223 var confid
= type
+ id
;
227 editor
: 'PVE.qemu.HDEdit',
228 never_delete
: caps
.vms
['VM.Config.Disk'] ? false : true,
229 isOnStorageBus
: true,
230 header
: gettext('Hard Disk') + ' (' + confid
+')',
231 cdheader
: gettext('CD/DVD Drive') + ' (' + confid
+')',
232 cloudheader
: gettext('CloudInit Drive') + ' (' + confid
+ ')'
235 for (i
= 0; i
< PVE
.Utils
.hardware_counts
.net
; i
++) {
236 confid
= "net" + i
.toString();
241 editor
: caps
.vms
['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
242 never_delete
: caps
.vms
['VM.Config.Network'] ? false : true,
243 header
: gettext('Network Device') + ' (' + confid
+')'
250 never_delete
: caps
.vms
['VM.Config.Disk'] ? false : true,
251 header
: gettext('EFI Disk')
253 for (i
= 0; i
< PVE
.Utils
.hardware_counts
.usb
; i
++) {
254 confid
= "usb" + i
.toString();
259 editor
: caps
.nodes
['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
260 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
261 header
: gettext('USB Device') + ' (' + confid
+ ')'
264 for (i
= 0; i
< PVE
.Utils
.hardware_counts
.hostpci
; i
++) {
265 confid
= "hostpci" + i
.toString();
269 tdCls
: 'pve-itype-icon-pci',
270 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
271 editor
: caps
.nodes
['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
272 header
: gettext('PCI Device') + ' (' + confid
+ ')'
275 for (i
= 0; i
< PVE
.Utils
.hardware_counts
.serial
; i
++) {
276 confid
= "serial" + i
.toString();
280 tdCls
: 'pve-itype-icon-serial',
281 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
282 header
: gettext('Serial Port') + ' (' + confid
+ ')'
287 iconCls
: 'volume-up',
288 editor
: caps
.vms
['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined,
289 never_delete
: caps
.vms
['VM.Config.HWType'] ? false : true,
290 header
: gettext('Audio Device')
292 for (i
= 0; i
< 256; i
++) {
293 rows
["unused" + i
.toString()] = {
297 del_extra_msg
: gettext('This will permanently erase all data.'),
298 editor
: caps
.vms
['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
299 header
: gettext('Unused Disk') + ' ' + i
.toString()
304 tdCls
: 'pve-itype-icon-die',
305 editor
: caps
.nodes
['Sys.Console'] ? 'PVE.qemu.RNGEdit' : undefined,
306 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
307 header
: gettext("VirtIO RNG")
310 var sorterFn = function(rec1
, rec2
) {
311 var v1
= rec1
.data
.key
;
312 var v2
= rec2
.data
.key
;
313 var g1
= rows
[v1
].group
|| 0;
314 var g2
= rows
[v2
].group
|| 0;
315 var order1
= rows
[v1
].order
|| 0;
316 var order2
= rows
[v2
].order
|| 0;
318 if ((g1
- g2
) !== 0) {
322 if ((order1
- order2
) !== 0) {
323 return order1
- order2
;
328 } else if (v1
< v2
) {
335 var baseurl
= 'nodes/' + nodename
+ '/qemu/' + vmid
+ '/config';
337 var sm
= Ext
.create('Ext.selection.RowModel', {});
339 var run_editor = function() {
340 var rec
= sm
.getSelection()[0];
345 var rowdef
= rows
[rec
.data
.key
];
346 if (!rowdef
.editor
) {
350 var editor
= rowdef
.editor
;
351 if (rowdef
.isOnStorageBus
) {
352 var value
= me
.getObjectValue(rec
.data
.key
, '', true);
353 if (value
.match(/vm-.*-cloudinit/)) {
355 } else if (value
.match(/media=cdrom/)) {
356 editor
= 'PVE.qemu.CDEdit';
357 } else if (!diskCap
) {
364 if (Ext
.isString(editor
)) {
365 win
= Ext
.create(editor
, {
366 pveSelNode
: me
.pveSelNode
,
367 confid
: rec
.data
.key
,
368 url
: '/api2/extjs/' + baseurl
371 var config
= Ext
.apply({
372 pveSelNode
: me
.pveSelNode
,
373 confid
: rec
.data
.key
,
374 url
: '/api2/extjs/' + baseurl
376 win
= Ext
.createWidget(rowdef
.editor
.xtype
, config
);
381 win
.on('destroy', me
.reload
, me
);
384 var run_resize = function() {
385 var rec
= sm
.getSelection()[0];
390 var win
= Ext
.create('PVE.window.HDResize', {
398 win
.on('destroy', me
.reload
, me
);
401 var run_move = function() {
402 var rec
= sm
.getSelection()[0];
407 var win
= Ext
.create('PVE.window.HDMove', {
415 win
.on('destroy', me
.reload
, me
);
418 var edit_btn
= new Proxmox
.button
.Button({
419 text
: gettext('Edit'),
425 var resize_btn
= new Proxmox
.button
.Button({
426 text
: gettext('Resize disk'),
432 var move_btn
= new Proxmox
.button
.Button({
433 text
: gettext('Move disk'),
439 var remove_btn
= new Proxmox
.button
.Button({
440 text
: gettext('Remove'),
441 defaultText
: gettext('Remove'),
442 altText
: gettext('Detach'),
447 confirmMsg: function(rec
) {
448 var warn
= gettext('Are you sure you want to remove entry {0}');
449 if (this.text
=== this.altText
) {
450 warn
= gettext('Are you sure you want to detach entry {0}');
452 var key
= rec
.data
.key
;
453 var entry
= rows
[key
];
455 var rendered
= me
.renderKey(key
, {}, rec
);
456 var msg
= Ext
.String
.format(warn
, "'" + rendered
+ "'");
458 if (entry
.del_extra_msg
) {
459 msg
+= '<br>' + entry
.del_extra_msg
;
464 handler: function(b
, e
, rec
) {
465 Proxmox
.Utils
.API2Request({
466 url
: '/api2/extjs/' + baseurl
,
468 method
: b
.RESTMethod
,
470 'delete': rec
.data
.key
472 callback
: () => me
.reload(),
473 failure: function (response
, opts
) {
474 Ext
.Msg
.alert('Error', response
.htmlStatus
);
476 success: function(response
, options
) {
477 if (b
.RESTMethod
=== 'POST') {
478 var upid
= response
.result
.data
;
479 var win
= Ext
.create('Proxmox.window.TaskProgress', {
482 destroy
: () => me
.reload(),
491 render: function(btn
) {
492 // hack: calculate an optimal button width on first display
493 // to prevent the whole toolbar to move when we switch
494 // between the "Remove" and "Detach" labels
495 var def
= btn
.getSize().width
;
497 btn
.setText(btn
.altText
);
498 var alt
= btn
.getSize().width
;
500 btn
.setText(btn
.defaultText
);
502 var optimal
= alt
> def
? alt
: def
;
503 btn
.setSize({ width
: optimal
});
508 var revert_btn
= new PVE
.button
.PendingRevert({
509 apiurl
: '/api2/extjs/' + baseurl
,
512 var efidisk_menuitem
= Ext
.create('Ext.menu.Item',{
513 text
: gettext('EFI Disk'),
514 iconCls
: 'fa fa-fw fa-hdd-o black',
515 disabled
: !caps
.vms
['VM.Config.Disk'],
516 handler: function() {
517 let bios
= me
.rstore
.getData().map
.bios
;
518 let usesEFI
= bios
&& (bios
.data
.value
=== 'ovmf' || bios
.data
.pending
=== 'ovmf');
520 var win
= Ext
.create('PVE.qemu.EFIDiskEdit', {
521 url
: '/api2/extjs/' + baseurl
,
522 pveSelNode
: me
.pveSelNode
,
525 win
.on('destroy', me
.reload
, me
);
531 let isAtLimit
= (type
) => (counts
[type
] >= PVE
.Utils
.hardware_counts
[type
]);
533 var set_button_status = function() {
534 var sm
= me
.getSelectionModel();
535 var rec
= sm
.getSelection()[0];
537 // en/disable hardwarebuttons
539 var hasCloudInit
= false;
540 me
.rstore
.getData().items
.forEach(function(item
){
541 if (!hasCloudInit
&& (
542 /vm-.*-cloudinit/.test(item
.data
.value
) ||
543 /vm-.*-cloudinit/.test(item
.data
.pending
)
549 let match
= item
.id
.match(/^([^\d]+)\d+$/);
551 if (match
&& PVE
.Utils
.hardware_counts
[match
[1]] !== undefined) {
557 counts
[type
] = (counts
[type
] || 0) + 1;
560 // heuristic only for disabling some stuff, the backend has the final word.
561 var noSysConsolePerm
= !caps
.nodes
['Sys.Console'];
562 var noVMConfigHWTypePerm
= !caps
.vms
['VM.Config.HWType'];
563 var noVMConfigNetPerm
= !caps
.vms
['VM.Config.Network'];
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(isAtLimit('efidisk'));
573 me
.down('#addci').setDisabled(noSysConsolePerm
|| hasCloudInit
);
576 remove_btn
.disable();
578 resize_btn
.disable();
580 revert_btn
.disable();
583 var key
= rec
.data
.key
;
584 var value
= rec
.data
.value
;
585 var rowdef
= rows
[key
];
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
;
592 var isCloudInit
= (value
&& value
.toString().match(/vm-.*-cloudinit/));
594 var isEfi
= (key
=== 'efidisk0');
596 remove_btn
.setDisabled(rec
.data
['delete'] || (rowdef
.never_delete
=== true) || (isUnusedDisk
&& !diskCap
));
597 remove_btn
.setText((isUsedDisk
&& !isCloudInit
) ? remove_btn
.altText
: remove_btn
.defaultText
);
598 remove_btn
.RESTMethod
= isUnusedDisk
? 'POST':'PUT';
600 edit_btn
.setDisabled(rec
.data
['delete'] || !rowdef
.editor
|| isCloudInit
|| (!isCDRom
&& !diskCap
));
602 resize_btn
.setDisabled(pending
|| !isUsedDisk
|| !diskCap
);
604 move_btn
.setDisabled(pending
|| !(isUsedDisk
|| isEfi
) || !diskCap
);
606 revert_btn
.setDisabled(!pending
);
611 url
: '/api2/json/' + 'nodes/' + nodename
+ '/qemu/' + vmid
+ '/pending',
614 run_editor
: run_editor
,
617 text
: gettext('Add'),
618 menu
: new Ext
.menu
.Menu({
619 cls
: 'pve-add-hw-menu',
622 text
: gettext('Hard Disk'),
623 iconCls
: 'fa fa-fw fa-hdd-o black',
624 disabled
: !caps
.vms
['VM.Config.Disk'],
625 handler: function() {
626 var win
= Ext
.create('PVE.qemu.HDEdit', {
627 url
: '/api2/extjs/' + baseurl
,
628 pveSelNode
: me
.pveSelNode
630 win
.on('destroy', me
.reload
, me
);
635 text
: gettext('CD/DVD Drive'),
636 iconCls
: 'pve-itype-icon-cdrom',
637 disabled
: !caps
.vms
['VM.Config.Disk'],
638 handler: function() {
639 var win
= Ext
.create('PVE.qemu.CDEdit', {
640 url
: '/api2/extjs/' + baseurl
,
641 pveSelNode
: me
.pveSelNode
643 win
.on('destroy', me
.reload
, me
);
648 text
: gettext('Network Device'),
650 iconCls
: 'fa fa-fw fa-exchange black',
651 disabled
: !caps
.vms
['VM.Config.Network'],
652 handler: function() {
653 var win
= Ext
.create('PVE.qemu.NetworkEdit', {
654 url
: '/api2/extjs/' + baseurl
,
655 pveSelNode
: me
.pveSelNode
,
658 win
.on('destroy', me
.reload
, me
);
664 text
: gettext('USB Device'),
666 iconCls
: 'fa fa-fw fa-usb black',
667 disabled
: !caps
.nodes
['Sys.Console'],
668 handler: function() {
669 var win
= Ext
.create('PVE.qemu.USBEdit', {
670 url
: '/api2/extjs/' + baseurl
,
671 pveSelNode
: me
.pveSelNode
673 win
.on('destroy', me
.reload
, me
);
678 text
: gettext('PCI Device'),
680 iconCls
: 'pve-itype-icon-pci',
681 disabled
: !caps
.nodes
['Sys.Console'],
682 handler: function() {
683 var win
= Ext
.create('PVE.qemu.PCIEdit', {
684 url
: '/api2/extjs/' + baseurl
,
685 pveSelNode
: me
.pveSelNode
687 win
.on('destroy', me
.reload
, me
);
692 text
: gettext('Serial Port'),
694 iconCls
: 'pve-itype-icon-serial',
695 disabled
: !caps
.vms
['VM.Config.Options'],
696 handler: function() {
697 var win
= Ext
.create('PVE.qemu.SerialEdit', {
698 url
: '/api2/extjs/' + baseurl
700 win
.on('destroy', me
.reload
, me
);
705 text
: gettext('CloudInit Drive'),
707 iconCls
: 'fa fa-fw fa-cloud black',
708 disabled
: !caps
.nodes
['Sys.Console'],
709 handler: function() {
710 var win
= Ext
.create('PVE.qemu.CIDriveEdit', {
711 url
: '/api2/extjs/' + baseurl
,
712 pveSelNode
: me
.pveSelNode
714 win
.on('destroy', me
.reload
, me
);
719 text
: gettext('Audio Device'),
721 iconCls
: 'fa fa-fw fa-volume-up black',
722 disabled
: !caps
.vms
['VM.Config.HWType'],
723 handler: function() {
724 var win
= Ext
.create('PVE.qemu.AudioEdit', {
725 url
: '/api2/extjs/' + baseurl
,
729 win
.on('destroy', me
.reload
, me
);
734 text
: gettext("VirtIO RNG"),
736 iconCls
: 'pve-itype-icon-die',
737 disabled
: !caps
.nodes
['Sys.Console'],
738 handler: function() {
739 var win
= Ext
.create('PVE.qemu.RNGEdit', {
740 url
: '/api2/extjs/' + baseurl
,
744 win
.on('destroy', me
.reload
, me
);
760 itemdblclick
: run_editor
,
761 selectionchange
: set_button_status
767 me
.on('activate', me
.rstore
.startUpdate
, me
.rstore
);
768 me
.on('destroy', me
.rstore
.stopUpdate
, me
.rstore
);
770 me
.mon(me
.getStore(), 'datachanged', set_button_status
, me
);