]>
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 | |
142 | } | |
143 | ||
144 | }; | |
145 | ||
146 | for (i = 0; i < 4; i++) { | |
147 | confid = "ide" + i; | |
148 | rows[confid] = { | |
149 | group: 1, | |
150 | tdCls: 'pve-itype-icon-storage', | |
151 | editor: 'PVE.qemu.HDEdit', | |
152 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
153 | header: gettext('Hard Disk') + ' (' + confid +')', | |
154 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')' | |
155 | }; | |
156 | } | |
157 | for (i = 0; i < 6; i++) { | |
158 | confid = "sata" + i; | |
159 | rows[confid] = { | |
160 | group: 1, | |
161 | tdCls: 'pve-itype-icon-storage', | |
162 | editor: 'PVE.qemu.HDEdit', | |
163 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
164 | header: gettext('Hard Disk') + ' (' + confid +')', | |
165 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')' | |
166 | }; | |
167 | } | |
168 | for (i = 0; i < 16; i++) { | |
169 | confid = "scsi" + i; | |
170 | rows[confid] = { | |
171 | group: 1, | |
172 | tdCls: 'pve-itype-icon-storage', | |
173 | editor: 'PVE.qemu.HDEdit', | |
174 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
175 | header: gettext('Hard Disk') + ' (' + confid +')', | |
176 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')' | |
177 | }; | |
178 | } | |
179 | for (i = 0; i < 16; i++) { | |
180 | confid = "virtio" + i; | |
181 | rows[confid] = { | |
182 | group: 1, | |
183 | tdCls: 'pve-itype-icon-storage', | |
184 | editor: 'PVE.qemu.HDEdit', | |
185 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
186 | header: gettext('Hard Disk') + ' (' + confid +')', | |
187 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')' | |
188 | }; | |
189 | } | |
190 | for (i = 0; i < 32; i++) { | |
191 | confid = "net" + i; | |
192 | rows[confid] = { | |
193 | group: 2, | |
194 | tdCls: 'pve-itype-icon-network', | |
195 | editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined, | |
196 | never_delete: caps.vms['VM.Config.Network'] ? false : true, | |
197 | header: gettext('Network Device') + ' (' + confid +')' | |
198 | }; | |
199 | } | |
200 | for (i = 0; i < 8; i++) { | |
201 | rows["unused" + i] = { | |
202 | group: 3, | |
203 | tdCls: 'pve-itype-icon-storage', | |
204 | editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined, | |
205 | header: gettext('Unused Disk') + ' ' + i | |
206 | }; | |
207 | } | |
208 | ||
209 | var sorterFn = function(rec1, rec2) { | |
210 | var v1 = rec1.data.key; | |
211 | var v2 = rec2.data.key; | |
212 | var g1 = rows[v1].group || 0; | |
213 | var g2 = rows[v2].group || 0; | |
214 | ||
215 | return (g1 !== g2) ? | |
216 | (g1 > g2 ? 1 : -1) : (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0)); | |
217 | }; | |
218 | ||
219 | var reload = function() { | |
220 | me.rstore.load(); | |
221 | }; | |
222 | ||
223 | var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config'; | |
224 | ||
225 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
226 | ||
227 | var run_editor = function() { | |
228 | var rec = sm.getSelection()[0]; | |
229 | if (!rec) { | |
230 | return; | |
231 | } | |
232 | ||
233 | var rowdef = rows[rec.data.key]; | |
234 | if (!rowdef.editor) { | |
235 | return; | |
236 | } | |
237 | ||
238 | var editor = rowdef.editor; | |
239 | if (rowdef.tdCls == 'pve-itype-icon-storage') { | |
240 | var value = me.getObjectValue(rec.data.key, '', true); | |
241 | if (value.match(/media=cdrom/)) { | |
242 | editor = 'PVE.qemu.CDEdit'; | |
243 | } | |
244 | } | |
245 | ||
246 | var win; | |
247 | ||
248 | if (Ext.isString(editor)) { | |
249 | win = Ext.create(editor, { | |
250 | pveSelNode: me.pveSelNode, | |
251 | confid: rec.data.key, | |
252 | url: '/api2/extjs/' + baseurl | |
253 | }); | |
254 | } else { | |
255 | var config = Ext.apply({ | |
256 | pveSelNode: me.pveSelNode, | |
257 | confid: rec.data.key, | |
258 | url: '/api2/extjs/' + baseurl | |
259 | }, rowdef.editor); | |
260 | win = Ext.createWidget(rowdef.editor.xtype, config); | |
261 | win.load(); | |
262 | } | |
263 | ||
264 | win.show(); | |
265 | win.on('destroy', reload); | |
266 | }; | |
267 | ||
268 | var run_diskthrottle = function() { | |
269 | var rec = sm.getSelection()[0]; | |
270 | if (!rec) { | |
271 | return; | |
272 | } | |
273 | ||
274 | var win = Ext.create('PVE.qemu.HDThrottle', { | |
275 | pveSelNode: me.pveSelNode, | |
276 | confid: rec.data.key, | |
277 | url: '/api2/extjs/' + baseurl | |
278 | }); | |
279 | ||
280 | win.show(); | |
281 | win.on('destroy', reload); | |
282 | }; | |
283 | ||
284 | var run_resize = function() { | |
285 | var rec = sm.getSelection()[0]; | |
286 | if (!rec) { | |
287 | return; | |
288 | } | |
289 | ||
290 | var win = Ext.create('PVE.window.HDResize', { | |
291 | disk: rec.data.key, | |
292 | nodename: nodename, | |
293 | vmid: vmid | |
294 | }); | |
295 | ||
296 | win.show(); | |
297 | ||
298 | win.on('destroy', reload); | |
299 | }; | |
300 | ||
301 | var run_cpuoptions = function() { | |
302 | var sockets = me.getObjectValue('sockets', 1); | |
303 | var cores = me.getObjectValue('cores', 1); | |
304 | ||
305 | var win = Ext.create('PVE.qemu.CPUOptions', { | |
306 | maxvcpus: sockets * cores, | |
307 | vmid: vmid, | |
308 | pveSelNode: me.pveSelNode, | |
309 | url: '/api2/extjs/' + baseurl | |
310 | }); | |
311 | ||
312 | win.show(); | |
313 | ||
314 | win.on('destroy', reload); | |
315 | }; | |
316 | ||
317 | var run_move = function() { | |
318 | var rec = sm.getSelection()[0]; | |
319 | if (!rec) { | |
320 | return; | |
321 | } | |
322 | ||
323 | var win = Ext.create('PVE.window.HDMove', { | |
324 | disk: rec.data.key, | |
325 | nodename: nodename, | |
326 | vmid: vmid | |
327 | }); | |
328 | ||
329 | win.show(); | |
330 | ||
331 | win.on('destroy', reload); | |
332 | }; | |
333 | ||
334 | var edit_btn = new PVE.button.Button({ | |
335 | text: gettext('Edit'), | |
336 | selModel: sm, | |
337 | disabled: true, | |
338 | handler: run_editor | |
339 | }); | |
340 | ||
341 | var resize_btn = new PVE.button.Button({ | |
342 | text: gettext('Resize disk'), | |
343 | selModel: sm, | |
344 | disabled: true, | |
345 | handler: run_resize | |
346 | }); | |
347 | ||
348 | var move_btn = new PVE.button.Button({ | |
349 | text: gettext('Move disk'), | |
350 | selModel: sm, | |
351 | disabled: true, | |
352 | handler: run_move | |
353 | }); | |
354 | ||
355 | var diskthrottle_btn = new PVE.button.Button({ | |
356 | text: gettext('Disk Throttle'), | |
357 | selModel: sm, | |
358 | disabled: true, | |
359 | handler: run_diskthrottle | |
360 | }); | |
361 | ||
362 | var cpuoptions_btn = new Ext.Button({ | |
363 | text: gettext('CPU options'), | |
364 | handler: run_cpuoptions | |
365 | }); | |
366 | ||
367 | var remove_btn = new PVE.button.Button({ | |
368 | text: gettext('Remove'), | |
369 | selModel: sm, | |
370 | disabled: true, | |
371 | dangerous: true, | |
372 | confirmMsg: function(rec) { | |
373 | var msg = Ext.String.format(gettext('Are you sure you want to remove entry {0}'), | |
374 | "'" + me.renderKey(rec.data.key, {}, rec) + "'"); | |
375 | if (rec.data.key.match(/^unused\d+$/)) { | |
376 | msg += " " + gettext('This will permanently erase all image data.'); | |
377 | } | |
378 | ||
379 | return msg; | |
380 | }, | |
381 | handler: function(b, e, rec) { | |
382 | PVE.Utils.API2Request({ | |
383 | url: '/api2/extjs/' + baseurl, | |
384 | waitMsgTarget: me, | |
385 | method: 'PUT', | |
386 | params: { | |
387 | 'delete': rec.data.key | |
388 | }, | |
389 | callback: function() { | |
390 | reload(); | |
391 | }, | |
392 | failure: function (response, opts) { | |
393 | Ext.Msg.alert('Error', response.htmlStatus); | |
394 | } | |
395 | }); | |
396 | } | |
397 | }); | |
398 | ||
399 | var revert_btn = new PVE.button.Button({ | |
400 | text: gettext('Revert'), | |
401 | selModel: sm, | |
402 | disabled: true, | |
403 | handler: function(b, e, rec) { | |
404 | var rowdef = me.rows[rec.data.key] || {}; | |
405 | var keys = rowdef.multiKey || [ rec.data.key ]; | |
406 | var revert = keys.join(','); | |
407 | PVE.Utils.API2Request({ | |
408 | url: '/api2/extjs/' + baseurl, | |
409 | waitMsgTarget: me, | |
410 | method: 'PUT', | |
411 | params: { | |
412 | 'revert': revert | |
413 | }, | |
414 | callback: function() { | |
415 | reload(); | |
416 | }, | |
417 | failure: function (response, opts) { | |
418 | Ext.Msg.alert('Error',response.htmlStatus); | |
419 | } | |
420 | }); | |
421 | } | |
422 | }); | |
423 | ||
424 | var set_button_status = function() { | |
425 | var sm = me.getSelectionModel(); | |
426 | var rec = sm.getSelection()[0]; | |
427 | ||
428 | if (!rec) { | |
429 | remove_btn.disable(); | |
430 | edit_btn.disable(); | |
431 | resize_btn.disable(); | |
432 | move_btn.disable(); | |
433 | diskthrottle_btn.disable(); | |
434 | revert_btn.disable(); | |
435 | return; | |
436 | } | |
437 | var key = rec.data.key; | |
438 | var value = rec.data.value; | |
439 | var rowdef = rows[key]; | |
440 | ||
441 | var pending = rec.data['delete'] || me.hasPendingChanges(key); | |
442 | var isDisk = !key.match(/^unused\d+/) && | |
443 | (rowdef.tdCls == 'pve-itype-icon-storage' && !value.match(/media=cdrom/)); | |
444 | ||
445 | remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true)); | |
446 | ||
447 | edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor); | |
448 | ||
449 | resize_btn.setDisabled(pending || !isDisk); | |
450 | ||
451 | move_btn.setDisabled(pending || !isDisk); | |
452 | ||
453 | diskthrottle_btn.setDisabled(pending || !isDisk); | |
454 | ||
455 | revert_btn.setDisabled(!pending); | |
456 | ||
457 | }; | |
458 | ||
459 | Ext.applyIf(me, { | |
460 | url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending', | |
461 | interval: 5000, | |
462 | selModel: sm, | |
463 | cwidth1: 170, | |
464 | tbar: [ | |
465 | { | |
466 | text: gettext('Add'), | |
467 | menu: new Ext.menu.Menu({ | |
468 | items: [ | |
469 | { | |
470 | text: gettext('Hard Disk'), | |
471 | iconCls: 'pve-itype-icon-storage', | |
472 | disabled: !caps.vms['VM.Config.Disk'], | |
473 | handler: function() { | |
474 | var win = Ext.create('PVE.qemu.HDEdit', { | |
475 | url: '/api2/extjs/' + baseurl, | |
476 | pveSelNode: me.pveSelNode | |
477 | }); | |
478 | win.on('destroy', reload); | |
479 | win.show(); | |
480 | } | |
481 | }, | |
482 | { | |
483 | text: gettext('CD/DVD Drive'), | |
484 | iconCls: 'pve-itype-icon-cdrom', | |
485 | disabled: !caps.vms['VM.Config.Disk'], | |
486 | handler: function() { | |
487 | var win = Ext.create('PVE.qemu.CDEdit', { | |
488 | url: '/api2/extjs/' + baseurl, | |
489 | pveSelNode: me.pveSelNode | |
490 | }); | |
491 | win.on('destroy', reload); | |
492 | win.show(); | |
493 | } | |
494 | }, | |
495 | { | |
496 | text: gettext('Network Device'), | |
497 | iconCls: 'pve-itype-icon-network', | |
498 | disabled: !caps.vms['VM.Config.Network'], | |
499 | handler: function() { | |
500 | var win = Ext.create('PVE.qemu.NetworkEdit', { | |
501 | url: '/api2/extjs/' + baseurl, | |
502 | pveSelNode: me.pveSelNode | |
503 | }); | |
504 | win.on('destroy', reload); | |
505 | win.show(); | |
506 | } | |
507 | } | |
508 | ] | |
509 | }) | |
510 | }, | |
511 | remove_btn, | |
512 | edit_btn, | |
513 | resize_btn, | |
514 | move_btn, | |
515 | diskthrottle_btn, | |
516 | cpuoptions_btn, | |
517 | revert_btn | |
518 | ], | |
519 | rows: rows, | |
520 | sorterFn: sorterFn, | |
521 | listeners: { | |
522 | itemdblclick: run_editor, | |
523 | selectionchange: set_button_status | |
524 | } | |
525 | }); | |
526 | ||
527 | me.callParent(); | |
528 | ||
529 | me.on('show', me.rstore.startUpdate); | |
530 | me.on('hide', me.rstore.stopUpdate); | |
531 | me.on('destroy', me.rstore.stopUpdate); | |
532 | ||
533 | me.rstore.on('datachanged', function() { | |
534 | set_button_status(); | |
535 | }); | |
536 | } | |
537 | }); |