]>
Commit | Line | Data |
---|---|---|
5f219fd3 DC |
1 | Ext.define('PVE.panel.MultiDiskPanel', { |
2 | extend: 'Ext.panel.Panel', | |
3 | ||
4 | setNodename: function(nodename) { | |
5 | this.items.each((panel) => panel.setNodename(nodename)); | |
6 | }, | |
7 | ||
8 | border: false, | |
9 | bodyBorder: false, | |
10 | ||
11 | layout: 'card', | |
12 | ||
13 | controller: { | |
14 | xclass: 'Ext.app.ViewController', | |
15 | ||
16 | vmconfig: {}, | |
17 | ||
18 | onAdd: function() { | |
19 | let me = this; | |
20 | me.lookup('addButton').setDisabled(true); | |
21 | me.addDisk(); | |
22 | let count = me.lookup('grid').getStore().getCount() + 1; // +1 is from ide2 | |
23 | me.lookup('addButton').setDisabled(count >= me.maxCount); | |
24 | }, | |
25 | ||
26 | getNextFreeDisk: function(vmconfig) { | |
27 | throw "implement in subclass"; | |
28 | }, | |
29 | ||
30 | addPanel: function(itemId, vmconfig, nextFreeDisk) { | |
31 | throw "implement in subclass"; | |
32 | }, | |
33 | ||
34 | // define in subclass | |
35 | diskSorter: undefined, | |
36 | ||
37 | addDisk: function() { | |
38 | let me = this; | |
39 | let grid = me.lookup('grid'); | |
40 | let store = grid.getStore(); | |
41 | ||
42 | // get free disk id | |
43 | let vmconfig = me.getVMConfig(true); | |
44 | let nextFreeDisk = me.getNextFreeDisk(vmconfig); | |
45 | if (!nextFreeDisk) { | |
46 | return; | |
47 | } | |
48 | ||
49 | // add store entry + panel | |
50 | let itemId = 'disk-card-' + ++Ext.idSeed; | |
51 | let rec = store.add({ | |
52 | name: nextFreeDisk.confid, | |
53 | itemId, | |
54 | })[0]; | |
55 | ||
56 | let panel = me.addPanel(itemId, vmconfig, nextFreeDisk); | |
57 | panel.updateVMConfig(vmconfig); | |
58 | ||
59 | // we need to setup a validitychange handler, so that we can show | |
60 | // that a disk has invalid fields | |
61 | let fields = panel.query('field'); | |
62 | fields.forEach((el) => el.on('validitychange', () => { | |
63 | let valid = fields.every((field) => field.isValid()); | |
64 | rec.set('valid', valid); | |
65 | me.checkValidity(); | |
66 | })); | |
67 | ||
68 | store.sort(me.diskSorter); | |
69 | ||
70 | // select if the panel added is the only one | |
71 | if (store.getCount() === 1) { | |
72 | grid.getSelectionModel().select(0, false); | |
73 | } | |
74 | }, | |
75 | ||
76 | getBaseVMConfig: function() { | |
77 | throw "implement in subclass"; | |
78 | }, | |
79 | ||
80 | getVMConfig: function(all) { | |
81 | let me = this; | |
82 | ||
83 | let vmconfig = me.getBaseVMConfig(); | |
84 | ||
85 | me.lookup('grid').getStore().each((rec) => { | |
86 | if (all || rec.get('valid')) { | |
87 | vmconfig[rec.get('name')] = rec.get('itemId'); | |
88 | } | |
89 | }); | |
90 | ||
91 | return vmconfig; | |
92 | }, | |
93 | ||
94 | checkValidity: function() { | |
95 | let me = this; | |
96 | let valid = me.lookup('grid').getStore().findExact('valid', false) === -1; | |
97 | me.lookup('validationfield').setValue(valid); | |
98 | }, | |
99 | ||
100 | updateVMConfig: function() { | |
101 | let me = this; | |
102 | let view = me.getView(); | |
103 | let grid = me.lookup('grid'); | |
104 | let store = grid.getStore(); | |
105 | ||
106 | let vmconfig = me.getVMConfig(); | |
107 | ||
108 | let valid = true; | |
109 | ||
110 | store.each((rec) => { | |
111 | let itemId = rec.get('itemId'); | |
112 | let name = rec.get('name'); | |
113 | let panel = view.getComponent(itemId); | |
114 | if (!panel) { | |
115 | throw "unexpected missing panel"; | |
116 | } | |
117 | ||
118 | // copy config for each panel and remote its own id | |
119 | let panel_vmconfig = Ext.apply({}, vmconfig); | |
120 | if (panel_vmconfig[name] === itemId) { | |
121 | delete panel_vmconfig[name]; | |
122 | } | |
123 | ||
124 | if (!rec.get('valid')) { | |
125 | valid = false; | |
126 | } | |
127 | ||
128 | panel.updateVMConfig(panel_vmconfig); | |
129 | }); | |
130 | ||
131 | me.lookup('validationfield').setValue(valid); | |
132 | ||
133 | return vmconfig; | |
134 | }, | |
135 | ||
136 | onChange: function(panel, newVal) { | |
137 | let me = this; | |
138 | let store = me.lookup('grid').getStore(); | |
139 | ||
140 | let el = store.findRecord('itemId', panel.itemId, 0, false, true, true); | |
141 | if (el.get('name') === newVal) { | |
142 | // do not update if there was no change | |
143 | return; | |
144 | } | |
145 | ||
146 | el.set('name', newVal); | |
147 | el.commit(); | |
148 | ||
149 | store.sort(me.diskSorter); | |
150 | ||
151 | // so that it happens after the layouting | |
152 | setTimeout(function() { | |
153 | me.updateVMConfig(); | |
154 | }, 10); | |
155 | }, | |
156 | ||
157 | onRemove: function(tableview, rowIndex, colIndex, item, event, record) { | |
158 | let me = this; | |
159 | let grid = me.lookup('grid'); | |
160 | let store = grid.getStore(); | |
161 | let removed_idx = store.indexOf(record); | |
162 | ||
163 | let selection = grid.getSelection()[0]; | |
164 | let selected_idx = store.indexOf(selection); | |
165 | ||
166 | if (selected_idx === removed_idx) { | |
167 | let newidx = store.getCount() > removed_idx + 1 ? removed_idx + 1: removed_idx - 1; | |
168 | grid.getSelectionModel().select(newidx, false); | |
169 | } | |
170 | ||
171 | store.remove(record); | |
172 | me.getView().remove(record.get('itemId')); | |
173 | me.lookup('addButton').setDisabled(false); | |
174 | me.updateVMConfig(); | |
175 | me.checkValidity(); | |
176 | }, | |
177 | ||
178 | onSelectionChange: function(grid, selection) { | |
179 | let me = this; | |
180 | if (!selection || selection.length < 1) { | |
181 | return; | |
182 | } | |
183 | ||
184 | me.getView().setActiveItem(selection[0].data.itemId); | |
185 | }, | |
186 | ||
187 | control: { | |
188 | 'inputpanel': { | |
189 | diskidchange: 'onChange', | |
190 | }, | |
191 | 'grid[reference=grid]': { | |
192 | selectionchange: 'onSelectionChange', | |
193 | }, | |
194 | }, | |
195 | ||
196 | init: function(view) { | |
197 | let me = this; | |
198 | me.onAdd(); | |
199 | me.lookup('grid').getSelectionModel().select(0, false); | |
200 | }, | |
201 | }, | |
202 | ||
203 | dockedItems: [ | |
204 | { | |
205 | xtype: 'container', | |
206 | layout: { | |
207 | type: 'vbox', | |
208 | align: 'stretch', | |
209 | }, | |
210 | dock: 'left', | |
211 | border: false, | |
212 | width: 130, | |
213 | items: [ | |
214 | { | |
215 | xtype: 'grid', | |
216 | hideHeaders: true, | |
217 | reference: 'grid', | |
218 | flex: 1, | |
219 | emptyText: gettext('No Disks'), | |
220 | margin: '0 0 5 0', | |
221 | store: { | |
222 | fields: ['name', 'itemId', 'valid'], | |
223 | data: [], | |
224 | }, | |
225 | columns: [ | |
226 | { | |
227 | dataIndex: 'name', | |
228 | renderer: function(val, md, rec) { | |
229 | let warn = ''; | |
230 | if (!rec.get('valid')) { | |
231 | warn = ' <i class="fa warning fa-warning"></i>'; | |
232 | } | |
233 | return val + warn; | |
234 | }, | |
235 | flex: 1, | |
236 | }, | |
237 | { | |
238 | xtype: 'actioncolumn', | |
239 | width: 30, | |
240 | align: 'center', | |
241 | menuDisabled: true, | |
242 | items: [ | |
243 | { | |
244 | iconCls: 'x-fa fa-trash critical', | |
245 | tooltip: 'Delete', | |
246 | handler: 'onRemove', | |
247 | isActionDisabled: 'deleteDisabled', | |
248 | }, | |
249 | ], | |
250 | }, | |
251 | ], | |
252 | }, | |
253 | { | |
254 | xtype: 'button', | |
255 | reference: 'addButton', | |
256 | text: gettext('Add'), | |
257 | iconCls: 'fa fa-plus-circle', | |
258 | handler: 'onAdd', | |
259 | }, | |
260 | { | |
261 | // dummy field to control wizard validation | |
262 | xtype: 'textfield', | |
263 | hidden: true, | |
264 | reference: 'validationfield', | |
265 | submitValue: false, | |
266 | value: true, | |
267 | validator: (val) => !!val, | |
268 | }, | |
269 | ], | |
270 | }, | |
271 | ], | |
272 | }); |