]>
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 | ||
0ea3b849 TL |
17 | if (rowdef.isOnStorageBus) { |
18 | var value = me.getObjectValue(key, '', false); | |
19 | if (value === '') { | |
20 | value = me.getObjectValue(key, '', true); | |
21 | } | |
22 | if (value.match(/vm-.*-cloudinit/)) { | |
23 | iconCls = 'cloud'; | |
24 | txt = rowdef.cloudheader; | |
25 | } else if (value.match(/media=cdrom/)) { | |
26 | metaData.tdCls = 'pve-itype-icon-cdrom'; | |
27 | return rowdef.cdheader; | |
28 | } | |
29 | } | |
30 | ||
f97670f5 DM |
31 | if (rowdef.tdCls) { |
32 | metaData.tdCls = rowdef.tdCls; | |
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 DM |
62 | |
63 | var rows = { | |
64 | memory: { | |
65 | header: gettext('Memory'), | |
66 | editor: caps.vms['VM.Config.Memory'] ? 'PVE.qemu.MemoryEdit' : undefined, | |
67 | never_delete: true, | |
72408db9 | 68 | defaultValue: '512', |
f97670f5 | 69 | tdCls: 'pve-itype-icon-memory', |
cbe0b4b8 | 70 | group: 2, |
1d902252 DC |
71 | multiKey: ['memory', 'balloon', 'shares'], |
72 | renderer: function(value, metaData, record, ri, ci, store, pending) { | |
73 | var res = ''; | |
74 | ||
75 | var max = me.getObjectValue('memory', 512, pending); | |
76 | var balloon = me.getObjectValue('balloon', undefined, pending); | |
77 | var shares = me.getObjectValue('shares', undefined, pending); | |
78 | ||
79 | res = Proxmox.Utils.format_size(max*1024*1024); | |
80 | ||
81 | if (balloon !== undefined && balloon > 0) { | |
82 | res = Proxmox.Utils.format_size(balloon*1024*1024) + "/" + res; | |
83 | ||
84 | if (shares) { | |
85 | res += ' [shares=' + shares +']'; | |
86 | } | |
87 | } else if (balloon === 0) { | |
88 | res += ' [balloon=0]'; | |
89 | } | |
90 | return res; | |
f97670f5 DM |
91 | } |
92 | }, | |
93 | sockets: { | |
94 | header: gettext('Processors'), | |
95 | never_delete: true, | |
96 | editor: (caps.vms['VM.Config.CPU'] || caps.vms['VM.Config.HWType']) ? | |
97 | 'PVE.qemu.ProcessorEdit' : undefined, | |
98 | tdCls: 'pve-itype-icon-processor', | |
cbe0b4b8 | 99 | group: 3, |
72408db9 | 100 | defaultValue: '1', |
f97670f5 DM |
101 | multiKey: ['sockets', 'cpu', 'cores', 'numa', 'vcpus', 'cpulimit', 'cpuunits'], |
102 | renderer: function(value, metaData, record, rowIndex, colIndex, store, pending) { | |
103 | ||
104 | var sockets = me.getObjectValue('sockets', 1, pending); | |
105 | var model = me.getObjectValue('cpu', undefined, pending); | |
106 | var cores = me.getObjectValue('cores', 1, pending); | |
107 | var numa = me.getObjectValue('numa', undefined, pending); | |
108 | var vcpus = me.getObjectValue('vcpus', undefined, pending); | |
109 | var cpulimit = me.getObjectValue('cpulimit', undefined, pending); | |
110 | var cpuunits = me.getObjectValue('cpuunits', undefined, pending); | |
111 | ||
72408db9 EK |
112 | var res = Ext.String.format('{0} ({1} sockets, {2} cores)', |
113 | sockets*cores, sockets, cores); | |
114 | ||
f97670f5 DM |
115 | if (model) { |
116 | res += ' [' + model + ']'; | |
117 | } | |
118 | ||
119 | if (numa) { | |
120 | res += ' [numa=' + numa +']'; | |
121 | } | |
122 | ||
123 | if (vcpus) { | |
124 | res += ' [vcpus=' + vcpus +']'; | |
125 | } | |
126 | ||
127 | if (cpulimit) { | |
128 | res += ' [cpulimit=' + cpulimit +']'; | |
129 | } | |
130 | ||
131 | if (cpuunits) { | |
132 | res += ' [cpuunits=' + cpuunits +']'; | |
133 | } | |
134 | ||
135 | return res; | |
136 | } | |
137 | }, | |
439d7821 DC |
138 | bios: { |
139 | header: 'BIOS', | |
140 | group: 4, | |
141 | never_delete: true, | |
142 | editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.BiosEdit' : undefined, | |
143 | defaultValue: '', | |
144 | iconCls: 'microchip', | |
145 | renderer: PVE.Utils.render_qemu_bios | |
146 | }, | |
f97670f5 DM |
147 | vga: { |
148 | header: gettext('Display'), | |
149 | editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.DisplayEdit' : undefined, | |
150 | never_delete: true, | |
a032a91a | 151 | iconCls: 'desktop', |
439d7821 | 152 | group:5, |
f97670f5 DM |
153 | defaultValue: '', |
154 | renderer: PVE.Utils.render_kvm_vga_driver | |
155 | }, | |
aae2273c DC |
156 | machine: { |
157 | header: gettext('Machine'), | |
158 | editor: caps.vms['VM.Config.HWType'] ? { | |
159 | xtype: 'proxmoxWindowEdit', | |
160 | subject: gettext('Machine'), | |
161 | width: 350, | |
162 | items: [{ | |
163 | xtype: 'proxmoxKVComboBox', | |
164 | name: 'machine', | |
165 | value: '__default__', | |
166 | fieldLabel: gettext('Machine'), | |
167 | comboItems: [ | |
168 | ['__default__', PVE.Utils.render_qemu_machine('')], | |
169 | ['q35', 'q35'] | |
170 | ] | |
171 | }]} : undefined, | |
172 | iconCls: 'cogs', | |
173 | never_delete: true, | |
174 | group: 6, | |
175 | defaultValue: '', | |
176 | renderer: PVE.Utils.render_qemu_machine | |
177 | }, | |
439d7821 DC |
178 | scsihw: { |
179 | header: gettext('SCSI Controller'), | |
180 | iconCls: 'database', | |
181 | editor: caps.vms['VM.Config.Options'] ? 'PVE.qemu.ScsiHwEdit' : undefined, | |
182 | renderer: PVE.Utils.render_scsihw, | |
183 | group: 7, | |
184 | never_delete: true, | |
185 | defaultValue: '' | |
186 | }, | |
23dfddc6 | 187 | vmstate: { |
4b07ffdd | 188 | header: gettext('Hibernation VM State'), |
23dfddc6 | 189 | iconCls: 'download', |
59bce287 TL |
190 | del_extra_msg: gettext('The saved VM state will be permanently lost.'), |
191 | group: 100, | |
23dfddc6 | 192 | }, |
f97670f5 DM |
193 | cores: { |
194 | visible: false | |
195 | }, | |
196 | cpu: { | |
197 | visible: false | |
198 | }, | |
199 | numa: { | |
200 | visible: false | |
201 | }, | |
202 | balloon: { | |
203 | visible: false | |
204 | }, | |
205 | hotplug: { | |
206 | visible: false | |
207 | }, | |
208 | vcpus: { | |
209 | visible: false | |
210 | }, | |
211 | cpuunits: { | |
212 | visible: false | |
213 | }, | |
214 | cpulimit: { | |
215 | visible: false | |
3b37ab6d | 216 | }, |
1d902252 DC |
217 | shares: { |
218 | visible: false | |
f97670f5 | 219 | } |
f97670f5 DM |
220 | }; |
221 | ||
abe824aa DC |
222 | PVE.Utils.forEachBus(undefined, function(type, id) { |
223 | var confid = type + id; | |
f97670f5 | 224 | rows[confid] = { |
8d554444 | 225 | group: 10, |
a032a91a | 226 | iconCls: 'hdd-o', |
f97670f5 DM |
227 | editor: 'PVE.qemu.HDEdit', |
228 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
0ea3b849 | 229 | isOnStorageBus: true, |
f97670f5 | 230 | header: gettext('Hard Disk') + ' (' + confid +')', |
b38d0810 DC |
231 | cdheader: gettext('CD/DVD Drive') + ' (' + confid +')', |
232 | cloudheader: gettext('CloudInit Drive') + ' (' + confid + ')' | |
f97670f5 | 233 | }; |
abe824aa | 234 | }); |
9d855398 | 235 | for (i = 0; i < PVE.Utils.hardware_counts.net; i++) { |
72408db9 | 236 | confid = "net" + i.toString(); |
f97670f5 | 237 | rows[confid] = { |
8d554444 | 238 | group: 15, |
cbe0b4b8 | 239 | order: i, |
a032a91a | 240 | iconCls: 'exchange', |
f97670f5 DM |
241 | editor: caps.vms['VM.Config.Network'] ? 'PVE.qemu.NetworkEdit' : undefined, |
242 | never_delete: caps.vms['VM.Config.Network'] ? false : true, | |
243 | header: gettext('Network Device') + ' (' + confid +')' | |
244 | }; | |
245 | } | |
194c9b09 | 246 | rows.efidisk0 = { |
8d554444 | 247 | group: 20, |
a032a91a | 248 | iconCls: 'hdd-o', |
194c9b09 DC |
249 | editor: null, |
250 | never_delete: caps.vms['VM.Config.Disk'] ? false : true, | |
251 | header: gettext('EFI Disk') | |
252 | }; | |
9d855398 | 253 | for (i = 0; i < PVE.Utils.hardware_counts.usb; i++) { |
72408db9 | 254 | confid = "usb" + i.toString(); |
fa522e48 | 255 | rows[confid] = { |
8d554444 | 256 | group: 25, |
cbe0b4b8 | 257 | order: i, |
fa9cdaa1 | 258 | iconCls: 'usb', |
2668ba64 DC |
259 | editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.USBEdit' : undefined, |
260 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
fa522e48 DC |
261 | header: gettext('USB Device') + ' (' + confid + ')' |
262 | }; | |
263 | } | |
9d855398 | 264 | for (i = 0; i < PVE.Utils.hardware_counts.hostpci; i++) { |
72408db9 | 265 | confid = "hostpci" + i.toString(); |
ec3d8372 | 266 | rows[confid] = { |
8d554444 | 267 | group: 30, |
cbe0b4b8 | 268 | order: i, |
ec3d8372 DC |
269 | tdCls: 'pve-itype-icon-pci', |
270 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
25a1063a | 271 | editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.PCIEdit' : undefined, |
ec3d8372 DC |
272 | header: gettext('PCI Device') + ' (' + confid + ')' |
273 | }; | |
274 | } | |
9d855398 | 275 | for (i = 0; i < PVE.Utils.hardware_counts.serial; i++) { |
a488da8c DC |
276 | confid = "serial" + i.toString(); |
277 | rows[confid] = { | |
8d554444 | 278 | group: 35, |
cbe0b4b8 | 279 | order: i, |
a488da8c DC |
280 | tdCls: 'pve-itype-icon-serial', |
281 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
282 | header: gettext('Serial Port') + ' (' + confid + ')' | |
283 | }; | |
284 | } | |
171ca270 AL |
285 | rows.audio0 = { |
286 | group: 40, | |
287 | iconCls: 'volume-up', | |
288 | editor: caps.vms['VM.Config.HWType'] ? 'PVE.qemu.AudioEdit' : undefined, | |
289 | never_delete: caps.vms['VM.Config.HWType'] ? false : true, | |
290 | header: gettext('Audio Device') | |
291 | }; | |
afa19975 | 292 | for (i = 0; i < 256; i++) { |
72408db9 | 293 | rows["unused" + i.toString()] = { |
cd86ab24 | 294 | group: 99, |
cbe0b4b8 | 295 | order: i, |
0ea3b849 | 296 | iconCls: 'hdd-o', |
b2a5aa18 | 297 | del_extra_msg: gettext('This will permanently erase all data.'), |
f97670f5 | 298 | editor: caps.vms['VM.Config.Disk'] ? 'PVE.qemu.HDEdit' : undefined, |
72408db9 | 299 | header: gettext('Unused Disk') + ' ' + i.toString() |
f97670f5 DM |
300 | }; |
301 | } | |
6c1d8ead SR |
302 | rows.rng0 = { |
303 | group: 45, | |
4ba4c7c6 | 304 | tdCls: 'pve-itype-icon-die', |
6c1d8ead SR |
305 | editor: caps.nodes['Sys.Console'] ? 'PVE.qemu.RNGEdit' : undefined, |
306 | never_delete: caps.nodes['Sys.Console'] ? false : true, | |
307 | header: gettext("VirtIO RNG") | |
308 | }; | |
f97670f5 DM |
309 | |
310 | var sorterFn = function(rec1, rec2) { | |
311 | var v1 = rec1.data.key; | |
312 | var v2 = rec2.data.key; | |
313 | var g1 = rows[v1].group || 0; | |
314 | var g2 = rows[v2].group || 0; | |
cbe0b4b8 DL |
315 | var order1 = rows[v1].order || 0; |
316 | var order2 = rows[v2].order || 0; | |
317 | ||
318 | if ((g1 - g2) !== 0) { | |
319 | return g1 - g2; | |
320 | } | |
f97670f5 | 321 | |
cbe0b4b8 DL |
322 | if ((order1 - order2) !== 0) { |
323 | return order1 - order2; | |
324 | } | |
325 | ||
326 | if (v1 > v2) { | |
327 | return 1; | |
328 | } else if (v1 < v2) { | |
329 | return -1; | |
330 | } else { | |
331 | return 0; | |
332 | } | |
f97670f5 DM |
333 | }; |
334 | ||
f97670f5 DM |
335 | var baseurl = 'nodes/' + nodename + '/qemu/' + vmid + '/config'; |
336 | ||
337 | var sm = Ext.create('Ext.selection.RowModel', {}); | |
338 | ||
339 | var run_editor = function() { | |
340 | var rec = sm.getSelection()[0]; | |
341 | if (!rec) { | |
342 | return; | |
343 | } | |
344 | ||
345 | var rowdef = rows[rec.data.key]; | |
346 | if (!rowdef.editor) { | |
347 | return; | |
348 | } | |
349 | ||
350 | var editor = rowdef.editor; | |
0ea3b849 TL |
351 | if (rowdef.isOnStorageBus) { |
352 | var value = me.getObjectValue(rec.data.key, '', true); | |
ab1ee352 DC |
353 | if (value.match(/vm-.*-cloudinit/)) { |
354 | return; | |
355 | } else if (value.match(/media=cdrom/)) { | |
f97670f5 | 356 | editor = 'PVE.qemu.CDEdit'; |
10525ed7 TL |
357 | } else if (!diskCap) { |
358 | return; | |
f97670f5 DM |
359 | } |
360 | } | |
361 | ||
362 | var win; | |
363 | ||
364 | if (Ext.isString(editor)) { | |
365 | win = Ext.create(editor, { | |
366 | pveSelNode: me.pveSelNode, | |
367 | confid: rec.data.key, | |
368 | url: '/api2/extjs/' + baseurl | |
369 | }); | |
370 | } else { | |
371 | var config = Ext.apply({ | |
372 | pveSelNode: me.pveSelNode, | |
373 | confid: rec.data.key, | |
374 | url: '/api2/extjs/' + baseurl | |
375 | }, rowdef.editor); | |
376 | win = Ext.createWidget(rowdef.editor.xtype, config); | |
377 | win.load(); | |
378 | } | |
379 | ||
380 | win.show(); | |
18f57a23 | 381 | win.on('destroy', me.reload, me); |
f97670f5 DM |
382 | }; |
383 | ||
f97670f5 DM |
384 | var run_resize = function() { |
385 | var rec = sm.getSelection()[0]; | |
386 | if (!rec) { | |
387 | return; | |
388 | } | |
389 | ||
390 | var win = Ext.create('PVE.window.HDResize', { | |
391 | disk: rec.data.key, | |
392 | nodename: nodename, | |
393 | vmid: vmid | |
394 | }); | |
395 | ||
396 | win.show(); | |
397 | ||
18f57a23 | 398 | win.on('destroy', me.reload, me); |
f97670f5 DM |
399 | }; |
400 | ||
f97670f5 DM |
401 | var run_move = function() { |
402 | var rec = sm.getSelection()[0]; | |
403 | if (!rec) { | |
404 | return; | |
405 | } | |
406 | ||
407 | var win = Ext.create('PVE.window.HDMove', { | |
408 | disk: rec.data.key, | |
409 | nodename: nodename, | |
410 | vmid: vmid | |
411 | }); | |
412 | ||
413 | win.show(); | |
414 | ||
18f57a23 | 415 | win.on('destroy', me.reload, me); |
f97670f5 DM |
416 | }; |
417 | ||
5720fafa | 418 | var edit_btn = new Proxmox.button.Button({ |
f97670f5 DM |
419 | text: gettext('Edit'), |
420 | selModel: sm, | |
421 | disabled: true, | |
422 | handler: run_editor | |
423 | }); | |
424 | ||
5720fafa | 425 | var resize_btn = new Proxmox.button.Button({ |
f97670f5 DM |
426 | text: gettext('Resize disk'), |
427 | selModel: sm, | |
428 | disabled: true, | |
429 | handler: run_resize | |
430 | }); | |
431 | ||
5720fafa | 432 | var move_btn = new Proxmox.button.Button({ |
f97670f5 DM |
433 | text: gettext('Move disk'), |
434 | selModel: sm, | |
435 | disabled: true, | |
436 | handler: run_move | |
437 | }); | |
438 | ||
5720fafa | 439 | var remove_btn = new Proxmox.button.Button({ |
f97670f5 | 440 | text: gettext('Remove'), |
877fce54 EK |
441 | defaultText: gettext('Remove'), |
442 | altText: gettext('Detach'), | |
f97670f5 DM |
443 | selModel: sm, |
444 | disabled: true, | |
445 | dangerous: true, | |
3b2e557f | 446 | RESTMethod: 'PUT', |
f97670f5 | 447 | confirmMsg: function(rec) { |
ff5a8a9b EK |
448 | var warn = gettext('Are you sure you want to remove entry {0}'); |
449 | if (this.text === this.altText) { | |
450 | warn = gettext('Are you sure you want to detach entry {0}'); | |
451 | } | |
b2a5aa18 TL |
452 | var key = rec.data.key; |
453 | var entry = rows[key]; | |
ff5a8a9b | 454 | |
b2a5aa18 | 455 | var rendered = me.renderKey(key, {}, rec); |
83b4bfc3 | 456 | var msg = Ext.String.format(warn, "'" + rendered + "'"); |
ff5a8a9b | 457 | |
b2a5aa18 TL |
458 | if (entry.del_extra_msg) { |
459 | msg += '<br>' + entry.del_extra_msg; | |
f97670f5 DM |
460 | } |
461 | ||
462 | return msg; | |
463 | }, | |
464 | handler: function(b, e, rec) { | |
e7ade592 | 465 | Proxmox.Utils.API2Request({ |
f97670f5 DM |
466 | url: '/api2/extjs/' + baseurl, |
467 | waitMsgTarget: me, | |
3b2e557f | 468 | method: b.RESTMethod, |
f97670f5 DM |
469 | params: { |
470 | 'delete': rec.data.key | |
471 | }, | |
18f57a23 | 472 | callback: () => me.reload(), |
f97670f5 DM |
473 | failure: function (response, opts) { |
474 | Ext.Msg.alert('Error', response.htmlStatus); | |
3b2e557f TM |
475 | }, |
476 | success: function(response, options) { | |
477 | if (b.RESTMethod === 'POST') { | |
478 | var upid = response.result.data; | |
479 | var win = Ext.create('Proxmox.window.TaskProgress', { | |
480 | upid: upid, | |
481 | listeners: { | |
18f57a23 | 482 | destroy: () => me.reload(), |
3b2e557f TM |
483 | } |
484 | }); | |
485 | win.show(); | |
486 | } | |
f97670f5 DM |
487 | } |
488 | }); | |
877fce54 EK |
489 | }, |
490 | listeners: { | |
491 | render: function(btn) { | |
492 | // hack: calculate an optimal button width on first display | |
493 | // to prevent the whole toolbar to move when we switch | |
494 | // between the "Remove" and "Detach" labels | |
495 | var def = btn.getSize().width; | |
496 | ||
497 | btn.setText(btn.altText); | |
498 | var alt = btn.getSize().width; | |
499 | ||
500 | btn.setText(btn.defaultText); | |
501 | ||
502 | var optimal = alt > def ? alt : def; | |
503 | btn.setSize({ width: optimal }); | |
504 | } | |
f97670f5 DM |
505 | } |
506 | }); | |
507 | ||
591efb67 | 508 | var revert_btn = new PVE.button.PendingRevert({ |
3ee15859 | 509 | apiurl: '/api2/extjs/' + baseurl, |
f97670f5 DM |
510 | }); |
511 | ||
3b37ab6d DC |
512 | var efidisk_menuitem = Ext.create('Ext.menu.Item',{ |
513 | text: gettext('EFI Disk'), | |
a032a91a | 514 | iconCls: 'fa fa-fw fa-hdd-o black', |
3b37ab6d DC |
515 | disabled: !caps.vms['VM.Config.Disk'], |
516 | handler: function() { | |
c570b6a9 TL |
517 | let bios = me.rstore.getData().map.bios; |
518 | let usesEFI = bios && (bios.data.value === 'ovmf' || bios.data.pending === 'ovmf'); | |
1535df62 | 519 | |
c570b6a9 TL |
520 | var win = Ext.create('PVE.qemu.EFIDiskEdit', { |
521 | url: '/api2/extjs/' + baseurl, | |
522 | pveSelNode: me.pveSelNode, | |
523 | usesEFI: usesEFI, | |
524 | }); | |
18f57a23 | 525 | win.on('destroy', me.reload, me); |
c570b6a9 | 526 | win.show(); |
3b37ab6d DC |
527 | } |
528 | }); | |
529 | ||
67847d54 DC |
530 | let counts = {}; |
531 | let isAtLimit = (type) => (counts[type] >= PVE.Utils.hardware_counts[type]); | |
532 | ||
f97670f5 DM |
533 | var set_button_status = function() { |
534 | var sm = me.getSelectionModel(); | |
535 | var rec = sm.getSelection()[0]; | |
536 | ||
67847d54 DC |
537 | // en/disable hardwarebuttons |
538 | counts = {}; | |
b38d0810 | 539 | var hasCloudInit = false; |
2668ba64 | 540 | me.rstore.getData().items.forEach(function(item){ |
67847d54 DC |
541 | if (!hasCloudInit && ( |
542 | /vm-.*-cloudinit/.test(item.data.value) || | |
543 | /vm-.*-cloudinit/.test(item.data.pending) | |
544 | )) { | |
b38d0810 | 545 | hasCloudInit = true; |
67847d54 DC |
546 | return; |
547 | } | |
548 | ||
549 | let match = item.id.match(/^([^\d]+)\d+$/); | |
550 | let type; | |
551 | if (match && PVE.Utils.hardware_counts[match[1]] !== undefined) { | |
552 | type = match[1]; | |
553 | } else { | |
554 | return; | |
b38d0810 | 555 | } |
67847d54 DC |
556 | |
557 | counts[type] = (counts[type] || 0) + 1; | |
2668ba64 | 558 | }); |
3097170d TL |
559 | |
560 | // heuristic only for disabling some stuff, the backend has the final word. | |
561 | var noSysConsolePerm = !caps.nodes['Sys.Console']; | |
171ca270 | 562 | var noVMConfigHWTypePerm = !caps.vms['VM.Config.HWType']; |
67847d54 DC |
563 | var noVMConfigNetPerm = !caps.vms['VM.Config.Network']; |
564 | ||
3097170d | 565 | |
67847d54 DC |
566 | me.down('#addusb').setDisabled(noSysConsolePerm || isAtLimit('usb')); |
567 | me.down('#addpci').setDisabled(noSysConsolePerm || isAtLimit('hostpci')); | |
568 | me.down('#addaudio').setDisabled(noVMConfigHWTypePerm || isAtLimit('audio')); | |
569 | me.down('#addserial').setDisabled(noVMConfigHWTypePerm || isAtLimit('serial')); | |
570 | me.down('#addnet').setDisabled(noVMConfigNetPerm || isAtLimit('net')); | |
6c1d8ead | 571 | me.down('#addrng').setDisabled(noSysConsolePerm || isAtLimit('rng')); |
67847d54 | 572 | efidisk_menuitem.setDisabled(isAtLimit('efidisk')); |
3097170d | 573 | me.down('#addci').setDisabled(noSysConsolePerm || hasCloudInit); |
3b37ab6d | 574 | |
f97670f5 DM |
575 | if (!rec) { |
576 | remove_btn.disable(); | |
577 | edit_btn.disable(); | |
578 | resize_btn.disable(); | |
579 | move_btn.disable(); | |
f97670f5 DM |
580 | revert_btn.disable(); |
581 | return; | |
582 | } | |
583 | var key = rec.data.key; | |
584 | var value = rec.data.value; | |
585 | var rowdef = rows[key]; | |
586 | ||
587 | var pending = rec.data['delete'] || me.hasPendingChanges(key); | |
2e1a8291 | 588 | var isCDRom = (value && !!value.toString().match(/media=cdrom/)); |
3b2e557f | 589 | var isUnusedDisk = key.match(/^unused\d+/); |
0ea3b849 | 590 | var isUsedDisk = !isUnusedDisk && rowdef.isOnStorageBus && !isCDRom; |
f97670f5 | 591 | |
ab1ee352 | 592 | var isCloudInit = (value && value.toString().match(/vm-.*-cloudinit/)); |
b38d0810 | 593 | |
3b37ab6d DC |
594 | var isEfi = (key === 'efidisk0'); |
595 | ||
d35b5b2a | 596 | remove_btn.setDisabled(rec.data['delete'] || (rowdef.never_delete === true) || (isUnusedDisk && !diskCap)); |
ab1ee352 | 597 | remove_btn.setText((isUsedDisk && !isCloudInit) ? remove_btn.altText : remove_btn.defaultText); |
3b2e557f | 598 | remove_btn.RESTMethod = isUnusedDisk ? 'POST':'PUT'; |
f97670f5 | 599 | |
10525ed7 | 600 | edit_btn.setDisabled(rec.data['delete'] || !rowdef.editor || isCloudInit || (!isCDRom && !diskCap)); |
f97670f5 | 601 | |
d35b5b2a | 602 | resize_btn.setDisabled(pending || !isUsedDisk || !diskCap); |
f97670f5 | 603 | |
41435292 | 604 | move_btn.setDisabled(pending || !(isUsedDisk || isEfi) || !diskCap); |
f97670f5 | 605 | |
f97670f5 DM |
606 | revert_btn.setDisabled(!pending); |
607 | ||
608 | }; | |
609 | ||
f09dd6c1 | 610 | Ext.apply(me, { |
f97670f5 DM |
611 | url: '/api2/json/' + 'nodes/' + nodename + '/qemu/' + vmid + '/pending', |
612 | interval: 5000, | |
613 | selModel: sm, | |
b08ae044 | 614 | run_editor: run_editor, |
f97670f5 DM |
615 | tbar: [ |
616 | { | |
617 | text: gettext('Add'), | |
618 | menu: new Ext.menu.Menu({ | |
e08c2321 | 619 | cls: 'pve-add-hw-menu', |
f97670f5 DM |
620 | items: [ |
621 | { | |
622 | text: gettext('Hard Disk'), | |
a032a91a | 623 | iconCls: 'fa fa-fw fa-hdd-o black', |
f97670f5 DM |
624 | disabled: !caps.vms['VM.Config.Disk'], |
625 | handler: function() { | |
626 | var win = Ext.create('PVE.qemu.HDEdit', { | |
627 | url: '/api2/extjs/' + baseurl, | |
628 | pveSelNode: me.pveSelNode | |
629 | }); | |
18f57a23 | 630 | win.on('destroy', me.reload, me); |
f97670f5 DM |
631 | win.show(); |
632 | } | |
633 | }, | |
634 | { | |
635 | text: gettext('CD/DVD Drive'), | |
636 | iconCls: 'pve-itype-icon-cdrom', | |
637 | disabled: !caps.vms['VM.Config.Disk'], | |
638 | handler: function() { | |
639 | var win = Ext.create('PVE.qemu.CDEdit', { | |
640 | url: '/api2/extjs/' + baseurl, | |
641 | pveSelNode: me.pveSelNode | |
642 | }); | |
18f57a23 | 643 | win.on('destroy', me.reload, me); |
f97670f5 DM |
644 | win.show(); |
645 | } | |
646 | }, | |
647 | { | |
648 | text: gettext('Network Device'), | |
67847d54 | 649 | itemId: 'addnet', |
a032a91a | 650 | iconCls: 'fa fa-fw fa-exchange black', |
f97670f5 DM |
651 | disabled: !caps.vms['VM.Config.Network'], |
652 | handler: function() { | |
653 | var win = Ext.create('PVE.qemu.NetworkEdit', { | |
654 | url: '/api2/extjs/' + baseurl, | |
b2991833 CE |
655 | pveSelNode: me.pveSelNode, |
656 | isCreate: true | |
f97670f5 | 657 | }); |
18f57a23 | 658 | win.on('destroy', me.reload, me); |
f97670f5 DM |
659 | win.show(); |
660 | } | |
3b37ab6d | 661 | }, |
2668ba64 DC |
662 | efidisk_menuitem, |
663 | { | |
664 | text: gettext('USB Device'), | |
665 | itemId: 'addusb', | |
a032a91a | 666 | iconCls: 'fa fa-fw fa-usb black', |
2668ba64 DC |
667 | disabled: !caps.nodes['Sys.Console'], |
668 | handler: function() { | |
669 | var win = Ext.create('PVE.qemu.USBEdit', { | |
670 | url: '/api2/extjs/' + baseurl, | |
671 | pveSelNode: me.pveSelNode | |
672 | }); | |
18f57a23 | 673 | win.on('destroy', me.reload, me); |
2668ba64 DC |
674 | win.show(); |
675 | } | |
b38d0810 | 676 | }, |
25a1063a DC |
677 | { |
678 | text: gettext('PCI Device'), | |
679 | itemId: 'addpci', | |
680 | iconCls: 'pve-itype-icon-pci', | |
681 | disabled: !caps.nodes['Sys.Console'], | |
682 | handler: function() { | |
683 | var win = Ext.create('PVE.qemu.PCIEdit', { | |
684 | url: '/api2/extjs/' + baseurl, | |
685 | pveSelNode: me.pveSelNode | |
686 | }); | |
18f57a23 | 687 | win.on('destroy', me.reload, me); |
25a1063a DC |
688 | win.show(); |
689 | } | |
690 | }, | |
37977fa5 TL |
691 | { |
692 | text: gettext('Serial Port'), | |
693 | itemId: 'addserial', | |
694 | iconCls: 'pve-itype-icon-serial', | |
695 | disabled: !caps.vms['VM.Config.Options'], | |
696 | handler: function() { | |
697 | var win = Ext.create('PVE.qemu.SerialEdit', { | |
698 | url: '/api2/extjs/' + baseurl | |
699 | }); | |
18f57a23 | 700 | win.on('destroy', me.reload, me); |
37977fa5 TL |
701 | win.show(); |
702 | } | |
703 | }, | |
b38d0810 DC |
704 | { |
705 | text: gettext('CloudInit Drive'), | |
706 | itemId: 'addci', | |
a032a91a | 707 | iconCls: 'fa fa-fw fa-cloud black', |
b38d0810 DC |
708 | disabled: !caps.nodes['Sys.Console'], |
709 | handler: function() { | |
710 | var win = Ext.create('PVE.qemu.CIDriveEdit', { | |
711 | url: '/api2/extjs/' + baseurl, | |
712 | pveSelNode: me.pveSelNode | |
713 | }); | |
18f57a23 | 714 | win.on('destroy', me.reload, me); |
b38d0810 DC |
715 | win.show(); |
716 | } | |
171ca270 AL |
717 | }, |
718 | { | |
719 | text: gettext('Audio Device'), | |
720 | itemId: 'addaudio', | |
a032a91a | 721 | iconCls: 'fa fa-fw fa-volume-up black', |
171ca270 AL |
722 | disabled: !caps.vms['VM.Config.HWType'], |
723 | handler: function() { | |
724 | var win = Ext.create('PVE.qemu.AudioEdit', { | |
725 | url: '/api2/extjs/' + baseurl, | |
726 | isCreate: true, | |
727 | isAdd: true | |
728 | }); | |
18f57a23 | 729 | win.on('destroy', me.reload, me); |
171ca270 AL |
730 | win.show(); |
731 | } | |
6c1d8ead SR |
732 | }, |
733 | { | |
734 | text: gettext("VirtIO RNG"), | |
735 | itemId: 'addrng', | |
4ba4c7c6 | 736 | iconCls: 'pve-itype-icon-die', |
6c1d8ead SR |
737 | disabled: !caps.nodes['Sys.Console'], |
738 | handler: function() { | |
739 | var win = Ext.create('PVE.qemu.RNGEdit', { | |
740 | url: '/api2/extjs/' + baseurl, | |
741 | isCreate: true, | |
742 | isAdd: true | |
743 | }); | |
744 | win.on('destroy', me.reload, me); | |
745 | win.show(); | |
746 | } | |
2668ba64 | 747 | } |
f97670f5 DM |
748 | ] |
749 | }) | |
fa522e48 | 750 | }, |
f97670f5 DM |
751 | remove_btn, |
752 | edit_btn, | |
753 | resize_btn, | |
754 | move_btn, | |
f97670f5 DM |
755 | revert_btn |
756 | ], | |
757 | rows: rows, | |
758 | sorterFn: sorterFn, | |
759 | listeners: { | |
760 | itemdblclick: run_editor, | |
761 | selectionchange: set_button_status | |
762 | } | |
763 | }); | |
764 | ||
765 | me.callParent(); | |
766 | ||
6386068d TL |
767 | me.on('activate', me.rstore.startUpdate, me.rstore); |
768 | me.on('destroy', me.rstore.stopUpdate, me.rstore); | |
f97670f5 | 769 | |
6386068d | 770 | me.mon(me.getStore(), 'datachanged', set_button_status, me); |
f97670f5 DM |
771 | } |
772 | }); |