]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/HardwareView.js
6bbc090c64c965ca1f128104aa432222a66132e6
[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 never_delete: true,
218 header: gettext('USB Device') + ' (' + confid + ')'
219 };
220 }
221 for (i = 0; i < 8; i++) {
222 rows["unused" + i] = {
223 group: 5,
224 tdCls: 'pve-itype-icon-storage',
225 editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined,
226 header: gettext('Unused Disk') + ' ' + i
227 };
228 }
229
230 var sorterFn = function(rec1, rec2) {
231 var v1 = rec1.data.key;
232 var v2 = rec2.data.key;
233 var g1 = rows[v1].group || 0;
234 var g2 = rows[v2].group || 0;
235
236 return (g1 !== g2) ?
237 (g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
238 };
239
240 var reload = function() {
241 me.rstore.load();
242 };
243
244 var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config';
245
246 var sm = Ext.create('Ext.selection.RowModel', {});
247
248 var run_editor = function() {
249 var rec = sm.getSelection()[0];
250 if (!rec) {
251 return;
252 }
253
254 var rowdef = rows[rec.data.key];
255 if (!rowdef.editor) {
256 return;
257 }
258
259 var editor = rowdef.editor;
260 if (rowdef.tdCls == 'pve-itype-icon-storage') {
261 var value = me.getObjectValue(rec.data.key, '', true);
262 if (value.match(/media=cdrom/)) {
263 editor = 'PVE.qemu.CDEdit';
264 }
265 }
266
267 var win;
268
269 if (Ext.isString(editor)) {
270 win = Ext.create(editor, {
271 pveSelNode: me.pveSelNode,
272 confid: rec.data.key,
273 url: '/api2/extjs/' + baseurl
274 });
275 } else {
276 var config = Ext.apply({
277 pveSelNode: me.pveSelNode,
278 confid: rec.data.key,
279 url: '/api2/extjs/' + baseurl
280 }, rowdef.editor);
281 win = Ext.createWidget(rowdef.editor.xtype, config);
282 win.load();
283 }
284
285 win.show();
286 win.on('destroy', reload);
287 };
288
289 var run_diskthrottle = function() {
290 var rec = sm.getSelection()[0];
291 if (!rec) {
292 return;
293 }
294
295 var win = Ext.create('PVE.qemu.HDThrottle', {
296 pveSelNode: me.pveSelNode,
297 confid: rec.data.key,
298 url: '/api2/extjs/' + baseurl
299 });
300
301 win.show();
302 win.on('destroy', reload);
303 };
304
305 var run_resize = function() {
306 var rec = sm.getSelection()[0];
307 if (!rec) {
308 return;
309 }
310
311 var win = Ext.create('PVE.window.HDResize', {
312 disk: rec.data.key,
313 nodename: nodename,
314 vmid: vmid
315 });
316
317 win.show();
318
319 win.on('destroy', reload);
320 };
321
322 var run_cpuoptions = function() {
323 var sockets = me.getObjectValue('sockets', 1);
324 var cores = me.getObjectValue('cores', 1);
325
326 var win = Ext.create('PVE.qemu.CPUOptions', {
327 maxvcpus: sockets * cores,
328 vmid: vmid,
329 pveSelNode: me.pveSelNode,
330 url: '/api2/extjs/' + baseurl
331 });
332
333 win.show();
334
335 win.on('destroy', reload);
336 };
337
338 var run_move = function() {
339 var rec = sm.getSelection()[0];
340 if (!rec) {
341 return;
342 }
343
344 var win = Ext.create('PVE.window.HDMove', {
345 disk: rec.data.key,
346 nodename: nodename,
347 vmid: vmid
348 });
349
350 win.show();
351
352 win.on('destroy', reload);
353 };
354
355 var edit_btn = new PVE.button.Button({
356 text: gettext('Edit'),
357 selModel: sm,
358 disabled: true,
359 handler: run_editor
360 });
361
362 var resize_btn = new PVE.button.Button({
363 text: gettext('Resize disk'),
364 selModel: sm,
365 disabled: true,
366 handler: run_resize
367 });
368
369 var move_btn = new PVE.button.Button({
370 text: gettext('Move disk'),
371 selModel: sm,
372 disabled: true,
373 handler: run_move
374 });
375
376 var diskthrottle_btn = new PVE.button.Button({
377 text: gettext('Disk Throttle'),
378 selModel: sm,
379 disabled: true,
380 handler: run_diskthrottle
381 });
382
383 var cpuoptions_btn = new Ext.Button({
384 text: gettext('CPU options'),
385 handler: run_cpuoptions
386 });
387
388 var remove_btn = new PVE.button.Button({
389 text: gettext('Remove'),
390 selModel: sm,
391 disabled: true,
392 dangerous: true,
393 confirmMsg: function(rec) {
394 var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'),
395 "'" + me.renderKey(rec.data.key, {}, rec) + "'");
396 if (rec.data.key.match(/^unused\d+$/)) {
397 msg += " " + gettext('This will permanently erase all data.');
398 }
399
400 return msg;
401 },
402 handler: function(b, e, rec) {
403 PVE.Utils.API2Request({
404 url: '/api2/extjs/' + baseurl,
405 waitMsgTarget: me,
406 method: 'PUT',
407 params: {
408 'delete': rec.data.key
409 },
410 callback: function() {
411 reload();
412 },
413 failure: function (response, opts) {
414 Ext.Msg.alert('Error', response.htmlStatus);
415 }
416 });
417 }
418 });
419
420 var revert_btn = new PVE.button.Button({
421 text: gettext('Revert'),
422 selModel: sm,
423 disabled: true,
424 handler: function(b, e, rec) {
425 var rowdef = me.rows[rec.data.key] || {};
426 var keys = rowdef.multiKey || [ rec.data.key ];
427 var revert = keys.join(',');
428 PVE.Utils.API2Request({
429 url: '/api2/extjs/' + baseurl,
430 waitMsgTarget: me,
431 method: 'PUT',
432 params: {
433 'revert': revert
434 },
435 callback: function() {
436 reload();
437 },
438 failure: function (response, opts) {
439 Ext.Msg.alert('Error',response.htmlStatus);
440 }
441 });
442 }
443 });
444
445 var efidisk_menuitem = Ext.create('Ext.menu.Item',{
446 text: gettext('EFI Disk'),
447 iconCls: 'pve-itype-icon-storage',
448 disabled: !caps.vms['VM.Config.Disk'],
449 handler: function() {
450
451 var rstoredata = me.rstore.getData().map;
452 // check if ovmf is configured
453 if (rstoredata.bios && rstoredata.bios.data.value === 'ovmf') {
454 var win = Ext.create('PVE.qemu.EFIDiskEdit', {
455 url: '/api2/extjs/' + baseurl,
456 pveSelNode: me.pveSelNode
457 });
458 win.on('destroy', reload);
459 win.show();
460 } else {
461 Ext.Msg.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.'));
462 }
463
464 }
465 });
466
467 var set_button_status = function() {
468 var sm = me.getSelectionModel();
469 var rec = sm.getSelection()[0];
470
471 // disable button when we have an efidisk already
472 // disable is ok in this case, because you can instantly
473 // see that there is already one
474 efidisk_menuitem.setDisabled(me.rstore.getData().map.efidisk0 !== undefined);
475
476 if (!rec) {
477 remove_btn.disable();
478 edit_btn.disable();
479 resize_btn.disable();
480 move_btn.disable();
481 diskthrottle_btn.disable();
482 revert_btn.disable();
483 return;
484 }
485 var key = rec.data.key;
486 var value = rec.data.value;
487 var rowdef = rows[key];
488
489 var pending = rec.data['delete'] || me.hasPendingChanges(key);
490 var isDisk = !key.match(/^unused\d+/) &&
491 rowdef.tdCls == 'pve-itype-icon-storage' &&
492 (value && !value.match(/media=cdrom/));
493
494 var isEfi = (key === 'efidisk0');
495
496
497 remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true));
498
499 edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor);
500
501 resize_btn.setDisabled(pending || !isDisk);
502
503 move_btn.setDisabled(pending || !isDisk);
504
505 diskthrottle_btn.setDisabled(pending || !isDisk || isEfi);
506
507 revert_btn.setDisabled(!pending);
508
509 };
510
511 Ext.apply(me, {
512 url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending',
513 interval: 5000,
514 selModel: sm,
515 tbar: [
516 {
517 text: gettext('Add'),
518 menu: new Ext.menu.Menu({
519 items: [
520 {
521 text: gettext('Hard Disk'),
522 iconCls: 'pve-itype-icon-storage',
523 disabled: !caps.vms['VM.Config.Disk'],
524 handler: function() {
525 var win = Ext.create('PVE.qemu.HDEdit', {
526 url: '/api2/extjs/' + baseurl,
527 pveSelNode: me.pveSelNode
528 });
529 win.on('destroy', reload);
530 win.show();
531 }
532 },
533 {
534 text: gettext('CD/DVD Drive'),
535 iconCls: 'pve-itype-icon-cdrom',
536 disabled: !caps.vms['VM.Config.Disk'],
537 handler: function() {
538 var win = Ext.create('PVE.qemu.CDEdit', {
539 url: '/api2/extjs/' + baseurl,
540 pveSelNode: me.pveSelNode
541 });
542 win.on('destroy', reload);
543 win.show();
544 }
545 },
546 {
547 text: gettext('Network Device'),
548 iconCls: 'pve-itype-icon-network',
549 disabled: !caps.vms['VM.Config.Network'],
550 handler: function() {
551 var win = Ext.create('PVE.qemu.NetworkEdit', {
552 url: '/api2/extjs/' + baseurl,
553 pveSelNode: me.pveSelNode
554 });
555 win.on('destroy', reload);
556 win.show();
557 }
558 },
559 efidisk_menuitem
560 ]
561 })
562 },
563 remove_btn,
564 edit_btn,
565 resize_btn,
566 move_btn,
567 diskthrottle_btn,
568 cpuoptions_btn,
569 revert_btn
570 ],
571 rows: rows,
572 sorterFn: sorterFn,
573 listeners: {
574 itemdblclick: run_editor,
575 selectionchange: set_button_status
576 }
577 });
578
579 me.callParent();
580
581 me.on('activate', me.rstore.startUpdate);
582 me.on('destroy', me.rstore.stopUpdate);
583
584 me.mon(me.rstore, 'refresh', function() {
585 set_button_status();
586 });
587 }
588 });