]>
Commit | Line | Data |
---|---|---|
f97670f5 | 1 | Ext.define('PVE.qemu.HardwareView', { |
bc0eaf58 | 2 | extend: 'Proxmox.grid.PendingObjectGrid', |
f97670f5 DM |
3 | alias: ['widget.PVE.qemu.HardwareView'], |
4 | ||
ba93a9c6 DC |
5 | onlineHelp: 'qm_virtual_machines_settings', |
6 | ||
f97670f5 DM |
7 | renderKey: function(key, metaData, rec, rowIndex, colIndex, store) { |
8 | var me = this; | |
9 | var rows = me.rows; | |
10 | var rowdef = rows[key] || {}; | |
6a040799 DC |
11 | var iconCls = rowdef.iconCls; |
12 | var icon = ''; | |
13 | var txt = (rowdef.header || key); | |
f97670f5 DM |
14 | |
15 | metaData.tdAttr = "valign=middle"; | |
16 | ||
17 | if (rowdef.tdCls) { | |
18 | metaData.tdCls = rowdef.tdCls; | |
19 | if (rowdef.tdCls == 'pve-itype-icon-storage') { | |
cdf5bec2 DL |
20 | var value = me.getObjectValue(key, '', false); |
21 | if (value === '') { | |
22 | value = me.getObjectValue(key, '', true); | |
23 | } | |
b38d0810 | 24 | if (value.match(/vm-.*-cloudinit/)) { |
7b7ca5e1 TL |
25 | icon = "<i class='pve-grid-fa fa fa-fw fa-cloud'></i>"; |
26 | metaData.tdCls = " pve-itype-fa"; | |
27 | txt = rowdef.cloudheader; | |
b38d0810 | 28 | } else if (value.match(/media=cdrom/)) { |
f97670f5 DM |
29 | metaData.tdCls = 'pve-itype-icon-cdrom'; |
30 | return rowdef.cdheader; | |
31 | } | |
32 | } | |
6a040799 DC |
33 | } else if (iconCls) { |
34 | icon = "<i class='pve-grid-fa fa fa-fw fa-" + iconCls + "'></i>"; | |
35 | metaData.tdCls += " pve-itype-fa"; | |
f97670f5 | 36 | } |
03558174 TL |
37 | |
38 | // only return icons in grid but not remove dialog | |
39 | if (rowIndex !== undefined) { | |
40 | return icon + txt; | |
41 | } else { | |
42 | return txt; | |
43 | } | |
f97670f5 DM |
44 | }, |
45 | ||
46 | initComponent : function() { | |
47 | var me = this; | |
48 | var i, confid; | |
49 | ||
50 | var nodename = me.pveSelNode.data.node; | |
51 | if (!nodename) { | |
52 | throw "no node name specified"; | |
53 | } | |
54 | ||
55 | var vmid = me.pveSelNode.data.vmid; | |
56 | if (!vmid) { | |
57 | throw "no VM ID specified"; | |
58 | } | |
59 | ||
60 | var caps = Ext.state.Manager.get('GuiCap'); | |
d35b5b2a | 61 | var diskCap = caps.vms['VM.Config.Disk']; |
f97670f5 | 62 | |
aae2273c | 63 | /*jslint confusion: true */ |
f97670f5 DM |
64 | var rows = { |
65 | memory: { | |
66 | header: gettext('Memory'), | |
67 | editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined, | |
68 | never_delete: true, | |
72408db9 | 69 | defaultValue: '512', |
f97670f5 | 70 | tdCls: 'pve-itype-icon-memory', |
cbe0b4b8 | 71 | group: 2, |
1d902252 DC |
72 | multiKey: ['memory', 'balloon', 'shares'], |
73 | renderer: function(value, metaData, record, ri, ci, store, pending) { | |
74 | var res = ''; | |
75 | ||
76 | var max = me.getObjectValue('memory', 512, pending); | |
77 | var balloon = me.getObjectValue('balloon', undefined, pending); | |
78 | var shares = me.getObjectValue('shares', undefined, pending); | |
79 | ||
80 | res = Proxmox.Utils.format_size(max*1024*1024); | |
81 | ||
82 | if (balloon !== undefined && balloon > 0) { | |
83 | res = Proxmox.Utils.format_size(balloon*1024*1024) + "/" + res; | |
84 | ||
85 | if (shares) { | |
86 | res += ' [shares=' + shares +']'; | |
87 | } | |
88 | } else if (balloon === 0) { | |
89 | res += ' [balloon=0]'; | |
90 | } | |
91 | return res; | |
f97670f5 DM |
92 | } |
93 | }, | |
94 | sockets: { | |
95 | header: gettext('Processors'), | |
96 | never_delete: true, | |
97 | editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ? | |
98 | 'PVE.qemu.ProcessorEdit' : undefined, | |
99 | tdCls: 'pve-itype-icon-processor', | |
cbe0b4b8 | 100 | group: 3, |
72408db9 | 101 | defaultValue: '1', |
f97670f5 DM |
102 | multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'], |
103 | renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) { | |
104 | ||
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); | |
112 | ||
72408db9 EK |
113 | var res = Ext.String.format('{0} ({1} sockets, {2} cores)', |
114 | sockets*cores, sockets, cores); | |
115 | ||
f97670f5 DM |
116 | if (model) { |
117 | res += ' [' + model + ']'; | |
118 | } | |
119 | ||
120 | if (numa) { | |
121 | res += ' [numa=' + numa +']'; | |
122 | } | |
123 | ||
124 | if (vcpus) { | |
125 | res += ' [vcpus=' + vcpus +']'; | |
126 | } | |
127 | ||
128 | if (cpulimit) { | |
129 | res += ' [cpulimit=' + cpulimit +']'; | |
130 | } | |
131 | ||
132 | if (cpuunits) { | |
133 | res += ' [cpuunits=' + cpuunits +']'; | |
134 | } | |
135 | ||
136 | return res; | |
137 | } | |
138 | }, | |
439d7821 DC |
139 | bios: { |
140 | header: 'BIOS', | |
141 | group: 4, | |
142 | never_delete: true, | |
143 | editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined, | |
144 | defaultValue: '', | |
145 | iconCls: 'microchip', | |
146 | renderer: PVE.Utils.render_qemu_bios | |
147 | }, | |
f97670f5 DM |
148 | vga: { |
149 | header: gettext('Display'), | |
150 | editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined, | |
151 | never_delete: true, | |
152 | tdCls: 'pve-itype-icon-display', | |
439d7821 | 153 | group:5, |
f97670f5 DM |
154 | defaultValue: '', |
155 | renderer: PVE.Utils.render_kvm_vga_driver | |
156 | }, | |
aae2273c DC |
157 | machine: { |
158 | header: gettext('Machine'), | |
159 | editor: caps.vms['VM.Config.HWType'] ? { | |
160 | xtype: 'proxmoxWindowEdit', | |
161 | subject: gettext('Machine'), | |
162 | width: 350, | |
163 | items: [{ | |
164 | xtype: 'proxmoxKVComboBox', | |
165 | name: 'machine', | |
166 | value: '__default__', | |
167 | fieldLabel: gettext('Machine'), | |
168 | comboItems: [ | |
169 | ['__default__', PVE.Utils.render_qemu_machine('')], | |
170 | ['q35', 'q35'] | |
171 | ] | |
172 | }]} : undefined, | |
173 | iconCls: 'cogs', | |
174 | never_delete: true, | |
175 | group: 6, | |
176 | defaultValue: '', | |
177 | renderer: PVE.Utils.render_qemu_machine | |
178 | }, | |
439d7821 DC |
179 | scsihw: { |
180 | header: gettext('SCSI Controller'), | |
181 | iconCls: 'database', | |
182 | editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined, | |
183 | renderer: PVE.Utils.render_scsihw, | |
184 | group: 7, | |
185 | never_delete: true, | |
186 | defaultValue: '' | |
187 | }, | |
f97670f5 DM |
188 | cores: { |
189 | visible: false | |
190 | }, | |
191 | cpu: { | |
192 | visible: false | |
193 | }, | |
194 | numa: { | |
195 | visible: false | |
196 | }, | |
197 | balloon: { | |
198 | visible: false | |
199 | }, | |
200 | hotplug: { | |
201 | visible: false | |
202 | }, | |
203 | vcpus: { | |
204 | visible: false | |
205 | }, | |
206 | cpuunits: { | |
207 | visible: false | |
208 | }, | |
209 | cpulimit: { | |
210 | visible: false | |
3b37ab6d | 211 | }, |
1d902252 DC |
212 | shares: { |
213 | visible: false | |
f97670f5 | 214 | } |
f97670f5 | 215 | }; |
aae2273c | 216 | /*jslint confusion: false */ |
f97670f5 | 217 | |
abe824aa DC |
218 | PVE.Utils.forEachBus(undefined, function(type, id) { |
219 | var confid = type + id; | |
f97670f5 | 220 | rows[confid] = { |
8d554444 | 221 | group: 10, |
f97670f5 DM |
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 +')', | |
b38d0810 DC |
226 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')', |
227 | cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')' | |
f97670f5 | 228 | }; |
abe824aa | 229 | }); |
f97670f5 | 230 | for (i = 0; i < 32; i++) { |
72408db9 | 231 | confid = "net" + i.toString(); |
f97670f5 | 232 | rows[confid] = { |
8d554444 | 233 | group: 15, |
cbe0b4b8 | 234 | order: i, |
f97670f5 DM |
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 +')' | |
239 | }; | |
240 | } | |
194c9b09 | 241 | rows.efidisk0 = { |
8d554444 | 242 | group: 20, |
194c9b09 DC |
243 | tdCls: 'pve-itype-icon-storage', |
244 | editor: null, | |
245 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
246 | header: gettext('EFI Disk') | |
247 | }; | |
fa522e48 | 248 | for (i = 0; i < 5; i++) { |
72408db9 | 249 | confid = "usb" + i.toString(); |
fa522e48 | 250 | rows[confid] = { |
8d554444 | 251 | group: 25, |
cbe0b4b8 | 252 | order: i, |
fa522e48 | 253 | tdCls: 'pve-itype-icon-usb', |
2668ba64 DC |
254 | editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined, |
255 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
fa522e48 DC |
256 | header: gettext('USB Device') + ' (' + confid + ')' |
257 | }; | |
258 | } | |
ec3d8372 | 259 | for (i = 0; i < 4; i++) { |
72408db9 | 260 | confid = "hostpci" + i.toString(); |
ec3d8372 | 261 | rows[confid] = { |
8d554444 | 262 | group: 30, |
cbe0b4b8 | 263 | order: i, |
ec3d8372 DC |
264 | tdCls: 'pve-itype-icon-pci', |
265 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
25a1063a | 266 | editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined, |
ec3d8372 DC |
267 | header: gettext('PCI Device') + ' (' + confid + ')' |
268 | }; | |
269 | } | |
a488da8c DC |
270 | for (i = 0; i < 4; i++) { |
271 | confid = "serial" + i.toString(); | |
272 | rows[confid] = { | |
8d554444 | 273 | group: 35, |
cbe0b4b8 | 274 | order: i, |
a488da8c DC |
275 | tdCls: 'pve-itype-icon-serial', |
276 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
277 | header: gettext('Serial Port') + ' (' + confid + ')' | |
278 | }; | |
279 | } | |
171ca270 AL |
280 | rows.audio0 = { |
281 | group: 40, | |
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') | |
286 | }; | |
afa19975 | 287 | for (i = 0; i < 256; i++) { |
72408db9 | 288 | rows["unused" + i.toString()] = { |
cd86ab24 | 289 | group: 99, |
cbe0b4b8 | 290 | order: i, |
f97670f5 DM |
291 | tdCls: 'pve-itype-icon-storage', |
292 | editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined, | |
72408db9 | 293 | header: gettext('Unused Disk') + ' ' + i.toString() |
f97670f5 DM |
294 | }; |
295 | } | |
296 | ||
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; | |
cbe0b4b8 DL |
302 | var order1 = rows[v1].order || 0; |
303 | var order2 = rows[v2].order || 0; | |
304 | ||
305 | if ((g1 - g2) !== 0) { | |
306 | return g1 - g2; | |
307 | } | |
f97670f5 | 308 | |
cbe0b4b8 DL |
309 | if ((order1 - order2) !== 0) { |
310 | return order1 - order2; | |
311 | } | |
312 | ||
313 | if (v1 > v2) { | |
314 | return 1; | |
315 | } else if (v1 < v2) { | |
316 | return -1; | |
317 | } else { | |
318 | return 0; | |
319 | } | |
f97670f5 DM |
320 | }; |
321 | ||
322 | var reload = function() { | |
323 | me.rstore.load(); | |
324 | }; | |
325 | ||
326 | var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config'; | |
327 | ||
328 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
329 | ||
330 | var run_editor = function() { | |
331 | var rec = sm.getSelection()[0]; | |
332 | if (!rec) { | |
333 | return; | |
334 | } | |
335 | ||
336 | var rowdef = rows[rec.data.key]; | |
337 | if (!rowdef.editor) { | |
338 | return; | |
339 | } | |
340 | ||
341 | var editor = rowdef.editor; | |
342 | if (rowdef.tdCls == 'pve-itype-icon-storage') { | |
343 | var value = me.getObjectValue(rec.data.key, '', true); | |
ab1ee352 DC |
344 | if (value.match(/vm-.*-cloudinit/)) { |
345 | return; | |
346 | } else if (value.match(/media=cdrom/)) { | |
f97670f5 | 347 | editor = 'PVE.qemu.CDEdit'; |
10525ed7 TL |
348 | } else if (!diskCap) { |
349 | return; | |
f97670f5 DM |
350 | } |
351 | } | |
352 | ||
353 | var win; | |
354 | ||
355 | if (Ext.isString(editor)) { | |
356 | win = Ext.create(editor, { | |
357 | pveSelNode: me.pveSelNode, | |
358 | confid: rec.data.key, | |
359 | url: '/api2/extjs/' + baseurl | |
360 | }); | |
361 | } else { | |
362 | var config = Ext.apply({ | |
363 | pveSelNode: me.pveSelNode, | |
364 | confid: rec.data.key, | |
365 | url: '/api2/extjs/' + baseurl | |
366 | }, rowdef.editor); | |
367 | win = Ext.createWidget(rowdef.editor.xtype, config); | |
368 | win.load(); | |
369 | } | |
370 | ||
371 | win.show(); | |
372 | win.on('destroy', reload); | |
373 | }; | |
374 | ||
f97670f5 DM |
375 | var run_resize = function() { |
376 | var rec = sm.getSelection()[0]; | |
377 | if (!rec) { | |
378 | return; | |
379 | } | |
380 | ||
381 | var win = Ext.create('PVE.window.HDResize', { | |
382 | disk: rec.data.key, | |
383 | nodename: nodename, | |
384 | vmid: vmid | |
385 | }); | |
386 | ||
387 | win.show(); | |
388 | ||
389 | win.on('destroy', reload); | |
390 | }; | |
391 | ||
f97670f5 DM |
392 | var run_move = function() { |
393 | var rec = sm.getSelection()[0]; | |
394 | if (!rec) { | |
395 | return; | |
396 | } | |
397 | ||
398 | var win = Ext.create('PVE.window.HDMove', { | |
399 | disk: rec.data.key, | |
400 | nodename: nodename, | |
401 | vmid: vmid | |
402 | }); | |
403 | ||
404 | win.show(); | |
405 | ||
406 | win.on('destroy', reload); | |
407 | }; | |
408 | ||
5720fafa | 409 | var edit_btn = new Proxmox.button.Button({ |
f97670f5 DM |
410 | text: gettext('Edit'), |
411 | selModel: sm, | |
412 | disabled: true, | |
413 | handler: run_editor | |
414 | }); | |
415 | ||
5720fafa | 416 | var resize_btn = new Proxmox.button.Button({ |
f97670f5 DM |
417 | text: gettext('Resize disk'), |
418 | selModel: sm, | |
419 | disabled: true, | |
420 | handler: run_resize | |
421 | }); | |
422 | ||
5720fafa | 423 | var move_btn = new Proxmox.button.Button({ |
f97670f5 DM |
424 | text: gettext('Move disk'), |
425 | selModel: sm, | |
426 | disabled: true, | |
427 | handler: run_move | |
428 | }); | |
429 | ||
5720fafa | 430 | var remove_btn = new Proxmox.button.Button({ |
f97670f5 | 431 | text: gettext('Remove'), |
877fce54 EK |
432 | defaultText: gettext('Remove'), |
433 | altText: gettext('Detach'), | |
f97670f5 DM |
434 | selModel: sm, |
435 | disabled: true, | |
436 | dangerous: true, | |
3b2e557f | 437 | RESTMethod: 'PUT', |
f97670f5 | 438 | confirmMsg: function(rec) { |
ff5a8a9b EK |
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}'); | |
442 | } | |
443 | ||
444 | var entry = rec.data.key; | |
83b4bfc3 TL |
445 | var rendered = me.renderKey(entry, {}, rec); |
446 | var msg = Ext.String.format(warn, "'" + rendered + "'"); | |
ff5a8a9b EK |
447 | |
448 | if (entry.match(/^unused\d+$/)) { | |
16152937 | 449 | msg += " " + gettext('This will permanently erase all data.'); |
f97670f5 DM |
450 | } |
451 | ||
452 | return msg; | |
453 | }, | |
454 | handler: function(b, e, rec) { | |
e7ade592 | 455 | Proxmox.Utils.API2Request({ |
f97670f5 DM |
456 | url: '/api2/extjs/' + baseurl, |
457 | waitMsgTarget: me, | |
3b2e557f | 458 | method: b.RESTMethod, |
f97670f5 DM |
459 | params: { |
460 | 'delete': rec.data.key | |
461 | }, | |
462 | callback: function() { | |
463 | reload(); | |
464 | }, | |
465 | failure: function (response, opts) { | |
466 | Ext.Msg.alert('Error', response.htmlStatus); | |
3b2e557f TM |
467 | }, |
468 | success: function(response, options) { | |
469 | if (b.RESTMethod === 'POST') { | |
470 | var upid = response.result.data; | |
471 | var win = Ext.create('Proxmox.window.TaskProgress', { | |
472 | upid: upid, | |
473 | listeners: { | |
474 | destroy: function () { | |
475 | me.reload(); | |
476 | } | |
477 | } | |
478 | }); | |
479 | win.show(); | |
480 | } | |
f97670f5 DM |
481 | } |
482 | }); | |
877fce54 EK |
483 | }, |
484 | listeners: { | |
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; | |
490 | ||
491 | btn.setText(btn.altText); | |
492 | var alt = btn.getSize().width; | |
493 | ||
494 | btn.setText(btn.defaultText); | |
495 | ||
496 | var optimal = alt > def ? alt : def; | |
497 | btn.setSize({ width: optimal }); | |
498 | } | |
f97670f5 DM |
499 | } |
500 | }); | |
501 | ||
5720fafa | 502 | var revert_btn = new Proxmox.button.Button({ |
f97670f5 DM |
503 | text: gettext('Revert'), |
504 | selModel: sm, | |
505 | disabled: true, | |
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(','); | |
e7ade592 | 510 | Proxmox.Utils.API2Request({ |
f97670f5 DM |
511 | url: '/api2/extjs/' + baseurl, |
512 | waitMsgTarget: me, | |
513 | method: 'PUT', | |
514 | params: { | |
515 | 'revert': revert | |
516 | }, | |
517 | callback: function() { | |
518 | reload(); | |
519 | }, | |
520 | failure: function (response, opts) { | |
521 | Ext.Msg.alert('Error',response.htmlStatus); | |
522 | } | |
523 | }); | |
524 | } | |
525 | }); | |
526 | ||
3b37ab6d DC |
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() { | |
1535df62 DC |
532 | |
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 | |
539 | }); | |
540 | win.on('destroy', reload); | |
541 | win.show(); | |
542 | } else { | |
543 | Ext.Msg.alert('Error',gettext('Please select OVMF(UEFI) as BIOS first.')); | |
544 | } | |
545 | ||
3b37ab6d DC |
546 | } |
547 | }); | |
548 | ||
f97670f5 DM |
549 | var set_button_status = function() { |
550 | var sm = me.getSelectionModel(); | |
551 | var rec = sm.getSelection()[0]; | |
552 | ||
1535df62 DC |
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); | |
2668ba64 | 557 | // en/disable usb add button |
25a1063a DC |
558 | var usbcount = 0; |
559 | var pcicount = 0; | |
171ca270 | 560 | var audiocount = 0; |
b38d0810 | 561 | var hasCloudInit = false; |
2668ba64 DC |
562 | me.rstore.getData().items.forEach(function(item){ |
563 | if (/^usb\d+/.test(item.id)) { | |
25a1063a DC |
564 | usbcount++; |
565 | } else if (/^hostpci\d+/.test(item.id)) { | |
566 | pcicount++; | |
171ca270 AL |
567 | } else if (/^audio\d+/.test(item.id)) { |
568 | audiocount++; | |
2668ba64 | 569 | } |
b38d0810 DC |
570 | if (!hasCloudInit && /vm-.*-cloudinit/.test(item.data.value)) { |
571 | hasCloudInit = true; | |
572 | } | |
2668ba64 | 573 | }); |
3097170d TL |
574 | |
575 | // heuristic only for disabling some stuff, the backend has the final word. | |
576 | var noSysConsolePerm = !caps.nodes['Sys.Console']; | |
171ca270 | 577 | var noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType']; |
3097170d | 578 | |
25a1063a DC |
579 | me.down('#addusb').setDisabled(noSysConsolePerm || (usbcount >= 5)); |
580 | me.down('#addpci').setDisabled(noSysConsolePerm || (pcicount >= 4)); | |
171ca270 | 581 | me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || (audiocount >= 1)); |
3097170d | 582 | me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit); |
3b37ab6d | 583 | |
f97670f5 DM |
584 | if (!rec) { |
585 | remove_btn.disable(); | |
586 | edit_btn.disable(); | |
587 | resize_btn.disable(); | |
588 | move_btn.disable(); | |
f97670f5 DM |
589 | revert_btn.disable(); |
590 | return; | |
591 | } | |
592 | var key = rec.data.key; | |
593 | var value = rec.data.value; | |
594 | var rowdef = rows[key]; | |
595 | ||
596 | var pending = rec.data['delete'] || me.hasPendingChanges(key); | |
2e1a8291 | 597 | var isCDRom = (value && !!value.toString().match(/media=cdrom/)); |
3b2e557f TM |
598 | var isUnusedDisk = key.match(/^unused\d+/); |
599 | var isUsedDisk = !isUnusedDisk && | |
25e0bc2d | 600 | rowdef.tdCls == 'pve-itype-icon-storage' && |
10525ed7 | 601 | !isCDRom; |
f97670f5 | 602 | |
ab1ee352 | 603 | var isCloudInit = (value && value.toString().match(/vm-.*-cloudinit/)); |
b38d0810 | 604 | |
3b37ab6d DC |
605 | var isEfi = (key === 'efidisk0'); |
606 | ||
d35b5b2a | 607 | remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true) || (isUnusedDisk && !diskCap)); |
ab1ee352 | 608 | remove_btn.setText((isUsedDisk && !isCloudInit) ? remove_btn.altText : remove_btn.defaultText); |
3b2e557f | 609 | remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT'; |
f97670f5 | 610 | |
10525ed7 | 611 | edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor || isCloudInit || (!isCDRom && !diskCap)); |
f97670f5 | 612 | |
d35b5b2a | 613 | resize_btn.setDisabled(pending || !isUsedDisk || !diskCap); |
f97670f5 | 614 | |
d35b5b2a | 615 | move_btn.setDisabled(pending || !isUsedDisk || !diskCap); |
f97670f5 | 616 | |
f97670f5 DM |
617 | revert_btn.setDisabled(!pending); |
618 | ||
619 | }; | |
620 | ||
f09dd6c1 | 621 | Ext.apply(me, { |
f97670f5 DM |
622 | url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending', |
623 | interval: 5000, | |
624 | selModel: sm, | |
b08ae044 | 625 | run_editor: run_editor, |
f97670f5 DM |
626 | tbar: [ |
627 | { | |
628 | text: gettext('Add'), | |
629 | menu: new Ext.menu.Menu({ | |
630 | items: [ | |
631 | { | |
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 | |
639 | }); | |
640 | win.on('destroy', reload); | |
641 | win.show(); | |
642 | } | |
643 | }, | |
644 | { | |
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 | |
652 | }); | |
653 | win.on('destroy', reload); | |
654 | win.show(); | |
655 | } | |
656 | }, | |
657 | { | |
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, | |
b2991833 CE |
664 | pveSelNode: me.pveSelNode, |
665 | isCreate: true | |
f97670f5 DM |
666 | }); |
667 | win.on('destroy', reload); | |
668 | win.show(); | |
669 | } | |
3b37ab6d | 670 | }, |
2668ba64 DC |
671 | efidisk_menuitem, |
672 | { | |
673 | text: gettext('USB Device'), | |
674 | itemId: 'addusb', | |
675 | iconCls: 'pve-itype-icon-usb', | |
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 | |
681 | }); | |
682 | win.on('destroy', reload); | |
683 | win.show(); | |
684 | } | |
b38d0810 | 685 | }, |
25a1063a DC |
686 | { |
687 | text: gettext('PCI Device'), | |
688 | itemId: 'addpci', | |
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 | |
695 | }); | |
696 | win.on('destroy', reload); | |
697 | win.show(); | |
698 | } | |
699 | }, | |
37977fa5 TL |
700 | { |
701 | text: gettext('Serial Port'), | |
702 | itemId: 'addserial', | |
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 | |
708 | }); | |
709 | win.on('destroy', reload); | |
710 | win.show(); | |
711 | } | |
712 | }, | |
b38d0810 DC |
713 | { |
714 | text: gettext('CloudInit Drive'), | |
715 | itemId: 'addci', | |
716 | iconCls: 'pve-itype-icon-cloud', | |
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 | |
722 | }); | |
723 | win.on('destroy', reload); | |
724 | win.show(); | |
725 | } | |
171ca270 AL |
726 | }, |
727 | { | |
728 | text: gettext('Audio Device'), | |
729 | itemId: 'addaudio', | |
730 | iconCls: 'fa fa-volume-up', | |
731 | disabled: !caps.vms['VM.Config.HWType'], | |
732 | handler: function() { | |
733 | var win = Ext.create('PVE.qemu.AudioEdit', { | |
734 | url: '/api2/extjs/' + baseurl, | |
735 | isCreate: true, | |
736 | isAdd: true | |
737 | }); | |
738 | win.on('destroy', reload); | |
739 | win.show(); | |
740 | } | |
2668ba64 | 741 | } |
f97670f5 DM |
742 | ] |
743 | }) | |
fa522e48 | 744 | }, |
f97670f5 DM |
745 | remove_btn, |
746 | edit_btn, | |
747 | resize_btn, | |
748 | move_btn, | |
f97670f5 DM |
749 | revert_btn |
750 | ], | |
751 | rows: rows, | |
752 | sorterFn: sorterFn, | |
753 | listeners: { | |
754 | itemdblclick: run_editor, | |
755 | selectionchange: set_button_status | |
756 | } | |
757 | }); | |
758 | ||
759 | me.callParent(); | |
760 | ||
f09dd6c1 | 761 | me.on('activate', me.rstore.startUpdate); |
f97670f5 DM |
762 | me.on('destroy', me.rstore.stopUpdate); |
763 | ||
f09dd6c1 | 764 | me.mon(me.rstore, 'refresh', function() { |
f97670f5 DM |
765 | set_button_status(); |
766 | }); | |
767 | } | |
768 | }); |