]>
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";
18 metaData
.tdCls
= rowdef
.tdCls
;
19 if (rowdef
.tdCls
== 'pve-itype-icon-storage') {
20 var value
= me
.getObjectValue(key
, '', false);
22 value
= me
.getObjectValue(key
, '', true);
24 if (value
.match(/vm-.*-cloudinit/)) {
25 icon
= "<i class='pve-grid-fa fa fa-fw fa-cloud'></i>";
26 metaData
.tdCls
= " pve-itype-fa";
27 txt
= rowdef
.cloudheader
;
28 } else if (value
.match(/media=cdrom/)) {
29 metaData
.tdCls
= 'pve-itype-icon-cdrom';
30 return rowdef
.cdheader
;
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'];
63 /*jslint confusion: true */
66 header
: gettext('Memory'),
67 editor
: caps
.vms
['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
70 tdCls
: 'pve-itype-icon-memory',
72 multiKey
: ['memory', 'balloon', 'shares'],
73 renderer: function(value
, metaData
, record
, ri
, ci
, store
, pending
) {
76 var max
= me
.getObjectValue('memory', 512, pending
);
77 var balloon
= me
.getObjectValue('balloon', undefined, pending
);
78 var shares
= me
.getObjectValue('shares', undefined, pending
);
80 res
= Proxmox
.Utils
.format_size(max
*1024*1024);
82 if (balloon
!== undefined && balloon
> 0) {
83 res
= Proxmox
.Utils
.format_size(balloon
*1024*1024) + "/" + res
;
86 res
+= ' [shares=' + shares
+']';
88 } else if (balloon
=== 0) {
89 res
+= ' [balloon=0]';
95 header
: gettext('Processors'),
97 editor
: (caps
.vms
['VM.Config.CPU'] || caps
.vms
['VM.Config.HWType']) ?
98 'PVE.qemu.ProcessorEdit' : undefined,
99 tdCls
: 'pve-itype-icon-processor',
102 multiKey
: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
103 renderer: function(value
, metaData
, record
, rowIndex
, colIndex
, store
, pending
) {
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
);
113 var res
= Ext
.String
.format('{0} ({1} sockets, {2} cores)',
114 sockets
*cores
, sockets
, cores
);
117 res
+= ' [' + model
+ ']';
121 res
+= ' [numa=' + numa
+']';
125 res
+= ' [vcpus=' + vcpus
+']';
129 res
+= ' [cpulimit=' + cpulimit
+']';
133 res
+= ' [cpuunits=' + cpuunits
+']';
143 editor
: caps
.vms
['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined,
145 iconCls
: 'microchip',
146 renderer
: PVE
.Utils
.render_qemu_bios
149 header
: gettext('Display'),
150 editor
: caps
.vms
['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
152 tdCls
: 'pve-itype-icon-display',
155 renderer
: PVE
.Utils
.render_kvm_vga_driver
158 header
: gettext('Machine'),
159 editor
: caps
.vms
['VM.Config.HWType'] ? {
160 xtype
: 'proxmoxWindowEdit',
161 subject
: gettext('Machine'),
164 xtype
: 'proxmoxKVComboBox',
166 value
: '__default__',
167 fieldLabel
: gettext('Machine'),
169 ['__default__', PVE
.Utils
.render_qemu_machine('')],
177 renderer
: PVE
.Utils
.render_qemu_machine
180 header
: gettext('SCSI Controller'),
182 editor
: caps
.vms
['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined,
183 renderer
: PVE
.Utils
.render_scsihw
,
216 /*jslint confusion: false */
218 PVE
.Utils
.forEachBus(undefined, function(type
, id
) {
219 var confid
= type
+ id
;
222 tdCls
: 'pve-itype-icon-storage',
223 editor
: 'PVE.qemu.HDEdit',
224 never_delete
: caps
.vms
['VM.Config.Disk'] ? false : true,
225 header
: gettext('Hard Disk') + ' (' + confid
+')',
226 cdheader
: gettext('CD/DVD Drive') + ' (' + confid
+')',
227 cloudheader
: gettext('CloudInit Drive') + ' (' + confid
+ ')'
230 for (i
= 0; i
< 32; i
++) {
231 confid
= "net" + i
.toString();
235 tdCls
: 'pve-itype-icon-network',
236 editor
: caps
.vms
['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
237 never_delete
: caps
.vms
['VM.Config.Network'] ? false : true,
238 header
: gettext('Network Device') + ' (' + confid
+')'
243 tdCls
: 'pve-itype-icon-storage',
245 never_delete
: caps
.vms
['VM.Config.Disk'] ? false : true,
246 header
: gettext('EFI Disk')
248 for (i
= 0; i
< 5; i
++) {
249 confid
= "usb" + i
.toString();
254 editor
: caps
.nodes
['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
255 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
256 header
: gettext('USB Device') + ' (' + confid
+ ')'
259 for (i
= 0; i
< 4; i
++) {
260 confid
= "hostpci" + i
.toString();
264 tdCls
: 'pve-itype-icon-pci',
265 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
266 editor
: caps
.nodes
['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined,
267 header
: gettext('PCI Device') + ' (' + confid
+ ')'
270 for (i
= 0; i
< 4; i
++) {
271 confid
= "serial" + i
.toString();
275 tdCls
: 'pve-itype-icon-serial',
276 never_delete
: caps
.nodes
['Sys.Console'] ? false : true,
277 header
: gettext('Serial Port') + ' (' + confid
+ ')'
282 iconCls
: 'volume-up',
283 editor
: caps
.vms
['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined,
284 never_delete
: caps
.vms
['VM.Config.HWType'] ? false : true,
285 header
: gettext('Audio Device')
287 for (i
= 0; i
< 256; i
++) {
288 rows
["unused" + i
.toString()] = {
291 tdCls
: 'pve-itype-icon-storage',
292 editor
: caps
.vms
['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
293 header
: gettext('Unused Disk') + ' ' + i
.toString()
297 var sorterFn = function(rec1
, rec2
) {
298 var v1
= rec1
.data
.key
;
299 var v2
= rec2
.data
.key
;
300 var g1
= rows
[v1
].group
|| 0;
301 var g2
= rows
[v2
].group
|| 0;
302 var order1
= rows
[v1
].order
|| 0;
303 var order2
= rows
[v2
].order
|| 0;
305 if ((g1
- g2
) !== 0) {
309 if ((order1
- order2
) !== 0) {
310 return order1
- order2
;
315 } else if (v1
< v2
) {
322 var reload = function() {
326 var baseurl
= 'nodes/' + nodename
+ '/qemu/' + vmid
+ '/config';
328 var sm
= Ext
.create('Ext.selection.RowModel', {});
330 var run_editor = function() {
331 var rec
= sm
.getSelection()[0];
336 var rowdef
= rows
[rec
.data
.key
];
337 if (!rowdef
.editor
) {
341 var editor
= rowdef
.editor
;
342 if (rowdef
.tdCls
== 'pve-itype-icon-storage') {
343 var value
= me
.getObjectValue(rec
.data
.key
, '', true);
344 if (value
.match(/vm-.*-cloudinit/)) {
346 } else if (value
.match(/media=cdrom/)) {
347 editor
= 'PVE.qemu.CDEdit';
348 } else if (!diskCap
) {
355 if (Ext
.isString(editor
)) {
356 win
= Ext
.create(editor
, {
357 pveSelNode
: me
.pveSelNode
,
358 confid
: rec
.data
.key
,
359 url
: '/api2/extjs/' + baseurl
362 var config
= Ext
.apply({
363 pveSelNode
: me
.pveSelNode
,
364 confid
: rec
.data
.key
,
365 url
: '/api2/extjs/' + baseurl
367 win
= Ext
.createWidget(rowdef
.editor
.xtype
, config
);
372 win
.on('destroy', reload
);
375 var run_resize = function() {
376 var rec
= sm
.getSelection()[0];
381 var win
= Ext
.create('PVE.window.HDResize', {
389 win
.on('destroy', reload
);
392 var run_move = function() {
393 var rec
= sm
.getSelection()[0];
398 var win
= Ext
.create('PVE.window.HDMove', {
406 win
.on('destroy', reload
);
409 var edit_btn
= new Proxmox
.button
.Button({
410 text
: gettext('Edit'),
416 var resize_btn
= new Proxmox
.button
.Button({
417 text
: gettext('Resize disk'),
423 var move_btn
= new Proxmox
.button
.Button({
424 text
: gettext('Move disk'),
430 var remove_btn
= new Proxmox
.button
.Button({
431 text
: gettext('Remove'),
432 defaultText
: gettext('Remove'),
433 altText
: gettext('Detach'),
438 confirmMsg: function(rec
) {
439 var warn
= gettext('Are you sure you want to remove entry {0}');
440 if (this.text
=== this.altText
) {
441 warn
= gettext('Are you sure you want to detach entry {0}');
444 var entry
= rec
.data
.key
;
445 var rendered
= me
.renderKey(entry
, {}, rec
);
446 var msg
= Ext
.String
.format(warn
, "'" + rendered
+ "'");
448 if (entry
.match(/^unused\d+$/)) {
449 msg
+= " " + gettext('This will permanently erase all data.');
454 handler: function(b
, e
, rec
) {
455 Proxmox
.Utils
.API2Request({
456 url
: '/api2/extjs/' + baseurl
,
458 method
: b
.RESTMethod
,
460 'delete': rec
.data
.key
462 callback: function() {
465 failure: function (response
, opts
) {
466 Ext
.Msg
.alert('Error', response
.htmlStatus
);
468 success: function(response
, options
) {
469 if (b
.RESTMethod
=== 'POST') {
470 var upid
= response
.result
.data
;
471 var win
= Ext
.create('Proxmox.window.TaskProgress', {
474 destroy: function () {
485 render: function(btn
) {
486 // hack: calculate an optimal button width on first display
487 // to prevent the whole toolbar to move when we switch
488 // between the "Remove" and "Detach" labels
489 var def
= btn
.getSize().width
;
491 btn
.setText(btn
.altText
);
492 var alt
= btn
.getSize().width
;
494 btn
.setText(btn
.defaultText
);
496 var optimal
= alt
> def
? alt
: def
;
497 btn
.setSize({ width
: optimal
});
502 var revert_btn
= new Proxmox
.button
.Button({
503 text
: gettext('Revert'),
506 handler: function(b
, e
, rec
) {
507 var rowdef
= me
.rows
[rec
.data
.key
] || {};
508 var keys
= rowdef
.multiKey
|| [ rec
.data
.key
];
509 var revert
= keys
.join(',');
510 Proxmox
.Utils
.API2Request({
511 url
: '/api2/extjs/' + baseurl
,
517 callback: function() {
520 failure: function (response
, opts
) {
521 Ext
.Msg
.alert('Error',response
.htmlStatus
);
527 var efidisk_menuitem
= Ext
.create('Ext.menu.Item',{
528 text
: gettext('EFI Disk'),
529 iconCls
: 'pve-itype-icon-storage',
530 disabled
: !caps
.vms
['VM.Config.Disk'],
531 handler: function() {
533 var rstoredata
= me
.rstore
.getData().map
;
534 // check if ovmf is configured
535 if (rstoredata
.bios
&& rstoredata
.bios
.data
.value
=== 'ovmf') {
536 var win
= Ext
.create('PVE.qemu.EFIDiskEdit', {
537 url
: '/api2/extjs/' + baseurl
,
538 pveSelNode
: me
.pveSelNode
540 win
.on('destroy', reload
);
543 Ext
.Msg
.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.'));
549 var set_button_status = function() {
550 var sm
= me
.getSelectionModel();
551 var rec
= sm
.getSelection()[0];
553 // disable button when we have an efidisk already
554 // disable is ok in this case, because you can instantly
555 // see that there is already one
556 efidisk_menuitem
.setDisabled(me
.rstore
.getData().map
.efidisk0
!== undefined);
557 // en/disable usb add button
561 var hasCloudInit
= false;
562 me
.rstore
.getData().items
.forEach(function(item
){
563 if (/^usb\d+/.test(item
.id
)) {
565 } else if (/^hostpci\d+/.test(item
.id
)) {
567 } else if (/^audio\d+/.test(item
.id
)) {
570 if (!hasCloudInit
&& /vm-.*-cloudinit/.test(item
.data
.value
)) {
575 // heuristic only for disabling some stuff, the backend has the final word.
576 var noSysConsolePerm
= !caps
.nodes
['Sys.Console'];
577 var noVMConfigHWTypePerm
= !caps
.vms
['VM.Config.HWType'];
579 me
.down('#addusb').setDisabled(noSysConsolePerm
|| (usbcount
>= 5));
580 me
.down('#addpci').setDisabled(noSysConsolePerm
|| (pcicount
>= 4));
581 me
.down('#addaudio').setDisabled(noVMConfigHWTypePerm
|| (audiocount
>= 1));
582 me
.down('#addci').setDisabled(noSysConsolePerm
|| hasCloudInit
);
585 remove_btn
.disable();
587 resize_btn
.disable();
589 revert_btn
.disable();
592 var key
= rec
.data
.key
;
593 var value
= rec
.data
.value
;
594 var rowdef
= rows
[key
];
596 var pending
= rec
.data
['delete'] || me
.hasPendingChanges(key
);
597 var isCDRom
= (value
&& !!value
.toString().match(/media=cdrom/));
598 var isUnusedDisk
= key
.match(/^unused\d+/);
599 var isUsedDisk
= !isUnusedDisk
&&
600 rowdef
.tdCls
== 'pve-itype-icon-storage' &&
603 var isCloudInit
= (value
&& value
.toString().match(/vm-.*-cloudinit/));
605 var isEfi
= (key
=== 'efidisk0');
607 remove_btn
.setDisabled(rec
.data
['delete'] || (rowdef
.never_delete
=== true) || (isUnusedDisk
&& !diskCap
));
608 remove_btn
.setText((isUsedDisk
&& !isCloudInit
) ? remove_btn
.altText
: remove_btn
.defaultText
);
609 remove_btn
.RESTMethod
= isUnusedDisk
? 'POST':'PUT';
611 edit_btn
.setDisabled(rec
.data
['delete'] || !rowdef
.editor
|| isCloudInit
|| (!isCDRom
&& !diskCap
));
613 resize_btn
.setDisabled(pending
|| !isUsedDisk
|| !diskCap
);
615 move_btn
.setDisabled(pending
|| !isUsedDisk
|| !diskCap
);
617 revert_btn
.setDisabled(!pending
);
622 url
: '/api2/json/' + 'nodes/' + nodename
+ '/qemu/' + vmid
+ '/pending',
625 run_editor
: run_editor
,
628 text
: gettext('Add'),
629 menu
: new Ext
.menu
.Menu({
632 text
: gettext('Hard Disk'),
633 iconCls
: 'pve-itype-icon-storage',
634 disabled
: !caps
.vms
['VM.Config.Disk'],
635 handler: function() {
636 var win
= Ext
.create('PVE.qemu.HDEdit', {
637 url
: '/api2/extjs/' + baseurl
,
638 pveSelNode
: me
.pveSelNode
640 win
.on('destroy', reload
);
645 text
: gettext('CD/DVD Drive'),
646 iconCls
: 'pve-itype-icon-cdrom',
647 disabled
: !caps
.vms
['VM.Config.Disk'],
648 handler: function() {
649 var win
= Ext
.create('PVE.qemu.CDEdit', {
650 url
: '/api2/extjs/' + baseurl
,
651 pveSelNode
: me
.pveSelNode
653 win
.on('destroy', reload
);
658 text
: gettext('Network Device'),
659 iconCls
: 'pve-itype-icon-network',
660 disabled
: !caps
.vms
['VM.Config.Network'],
661 handler: function() {
662 var win
= Ext
.create('PVE.qemu.NetworkEdit', {
663 url
: '/api2/extjs/' + baseurl
,
664 pveSelNode
: me
.pveSelNode
,
667 win
.on('destroy', reload
);
673 text
: gettext('USB Device'),
675 iconCls
: 'fa fa-usb black',
676 disabled
: !caps
.nodes
['Sys.Console'],
677 handler: function() {
678 var win
= Ext
.create('PVE.qemu.USBEdit', {
679 url
: '/api2/extjs/' + baseurl
,
680 pveSelNode
: me
.pveSelNode
682 win
.on('destroy', reload
);
687 text
: gettext('PCI Device'),
689 iconCls
: 'pve-itype-icon-pci',
690 disabled
: !caps
.nodes
['Sys.Console'],
691 handler: function() {
692 var win
= Ext
.create('PVE.qemu.PCIEdit', {
693 url
: '/api2/extjs/' + baseurl
,
694 pveSelNode
: me
.pveSelNode
696 win
.on('destroy', reload
);
701 text
: gettext('Serial Port'),
703 iconCls
: 'pve-itype-icon-serial',
704 disabled
: !caps
.vms
['VM.Config.Options'],
705 handler: function() {
706 var win
= Ext
.create('PVE.qemu.SerialEdit', {
707 url
: '/api2/extjs/' + baseurl
709 win
.on('destroy', reload
);
714 text
: gettext('CloudInit Drive'),
716 iconCls
: 'fa fa-cloud black',
717 disabled
: !caps
.nodes
['Sys.Console'],
718 handler: function() {
719 var win
= Ext
.create('PVE.qemu.CIDriveEdit', {
720 url
: '/api2/extjs/' + baseurl
,
721 pveSelNode
: me
.pveSelNode
723 win
.on('destroy', reload
);
728 text
: gettext('Audio Device'),
730 iconCls
: 'fa fa-volume-up black',
731 disabled
: !caps
.vms
['VM.Config.HWType'],
732 handler: function() {
733 var win
= Ext
.create('PVE.qemu.AudioEdit', {
734 url
: '/api2/extjs/' + baseurl
,
738 win
.on('destroy', reload
);
754 itemdblclick
: run_editor
,
755 selectionchange
: set_button_status
761 me
.on('activate', me
.rstore
.startUpdate
);
762 me
.on('destroy', me
.rstore
.stopUpdate
);
764 me
.mon(me
.rstore
, 'refresh', function() {