]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/qemu/BootOrderEdit.js
ui: boot order: handle cloudinit correctly
[pve-manager.git] / www / manager6 / qemu / BootOrderEdit.js
1 Ext.define('pve-boot-order-entry', {
2 extend: 'Ext.data.Model',
3 fields: [
4 { name: 'name', type: 'string' },
5 { name: 'enabled', type: 'bool' },
6 { name: 'desc', type: 'string' },
7 ],
8 });
9
10 Ext.define('PVE.qemu.BootOrderPanel', {
11 extend: 'Proxmox.panel.InputPanel',
12 alias: 'widget.pveQemuBootOrderPanel',
13
14 vmconfig: {}, // store loaded vm config
15 store: undefined,
16
17 inUpdate: false,
18 controller: {
19 xclass: 'Ext.app.ViewController',
20 },
21
22 isCloudinit: (v) => v.match(/media=cdrom/) && v.match(/[:/]vm-\d+-cloudinit/),
23
24 isDisk: function(value) {
25 return PVE.Utils.bus_match.test(value);
26 },
27
28 isBootdev: function(dev, value) {
29 return (this.isDisk(dev) && !this.isCloudinit(value)) ||
30 (/^net\d+/).test(dev) ||
31 (/^hostpci\d+/).test(dev) ||
32 ((/^usb\d+/).test(dev) && !(/spice/).test(value));
33 },
34
35 setVMConfig: function(vmconfig) {
36 let me = this;
37 me.vmconfig = vmconfig;
38
39 me.store.removeAll();
40
41 let boot = PVE.Parser.parsePropertyString(me.vmconfig.boot, "legacy");
42
43 let bootorder = [];
44 if (boot.order) {
45 bootorder = boot.order.split(';').map(dev => ({ name: dev, enabled: true }));
46 } else if (!(/^\s*$/).test(me.vmconfig.boot)) {
47 // legacy style, transform to new bootorder
48 let order = boot.legacy || 'cdn';
49 let bootdisk = me.vmconfig.bootdisk || undefined;
50
51 // get the first 4 characters (acdn)
52 // ignore the rest (there should never be more than 4)
53 let orderList = order.split('').slice(0, 4);
54
55 // build bootdev list
56 for (let i = 0; i < orderList.length; i++) {
57 let list = [];
58 if (orderList[i] === 'c') {
59 if (bootdisk !== undefined && me.vmconfig[bootdisk]) {
60 list.push(bootdisk);
61 }
62 } else if (orderList[i] === 'd') {
63 Ext.Object.each(me.vmconfig, function(key, value) {
64 if (me.isDisk(key) && value.match(/media=cdrom/) && !me.isCloudinit(value)) {
65 list.push(key);
66 }
67 });
68 } else if (orderList[i] === 'n') {
69 Ext.Object.each(me.vmconfig, function(key, value) {
70 if ((/^net\d+/).test(key)) {
71 list.push(key);
72 }
73 });
74 }
75
76 // Object.each iterates in random order, sort alphabetically
77 list.sort();
78 list.forEach(dev => bootorder.push({ name: dev, enabled: true }));
79 }
80 }
81
82 // add disabled devices as well
83 let disabled = [];
84 Ext.Object.each(me.vmconfig, function(key, value) {
85 if (me.isBootdev(key, value) &&
86 !Ext.Array.some(bootorder, x => x.name === key)) {
87 disabled.push(key);
88 }
89 });
90 disabled.sort();
91 disabled.forEach(dev => bootorder.push({ name: dev, enabled: false }));
92
93 // add descriptions
94 bootorder.forEach(entry => {
95 entry.desc = me.vmconfig[entry.name];
96 });
97
98 me.store.insert(0, bootorder);
99 me.store.fireEvent("update");
100 },
101
102 calculateValue: function() {
103 let me = this;
104 return me.store.getData().items
105 .filter(x => x.data.enabled)
106 .map(x => x.data.name)
107 .join(';');
108 },
109
110 onGetValues: function() {
111 let me = this;
112 // Note: we allow an empty value, so no 'delete' option
113 let val = { order: me.calculateValue() };
114 let res = { boot: PVE.Parser.printPropertyString(val) };
115 return res;
116 },
117
118 items: [
119 {
120 xtype: 'grid',
121 reference: 'grid',
122 margin: '0 0 5 0',
123 minHeight: 150,
124 defaults: {
125 sortable: false,
126 hideable: false,
127 draggable: false,
128 },
129 columns: [
130 {
131 header: '#',
132 flex: 4,
133 renderer: (value, metaData, record, rowIndex) => {
134 let dragHandle = "<i class='pve-grid-fa fa fa-fw fa-reorder cursor-move'></i>";
135 let idx = (rowIndex + 1).toString();
136 if (record.get('enabled')) {
137 return dragHandle + idx;
138 } else {
139 return dragHandle + "<span class='faded'>" + idx + "</span>";
140 }
141 },
142 },
143 {
144 xtype: 'checkcolumn',
145 header: gettext('Enabled'),
146 dataIndex: 'enabled',
147 flex: 4,
148 },
149 {
150 header: gettext('Device'),
151 dataIndex: 'name',
152 flex: 6,
153 renderer: (value, metaData, record, rowIndex) => {
154 let desc = record.get('desc');
155
156 let icon = '', iconCls;
157 if (value.match(/^net\d+$/)) {
158 iconCls = 'exchange';
159 } else if (desc.match(/media=cdrom/)) {
160 metaData.tdCls = 'pve-itype-icon-cdrom';
161 } else {
162 iconCls = 'hdd-o';
163 }
164 if (iconCls !== undefined) {
165 metaData.tdCls += 'pve-itype-fa';
166 icon = `<i class="pve-grid-fa fa fa-fw fa-${iconCls}"></i>`;
167 }
168
169 return icon + value;
170 },
171 },
172 {
173 header: gettext('Description'),
174 dataIndex: 'desc',
175 flex: 20,
176 },
177 ],
178 viewConfig: {
179 plugins: {
180 ptype: 'gridviewdragdrop',
181 dragText: gettext('Drag and drop to reorder'),
182 },
183 },
184 listeners: {
185 drop: function() {
186 // doesn't fire automatically on reorder
187 this.getStore().fireEvent("update");
188 },
189 },
190 },
191 {
192 xtype: 'component',
193 html: gettext('Drag and drop to reorder'),
194 },
195 {
196 xtype: 'displayfield',
197 reference: 'emptyWarning',
198 userCls: 'pmx-hint',
199 value: gettext('Warning: No devices selected, the VM will probably not boot!'),
200 },
201 {
202 // for dirty marking and 'reset' function
203 xtype: 'field',
204 reference: 'marker',
205 hidden: true,
206 setValue: function(val) {
207 let me = this;
208 let panel = me.up('pveQemuBootOrderPanel');
209
210 // on form reset, go back to original state
211 if (!panel.inUpdate) {
212 panel.setVMConfig(panel.vmconfig);
213 }
214
215 // not a subclass, so no callParent; just do it manually
216 me.setRawValue(me.valueToRaw(val));
217 return me.mixins.field.setValue.call(me, val);
218 },
219 },
220 ],
221
222 initComponent: function() {
223 let me = this;
224
225 me.callParent();
226
227 let controller = me.getController();
228
229 let grid = controller.lookup('grid');
230 let marker = controller.lookup('marker');
231 let emptyWarning = controller.lookup('emptyWarning');
232
233 marker.originalValue = undefined;
234
235 me.store = Ext.create('Ext.data.Store', {
236 model: 'pve-boot-order-entry',
237 listeners: {
238 update: function() {
239 this.commitChanges();
240 let val = me.calculateValue();
241 if (marker.originalValue === undefined) {
242 marker.originalValue = val;
243 }
244 me.inUpdate = true;
245 marker.setValue(val);
246 me.inUpdate = false;
247 marker.checkDirty();
248 emptyWarning.setHidden(val !== '');
249 grid.getView().refresh();
250 },
251 },
252 });
253 grid.setStore(me.store);
254 },
255 });
256
257 Ext.define('PVE.qemu.BootOrderEdit', {
258 extend: 'Proxmox.window.Edit',
259
260 items: [{
261 xtype: 'pveQemuBootOrderPanel',
262 itemId: 'inputpanel',
263 }],
264
265 subject: gettext('Boot Order'),
266 width: 640,
267
268 initComponent: function() {
269 let me = this;
270 me.callParent();
271 me.load({
272 success: function(response, options) {
273 me.down('#inputpanel').setVMConfig(response.result.data);
274 },
275 });
276 },
277 });