]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/qemu/HardwareView.js
ui: tfa/totp: fix setting issuer in secret URL
[pve-manager.git] / www / manager6 / qemu / HardwareView.js
CommitLineData
f97670f5 1Ext.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});