]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
show hostpci devices in gui
[pve-manager.git] / www / manager6 / qemu / HardwareView.js
1 // fixme: howto avoid jslint type confusion?
2 /*jslint confusion: true */
3 Ext.define('PVE.qemu.HardwareView', {
4 extend: 'PVE.grid.PendingObjectGrid',
5 alias: ['widget.PVE.qemu.HardwareView'],
6
7 onlineHelp: 'qm_virtual_machines_settings',
8
9 renderKey: function(key, metaData, rec, rowIndex, colIndex, store) {
10 var me = this;
11 var rows = me.rows;
12 var rowdef = rows[key] || {};
13
14 metaData.tdAttr = "valign=middle";
15
16 if (rowdef.tdCls) {
17 metaData.tdCls = rowdef.tdCls;
18 if (rowdef.tdCls == 'pve-itype-icon-storage') {
19 var value = me.getObjectValue(key, '', true);
20 if (value.match(/media=cdrom/)) {
21 metaData.tdCls = 'pve-itype-icon-cdrom';
22 return rowdef.cdheader;
23 }
24 }
25 }
26 return rowdef.header || key;
27 },
28
29 initComponent : function() {
30 var me = this;
31 var i, confid;
32
33 var nodename = me.pveSelNode.data.node;
34 if (!nodename) {
35 throw "no node name specified";
36 }
37
38 var vmid = me.pveSelNode.data.vmid;
39 if (!vmid) {
40 throw "no VM ID specified";
41 }
42
43 var caps = Ext.state.Manager.get('GuiCap');
44
45 var rows = {
46 memory: {
47 header: gettext('Memory'),
48 editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined,
49 never_delete: true,
50 defaultValue: 512,
51 tdCls: 'pve-itype-icon-memory',
52 renderer: function(value, metaData, record) {
53 var balloon = me.getObjectValue('balloon');
54 if (balloon) {
55 return PVE.Utils.format_size(balloon*1024*1024) + "/" +
56 PVE.Utils.format_size(value*1024*1024);
57
58 }
59 return PVE.Utils.format_size(value*1024*1024);
60 }
61 },
62 sockets: {
63 header: gettext('Processors'),
64 never_delete: true,
65 editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ?
66 'PVE.qemu.ProcessorEdit' : undefined,
67 tdCls: 'pve-itype-icon-processor',
68 defaultValue: 1,
69 multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'],
70 renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) {
71
72 var sockets = me.getObjectValue('sockets', 1, pending);
73 var model = me.getObjectValue('cpu', undefined, pending);
74 var cores = me.getObjectValue('cores', 1, pending);
75 var numa = me.getObjectValue('numa', undefined, pending);
76 var vcpus = me.getObjectValue('vcpus', undefined, pending);
77 var cpulimit = me.getObjectValue('cpulimit', undefined, pending);
78 var cpuunits = me.getObjectValue('cpuunits', undefined, pending);
79
80 var res = (sockets*cores) + ' (' + sockets + ' sockets, ' + cores + ' cores)';
81
82 if (model) {
83 res += ' [' + model + ']';
84 }
85
86 if (numa) {
87 res += ' [numa=' + numa +']';
88 }
89
90 if (vcpus) {
91 res += ' [vcpus=' + vcpus +']';
92 }
93
94 if (cpulimit) {
95 res += ' [cpulimit=' + cpulimit +']';
96 }
97
98 if (cpuunits) {
99 res += ' [cpuunits=' + cpuunits +']';
100 }
101
102 return res;
103 }
104 },
105 keyboard: {
106 header: gettext('Keyboard Layout'),
107 never_delete: true,
108 editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.KeyboardEdit' : undefined,
109 tdCls: 'pve-itype-icon-keyboard',
110 defaultValue: '',
111 renderer: PVE.Utils.render_kvm_language
112 },
113 vga: {
114 header: gettext('Display'),
115 editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined,
116 never_delete: true,
117 tdCls: 'pve-itype-icon-display',
118 defaultValue: '',
119 renderer: PVE.Utils.render_kvm_vga_driver
120 },
121 cores: {
122 visible: false
123 },
124 cpu: {
125 visible: false
126 },
127 numa: {
128 visible: false
129 },
130 balloon: {
131 visible: false
132 },
133 hotplug: {
134 visible: false
135 },
136 vcpus: {
137 visible: false
138 },
139 cpuunits: {
140 visible: false
141 },
142 cpulimit: {
143 visible: false
144 },
145 bios: {
146 visible: false
147 }
148
149 };
150
151 for (i = 0; i < 4; i++) {
152 confid = "ide" + i;
153 rows[confid] = {
154 group: 1,
155 tdCls: 'pve-itype-icon-storage',
156 editor: 'PVE.qemu.HDEdit',
157 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
158 header: gettext('Hard Disk') + ' (' + confid +')',
159 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
160 };
161 }
162 for (i = 0; i < 6; i++) {
163 confid = "sata" + i;
164 rows[confid] = {
165 group: 1,
166 tdCls: 'pve-itype-icon-storage',
167 editor: 'PVE.qemu.HDEdit',
168 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
169 header: gettext('Hard Disk') + ' (' + confid +')',
170 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
171 };
172 }
173 for (i = 0; i < 16; i++) {
174 confid = "scsi" + i;
175 rows[confid] = {
176 group: 1,
177 tdCls: 'pve-itype-icon-storage',
178 editor: 'PVE.qemu.HDEdit',
179 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
180 header: gettext('Hard Disk') + ' (' + confid +')',
181 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
182 };
183 }
184 for (i = 0; i < 16; i++) {
185 confid = "virtio" + i;
186 rows[confid] = {
187 group: 1,
188 tdCls: 'pve-itype-icon-storage',
189 editor: 'PVE.qemu.HDEdit',
190 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
191 header: gettext('Hard Disk') + ' (' + confid +')',
192 cdheader: gettext('CD/DVD Drive') + ' (' + confid +')'
193 };
194 }
195 for (i = 0; i < 32; i++) {
196 confid = "net" + i;
197 rows[confid] = {
198 group: 2,
199 tdCls: 'pve-itype-icon-network',
200 editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined,
201 never_delete: caps.vms['VM.Config.Network'] ? false : true,
202 header: gettext('Network Device') + ' (' + confid +')'
203 };
204 }
205 rows.efidisk0 = {
206 group: 3,
207 tdCls: 'pve-itype-icon-storage',
208 editor: null,
209 never_delete: caps.vms['VM.Config.Disk'] ? false : true,
210 header: gettext('EFI Disk')
211 };
212 for (i = 0; i < 5; i++) {
213 confid = "usb" + i;
214 rows[confid] = {
215 group: 4,
216 tdCls: 'pve-itype-icon-usb',
217 editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined,
218 never_delete: caps.nodes['Sys.Console'] ? false : true,
219 header: gettext('USB Device') + ' (' + confid + ')'
220 };
221 }
222 for (i = 0; i < 4; i++) {
223 confid = "hostpci" + i;
224 rows[confid] = {
225 group: 5,
226 tdCls: 'pve-itype-icon-pci',
227 never_delete: caps.nodes['Sys.Console'] ? false : true,
228 header: gettext('PCI Device') + ' (' + confid + ')'
229 };
230 }
231 for (i = 0; i < 8; i++) {
232 rows["unused" + i] = {
233 group: 6,
234 tdCls: 'pve-itype-icon-storage',
235 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
236 header: gettext('Unused Disk') + ' ' + i
237 };
238 }
239
240 var sorterFn = function(rec1, rec2) {
241 var v1 = rec1.data.key;
242 var v2 = rec2.data.key;
243 var g1 = rows[v1].group || 0;
244 var g2 = rows[v2].group || 0;
245
246 return (g1 !== g2) ?
247 (g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
248 };
249
250 var reload = function() {
251 me.rstore.load();
252 };
253
254 var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
255
256 var sm = Ext.create('Ext.selection.RowModel', {});
257
258 var run_editor = function() {
259 var rec = sm.getSelection()[0];
260 if (!rec) {
261 return;
262 }
263
264 var rowdef = rows[rec.data.key];
265 if (!rowdef.editor) {
266 return;
267 }
268
269 var editor = rowdef.editor;
270 if (rowdef.tdCls == 'pve-itype-icon-storage') {
271 var value = me.getObjectValue(rec.data.key, '', true);
272 if (value.match(/media=cdrom/)) {
273 editor = 'PVE.qemu.CDEdit';
274 }
275 }
276
277 var win;
278
279 if (Ext.isString(editor)) {
280 win = Ext.create(editor, {
281 pveSelNode: me.pveSelNode,
282 confid: rec.data.key,
283 url: '/api2/extjs/' + baseurl
284 });
285 } else {
286 var config = Ext.apply({
287 pveSelNode: me.pveSelNode,
288 confid: rec.data.key,
289 url: '/api2/extjs/' + baseurl
290 }, rowdef.editor);
291 win = Ext.createWidget(rowdef.editor.xtype, config);
292 win.load();
293 }
294
295 win.show();
296 win.on('destroy', reload);
297 };
298
299 var run_diskthrottle = function() {
300 var rec = sm.getSelection()[0];
301 if (!rec) {
302 return;
303 }
304
305 var win = Ext.create('PVE.qemu.HDThrottle', {
306 pveSelNode: me.pveSelNode,
307 confid: rec.data.key,
308 url: '/api2/extjs/' + baseurl
309 });
310
311 win.show();
312 win.on('destroy', reload);
313 };
314
315 var run_resize = function() {
316 var rec = sm.getSelection()[0];
317 if (!rec) {
318 return;
319 }
320
321 var win = Ext.create('PVE.window.HDResize', {
322 disk: rec.data.key,
323 nodename: nodename,
324 vmid: vmid
325 });
326
327 win.show();
328
329 win.on('destroy', reload);
330 };
331
332 var run_cpuoptions = function() {
333 var sockets = me.getObjectValue('sockets', 1);
334 var cores = me.getObjectValue('cores', 1);
335
336 var win = Ext.create('PVE.qemu.CPUOptions', {
337 maxvcpus: sockets * cores,
338 vmid: vmid,
339 pveSelNode: me.pveSelNode,
340 url: '/api2/extjs/' + baseurl
341 });
342
343 win.show();
344
345 win.on('destroy', reload);
346 };
347
348 var run_move = function() {
349 var rec = sm.getSelection()[0];
350 if (!rec) {
351 return;
352 }
353
354 var win = Ext.create('PVE.window.HDMove', {
355 disk: rec.data.key,
356 nodename: nodename,
357 vmid: vmid
358 });
359
360 win.show();
361
362 win.on('destroy', reload);
363 };
364
365 var edit_btn = new PVE.button.Button({
366 text: gettext('Edit'),
367 selModel: sm,
368 disabled: true,
369 handler: run_editor
370 });
371
372 var resize_btn = new PVE.button.Button({
373 text: gettext('Resize disk'),
374 selModel: sm,
375 disabled: true,
376 handler: run_resize
377 });
378
379 var move_btn = new PVE.button.Button({
380 text: gettext('Move disk'),
381 selModel: sm,
382 disabled: true,
383 handler: run_move
384 });
385
386 var diskthrottle_btn = new PVE.button.Button({
387 text: gettext('Disk Throttle'),
388 selModel: sm,
389 disabled: true,
390 handler: run_diskthrottle
391 });
392
393 var cpuoptions_btn = new Ext.Button({
394 text: gettext('CPU options'),
395 handler: run_cpuoptions
396 });
397
398 var remove_btn = new PVE.button.Button({
399 text: gettext('Remove'),
400 selModel: sm,
401 disabled: true,
402 dangerous: true,
403 confirmMsg: function(rec) {
404 var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
405 "'" + me.renderKey(rec.data.key, {}, rec) + "'");
406 if (rec.data.key.match(/^unused\d+$/)) {
407 msg += " " + gettext('This will permanently erase all data.');
408 }
409
410 return msg;
411 },
412 handler: function(b, e, rec) {
413 PVE.Utils.API2Request({
414 url: '/api2/extjs/' + baseurl,
415 waitMsgTarget: me,
416 method: 'PUT',
417 params: {
418 'delete': rec.data.key
419 },
420 callback: function() {
421 reload();
422 },
423 failure: function (response, opts) {
424 Ext.Msg.alert('Error', response.htmlStatus);
425 }
426 });
427 }
428 });
429
430 var revert_btn = new PVE.button.Button({
431 text: gettext('Revert'),
432 selModel: sm,
433 disabled: true,
434 handler: function(b, e, rec) {
435 var rowdef = me.rows[rec.data.key] || {};
436 var keys = rowdef.multiKey || [ rec.data.key ];
437 var revert = keys.join(',');
438 PVE.Utils.API2Request({
439 url: '/api2/extjs/' + baseurl,
440 waitMsgTarget: me,
441 method: 'PUT',
442 params: {
443 'revert': revert
444 },
445 callback: function() {
446 reload();
447 },
448 failure: function (response, opts) {
449 Ext.Msg.alert('Error',response.htmlStatus);
450 }
451 });
452 }
453 });
454
455 var efidisk_menuitem = Ext.create('Ext.menu.Item',{
456 text: gettext('EFI Disk'),
457 iconCls: 'pve-itype-icon-storage',
458 disabled: !caps.vms['VM.Config.Disk'],
459 handler: function() {
460
461 var rstoredata = me.rstore.getData().map;
462 // check if ovmf is configured
463 if (rstoredata.bios && rstoredata.bios.data.value === 'ovmf') {
464 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
465 url: '/api2/extjs/' + baseurl,
466 pveSelNode: me.pveSelNode
467 });
468 win.on('destroy', reload);
469 win.show();
470 } else {
471 Ext.Msg.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.'));
472 }
473
474 }
475 });
476
477 var set_button_status = function() {
478 var sm = me.getSelectionModel();
479 var rec = sm.getSelection()[0];
480
481 // disable button when we have an efidisk already
482 // disable is ok in this case, because you can instantly
483 // see that there is already one
484 efidisk_menuitem.setDisabled(me.rstore.getData().map.efidisk0 !== undefined);
485 // en/disable usb add button
486 var count = 0;
487 me.rstore.getData().items.forEach(function(item){
488 if (/^usb\d+/.test(item.id)) {
489 count++;
490 }
491 });
492 me.down('#addusb').setDisabled((count >= 5));
493
494 if (!rec) {
495 remove_btn.disable();
496 edit_btn.disable();
497 resize_btn.disable();
498 move_btn.disable();
499 diskthrottle_btn.disable();
500 revert_btn.disable();
501 return;
502 }
503 var key = rec.data.key;
504 var value = rec.data.value;
505 var rowdef = rows[key];
506
507 var pending = rec.data['delete'] || me.hasPendingChanges(key);
508 var isDisk = !key.match(/^unused\d+/) &&
509 rowdef.tdCls == 'pve-itype-icon-storage' &&
510 (value && !value.match(/media=cdrom/));
511
512 var isEfi = (key === 'efidisk0');
513
514
515 remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true));
516
517 edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
518
519 resize_btn.setDisabled(pending || !isDisk);
520
521 move_btn.setDisabled(pending || !isDisk);
522
523 diskthrottle_btn.setDisabled(pending || !isDisk || isEfi);
524
525 revert_btn.setDisabled(!pending);
526
527 };
528
529 Ext.apply(me, {
530 url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
531 interval: 5000,
532 selModel: sm,
533 tbar: [
534 {
535 text: gettext('Add'),
536 menu: new Ext.menu.Menu({
537 items: [
538 {
539 text: gettext('Hard Disk'),
540 iconCls: 'pve-itype-icon-storage',
541 disabled: !caps.vms['VM.Config.Disk'],
542 handler: function() {
543 var win = Ext.create('PVE.qemu.HDEdit', {
544 url: '/api2/extjs/' + baseurl,
545 pveSelNode: me.pveSelNode
546 });
547 win.on('destroy', reload);
548 win.show();
549 }
550 },
551 {
552 text: gettext('CD/DVD Drive'),
553 iconCls: 'pve-itype-icon-cdrom',
554 disabled: !caps.vms['VM.Config.Disk'],
555 handler: function() {
556 var win = Ext.create('PVE.qemu.CDEdit', {
557 url: '/api2/extjs/' + baseurl,
558 pveSelNode: me.pveSelNode
559 });
560 win.on('destroy', reload);
561 win.show();
562 }
563 },
564 {
565 text: gettext('Network Device'),
566 iconCls: 'pve-itype-icon-network',
567 disabled: !caps.vms['VM.Config.Network'],
568 handler: function() {
569 var win = Ext.create('PVE.qemu.NetworkEdit', {
570 url: '/api2/extjs/' + baseurl,
571 pveSelNode: me.pveSelNode
572 });
573 win.on('destroy', reload);
574 win.show();
575 }
576 },
577 efidisk_menuitem,
578 {
579 text: gettext('USB Device'),
580 itemId: 'addusb',
581 iconCls: 'pve-itype-icon-usb',
582 disabled: !caps.nodes['Sys.Console'],
583 handler: function() {
584 var win = Ext.create('PVE.qemu.USBEdit', {
585 url: '/api2/extjs/' + baseurl,
586 pveSelNode: me.pveSelNode
587 });
588 win.on('destroy', reload);
589 win.show();
590 }
591 }
592 ]
593 })
594 },
595 remove_btn,
596 edit_btn,
597 resize_btn,
598 move_btn,
599 diskthrottle_btn,
600 cpuoptions_btn,
601 revert_btn
602 ],
603 rows: rows,
604 sorterFn: sorterFn,
605 listeners: {
606 itemdblclick: run_editor,
607 selectionchange: set_button_status
608 }
609 });
610
611 me.callParent();
612
613 me.on('activate', me.rstore.startUpdate);
614 me.on('destroy', me.rstore.stopUpdate);
615
616 me.mon(me.rstore, 'refresh', function() {
617 set_button_status();
618 });
619 }
620 });