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