]>
Commit | Line | Data |
---|---|---|
02adfe17 DC |
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 | ||
e633ac0f DC |
11 | // will be called after loading finished |
12 | onLoadCallBack: Ext.emptyFn, | |
13 | ||
02adfe17 DC |
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'); | |
aafc1f30 | 83 | me.setSelection(); |
02adfe17 DC |
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 | ||
aafc1f30 | 102 | me.setSelection(); |
02adfe17 DC |
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 | } | |
aafc1f30 | 121 | me.setSelection(); |
02adfe17 DC |
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 | |
02adfe17 DC |
134 | if (slots[slotname] !== undefined) { |
135 | slots[slotname].count++; | |
b736219f | 136 | rec.set('slot', slots[slotname]); |
02adfe17 DC |
137 | return; |
138 | } | |
02adfe17 DC |
139 | slots[slotname] = { |
140 | count: 1, | |
141 | }; | |
142 | ||
b736219f DC |
143 | rec.set('slot', slots[slotname]); |
144 | ||
02adfe17 DC |
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', | |
b736219f DC |
220 | renderer: function(value, _md, rec) { |
221 | if (value.match(/\.[0-9a-f]/i) && rec.data.slot?.count > 1) { | |
222 | return ` ${value}`; | |
223 | } | |
224 | return value; | |
225 | }, | |
02adfe17 DC |
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 | ||
e633ac0f DC |
293 | me.mon(me.getStore(), 'load', me.onLoadCallBack); |
294 | ||
02adfe17 DC |
295 | Proxmox.Utils.monStoreErrors(me, me.getStore(), true); |
296 | ||
297 | me.setNodename(nodename); | |
298 | ||
299 | me.initField(); | |
300 | }, | |
301 | }); |