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