]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/form/MultiPCISelector.js
guest import: allow setting VLAN-tag
[pve-manager.git] / www / manager6 / form / MultiPCISelector.js
1 Ext.define('PVE.form.MultiPCISelector', {
2 extend: 'Ext.grid.Panel',
3 alias: 'widget.pveMultiPCISelector',
4
5 emptyText: gettext('No Devices found'),
6
7 mixins: {
8 field: 'Ext.form.field.Field',
9 },
10
11 // will be called after loading finished
12 onLoadCallBack: Ext.emptyFn,
13
14 getValue: function() {
15 let me = this;
16 return me.value ?? [];
17 },
18
19 getSubmitData: function() {
20 let me = this;
21 let res = {};
22 res[me.name] = me.getValue();
23 return res;
24 },
25
26 setValue: function(value) {
27 let me = this;
28
29 value ??= [];
30
31 me.updateSelectedDevices(value);
32
33 return me.mixins.field.setValue.call(me, value);
34 },
35
36 getErrors: function() {
37 let me = this;
38
39 let errorCls = ['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid'];
40
41 if (me.getValue().length < 1) {
42 let error = gettext("Must choose at least one device");
43 me.addCls(errorCls);
44 me.getActionEl()?.dom.setAttribute('data-errorqtip', error);
45
46 return [error];
47 }
48
49 me.removeCls(errorCls);
50 me.getActionEl()?.dom.setAttribute('data-errorqtip', "");
51
52 return [];
53 },
54
55 viewConfig: {
56 getRowClass: function(record) {
57 if (record.data.disabled === true) {
58 return 'x-item-disabled';
59 }
60 return '';
61 },
62 },
63
64 updateSelectedDevices: function(value = []) {
65 let me = this;
66
67 let recs = [];
68 let store = me.getStore();
69
70 for (const map of value) {
71 let parsed = PVE.Parser.parsePropertyString(map);
72 if (parsed.node !== me.nodename) {
73 continue;
74 }
75
76 let rec = store.getById(parsed.path);
77 if (rec) {
78 recs.push(rec);
79 }
80 }
81
82 me.suspendEvent('change');
83 me.setSelection();
84 me.setSelection(recs);
85 me.resumeEvent('change');
86 },
87
88 setNodename: function(nodename) {
89 let me = this;
90
91 if (!nodename || me.nodename === nodename) {
92 return;
93 }
94
95 me.nodename = nodename;
96
97 me.getStore().setProxy({
98 type: 'proxmox',
99 url: '/api2/json/nodes/' + me.nodename + '/hardware/pci?pci-class-blacklist=',
100 });
101
102 me.setSelection();
103
104 me.getStore().load({
105 callback: (recs, op, success) => me.addSlotRecords(recs, op, success),
106 });
107 },
108
109 setMdev: function(mdev) {
110 let me = this;
111 if (mdev) {
112 me.getStore().addFilter({
113 id: 'mdev-filter',
114 property: 'mdev',
115 value: '1',
116 operator: '=',
117 });
118 } else {
119 me.getStore().removeFilter('mdev-filter');
120 }
121 me.setSelection();
122 },
123
124 // adds the virtual 'slot' records (e.g. '0000:01:00') to the store
125 addSlotRecords: function(records, _op, success) {
126 let me = this;
127 if (!success) {
128 return;
129 }
130
131 let slots = {};
132 records.forEach((rec) => {
133 let slotname = rec.data.id.slice(0, -2); // remove function
134 if (slots[slotname] !== undefined) {
135 slots[slotname].count++;
136 rec.set('slot', slots[slotname]);
137 return;
138 }
139 slots[slotname] = {
140 count: 1,
141 };
142
143 rec.set('slot', slots[slotname]);
144
145 if (rec.data.id.endsWith('.0')) {
146 slots[slotname].device = rec.data;
147 }
148 });
149
150 let store = me.getStore();
151
152 for (const [slot, { count, device }] of Object.entries(slots)) {
153 if (count === 1) {
154 continue;
155 }
156 store.add(Ext.apply({}, {
157 id: slot,
158 mdev: undefined,
159 device_name: gettext('Pass through all functions as one device'),
160 }, device));
161 }
162
163 me.updateSelectedDevices(me.value);
164 },
165
166 selectionChange: function(_grid, selection) {
167 let me = this;
168
169 let ids = {};
170 selection
171 .filter(rec => rec.data.id.indexOf('.') === -1)
172 .forEach((rec) => { ids[rec.data.id] = true; });
173
174 let to_disable = [];
175
176 me.getStore().each(rec => {
177 let id = rec.data.id;
178 rec.set('disabled', false);
179 if (id.indexOf('.') === -1) {
180 return;
181 }
182 let slot = id.slice(0, -2); // remove function
183
184 if (ids[slot]) {
185 to_disable.push(rec);
186 rec.set('disabled', true);
187 }
188 });
189
190 me.suspendEvent('selectionchange');
191 me.getSelectionModel().deselect(to_disable);
192 me.resumeEvent('selectionchange');
193
194 me.value = me.getSelection().map((rec) => {
195 let res = {
196 path: rec.data.id,
197 node: me.nodename,
198 id: `${rec.data.vendor}:${rec.data.device}`.replace(/0x/g, ''),
199 'subsystem-id': `${rec.data.subsystem_vendor}:${rec.data.subsystem_device}`.replace(/0x/g, ''),
200 };
201
202 if (rec.data.iommugroup !== -1) {
203 res.iommugroup = rec.data.iommugroup;
204 }
205
206 return PVE.Parser.printPropertyString(res);
207 });
208 me.checkChange();
209 },
210
211 selModel: {
212 type: 'checkboxmodel',
213 mode: 'SIMPLE',
214 },
215
216 columns: [
217 {
218 header: 'ID',
219 dataIndex: 'id',
220 renderer: function(value, _md, rec) {
221 if (value.match(/\.[0-9a-f]/i) && rec.data.slot?.count > 1) {
222 return `&emsp;${value}`;
223 }
224 return value;
225 },
226 width: 150,
227 },
228 {
229 header: gettext('IOMMU Group'),
230 dataIndex: 'iommugroup',
231 renderer: (v, _md, rec) => rec.data.slot === rec.data.id ? '' : v === -1 ? '-' : v,
232 width: 50,
233 },
234 {
235 header: gettext('Vendor'),
236 dataIndex: 'vendor_name',
237 flex: 3,
238 },
239 {
240 header: gettext('Device'),
241 dataIndex: 'device_name',
242 flex: 6,
243 },
244 {
245 header: gettext('Mediated Devices'),
246 dataIndex: 'mdev',
247 flex: 1,
248 renderer: function(val) {
249 return Proxmox.Utils.format_boolean(!!val);
250 },
251 },
252 ],
253
254 listeners: {
255 selectionchange: function() {
256 this.selectionChange(...arguments);
257 },
258 },
259
260 store: {
261 fields: [
262 'id', 'vendor_name', 'device_name', 'vendor', 'device', 'iommugroup', 'mdev',
263 'subsystem_vendor', 'subsystem_device', 'disabled',
264 {
265 name: 'subsystem-vendor',
266 calculate: function(data) {
267 return data.subsystem_vendor;
268 },
269 },
270 {
271 name: 'subsystem-device',
272 calculate: function(data) {
273 return data.subsystem_device;
274 },
275 },
276 ],
277 sorters: [
278 {
279 property: 'id',
280 direction: 'ASC',
281 },
282 ],
283 },
284
285 initComponent: function() {
286 let me = this;
287
288 let nodename = me.nodename;
289 me.nodename = undefined;
290
291 me.callParent();
292
293 me.mon(me.getStore(), 'load', me.onLoadCallBack);
294
295 Proxmox.Utils.monStoreErrors(me, me.getStore(), true);
296
297 me.setNodename(nodename);
298
299 me.initField();
300 },
301 });