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