]>
Commit | Line | Data |
---|---|---|
5957f47f SR |
1 | Ext.define('PVE.form.CorosyncLinkEditorController', { |
2 | extend: 'Ext.app.ViewController', | |
3 | alias: 'controller.pveCorosyncLinkEditorController', | |
4 | ||
5 | addLinkIfEmpty: function() { | |
6 | let view = this.getView(); | |
7 | if (view.items || view.items.length == 0) { | |
8 | this.addLink(); | |
9 | } | |
10 | }, | |
11 | ||
12 | addEmptyLink: function() { | |
13 | // discard parameters to allow being called from 'handler' | |
14 | this.addLink(); | |
15 | }, | |
16 | ||
4ea2cac2 | 17 | addLink: function(link) { |
5957f47f SR |
18 | let me = this; |
19 | let view = me.getView(); | |
20 | let vm = view.getViewModel(); | |
21 | ||
22 | let linkCount = vm.get('linkCount'); | |
23 | if (linkCount >= vm.get('maxLinkCount')) { | |
24 | return; | |
25 | } | |
26 | ||
4ea2cac2 TL |
27 | link = link || {}; |
28 | ||
29 | if (link.number === undefined) { | |
30 | link.number = me.getNextFreeNumber(); | |
5957f47f | 31 | } |
4ea2cac2 TL |
32 | if (link.value === undefined) { |
33 | link.value = me.getNextFreeNetwork(); | |
5957f47f SR |
34 | } |
35 | ||
36 | let linkSelector = Ext.create('PVE.form.CorosyncLinkSelector', { | |
37 | maxLinkNumber: vm.get('maxLinkCount') - 1, | |
38 | allowNumberEdit: vm.get('allowNumberEdit'), | |
4ea2cac2 TL |
39 | allowBlankNetwork: link.allowBlank, |
40 | initNumber: link.number, | |
41 | initNetwork: link.value, | |
42 | text: link.text, | |
43 | emptyText: link.emptyText, | |
5957f47f SR |
44 | |
45 | // needs to be set here, because we need to update the viewmodel | |
46 | removeBtnHandler: function() { | |
47 | let curLinkCount = vm.get('linkCount'); | |
48 | ||
49 | if (curLinkCount <= 1) { | |
50 | return; | |
51 | } | |
52 | ||
53 | vm.set('linkCount', curLinkCount - 1); | |
54 | ||
55 | // 'this' is the linkSelector here | |
56 | view.remove(this); | |
57 | ||
58 | me.updateDeleteButtonState(); | |
f6710aac | 59 | }, |
5957f47f SR |
60 | }); |
61 | ||
62 | view.add(linkSelector); | |
63 | ||
64 | linkCount++; | |
65 | vm.set('linkCount', linkCount); | |
66 | ||
67 | me.updateDeleteButtonState(); | |
68 | }, | |
69 | ||
70 | // ExtJS trips on binding this for some reason, so do it manually | |
71 | updateDeleteButtonState: function() { | |
72 | let view = this.getView(); | |
73 | let vm = view.getViewModel(); | |
74 | ||
75 | let disabled = vm.get('linkCount') <= 1; | |
76 | ||
77 | let deleteButtons = view.query('button[cls=removeLinkBtn]'); | |
78 | Ext.Array.each(deleteButtons, btn => { | |
79 | btn.setDisabled(disabled); | |
8058410f | 80 | }); |
5957f47f SR |
81 | }, |
82 | ||
83 | getNextFreeNetwork: function() { | |
84 | let view = this.getView(); | |
85 | let vm = view.getViewModel(); | |
86 | let netsInUse = Ext.Array.map( | |
87 | view.query('proxmoxNetworkSelector'), selector => selector.value); | |
88 | ||
89 | // default to empty field, user has to set up link manually | |
90 | let retval = undefined; | |
91 | ||
92 | let nets = vm.get('networks'); | |
93 | Ext.Array.each(nets, net => { | |
94 | if (!Ext.Array.contains(netsInUse, net)) { | |
95 | retval = net; | |
96 | return false; // break | |
97 | } | |
98 | }); | |
99 | ||
100 | return retval; | |
101 | }, | |
102 | ||
103 | getNextFreeNumber: function() { | |
104 | let view = this.getView(); | |
105 | let vm = view.getViewModel(); | |
106 | let numbersInUse = Ext.Array.map( | |
107 | view.query('numberfield'), field => field.value); | |
108 | ||
109 | for (let i = 0; i < vm.get('maxLinkCount'); i++) { | |
110 | if (!Ext.Array.contains(numbersInUse, i)) { | |
111 | return i; | |
112 | } | |
113 | } | |
114 | ||
115 | // all numbers in use, this should never happen since add button is | |
116 | // disabled automatically | |
117 | return 0; | |
f6710aac | 118 | }, |
5957f47f SR |
119 | }); |
120 | ||
121 | Ext.define('PVE.form.CorosyncLinkSelector', { | |
122 | extend: 'Ext.panel.Panel', | |
123 | xtype: 'pveCorosyncLinkSelector', | |
124 | ||
8058410f | 125 | mixins: ['Proxmox.Mixin.CBind'], |
5957f47f SR |
126 | cbindData: [], |
127 | ||
128 | // config | |
129 | maxLinkNumber: 7, | |
130 | allowNumberEdit: true, | |
131 | allowBlankNetwork: false, | |
132 | removeBtnHandler: undefined, | |
4ea2cac2 | 133 | emptyText: '', |
5957f47f SR |
134 | |
135 | // values | |
136 | initNumber: 0, | |
137 | initNetwork: '', | |
b1e7a7d3 | 138 | text: '', |
5957f47f SR |
139 | |
140 | layout: 'hbox', | |
141 | bodyPadding: 5, | |
142 | border: 0, | |
143 | ||
144 | items: [ | |
61b05a5d TL |
145 | { |
146 | xtype: 'displayfield', | |
147 | fieldLabel: 'Link', | |
148 | cbind: { | |
149 | hidden: '{allowNumberEdit}', | |
f6710aac | 150 | value: '{initNumber}', |
61b05a5d TL |
151 | }, |
152 | width: 45, | |
153 | labelWidth: 30, | |
154 | allowBlank: false, | |
155 | }, | |
5957f47f SR |
156 | { |
157 | xtype: 'numberfield', | |
61b05a5d | 158 | fieldLabel: 'Link', |
5957f47f SR |
159 | cbind: { |
160 | maxValue: '{maxLinkNumber}', | |
61b05a5d | 161 | hidden: '{!allowNumberEdit}', |
f6710aac | 162 | value: '{initNumber}', |
5957f47f | 163 | }, |
5957f47f SR |
164 | width: 80, |
165 | labelWidth: 30, | |
61b05a5d TL |
166 | minValue: 0, |
167 | submitValue: false, // see getSubmitValue of network selector | |
168 | allowBlank: false, | |
5957f47f SR |
169 | }, |
170 | { | |
171 | xtype: 'proxmoxNetworkSelector', | |
172 | cbind: { | |
173 | allowBlank: '{allowBlankNetwork}', | |
61b05a5d TL |
174 | value: '{initNetwork}', |
175 | emptyText: '{emptyText}', | |
5957f47f | 176 | }, |
5957f47f SR |
177 | autoSelect: false, |
178 | valueField: 'address', | |
179 | displayField: 'address', | |
61b05a5d | 180 | width: 220, |
5957f47f SR |
181 | margin: '0 5px 0 5px', |
182 | getSubmitValue: function() { | |
61b05a5d TL |
183 | // link number is encoded into key, so we need to set field |
184 | // name before value retrieval | |
5957f47f | 185 | let me = this; |
61b05a5d | 186 | let numSelect = me.prev('numberfield'); // always the correct one |
5957f47f SR |
187 | let linkNumber = numSelect.getValue(); |
188 | me.name = 'link' + linkNumber; | |
189 | return me.getValue(); | |
f6710aac | 190 | }, |
5957f47f SR |
191 | }, |
192 | { | |
193 | xtype: 'button', | |
194 | iconCls: 'fa fa-trash-o', | |
195 | cls: 'removeLinkBtn', | |
5957f47f | 196 | cbind: { |
f6710aac | 197 | hidden: '{!allowNumberEdit}', |
5957f47f | 198 | }, |
5957f47f SR |
199 | handler: function() { |
200 | let me = this; | |
201 | let parent = me.up('pveCorosyncLinkSelector'); | |
202 | if (parent.removeBtnHandler !== undefined) { | |
203 | parent.removeBtnHandler(); | |
204 | } | |
f6710aac | 205 | }, |
b1e7a7d3 SR |
206 | }, |
207 | { | |
208 | xtype: 'label', | |
209 | margin: '-1px 0 0 5px', | |
210 | ||
211 | // for muted effect | |
212 | cls: 'x-form-item-label-default', | |
213 | ||
214 | cbind: { | |
f6710aac TL |
215 | text: '{text}', |
216 | }, | |
217 | }, | |
5957f47f SR |
218 | ], |
219 | ||
220 | initComponent: function() { | |
221 | let me = this; | |
222 | ||
223 | me.callParent(); | |
224 | ||
225 | let numSelect = me.down('numberfield'); | |
226 | let netSelect = me.down('proxmoxNetworkSelector'); | |
227 | ||
228 | numSelect.validator = this.createNoDuplicatesValidator( | |
229 | 'numberfield', | |
f6710aac | 230 | gettext("Duplicate link number not allowed."), |
5957f47f SR |
231 | ); |
232 | ||
233 | netSelect.validator = this.createNoDuplicatesValidator( | |
234 | 'proxmoxNetworkSelector', | |
f6710aac | 235 | gettext("Duplicate link address not allowed."), |
5957f47f SR |
236 | ); |
237 | }, | |
238 | ||
239 | createNoDuplicatesValidator: function(queryString, errorMsg) { | |
240 | // linkSelector | |
241 | let me = this; | |
242 | ||
243 | return function(val) { | |
244 | let curField = this; | |
245 | let form = me.up('form'); | |
246 | let linkEditor = me.up('pveCorosyncLinkEditor'); | |
247 | ||
248 | if (!form.validating) { | |
249 | // avoid recursion/double validation by setting temporary states | |
250 | curField.validating = true; | |
251 | form.validating = true; | |
252 | ||
253 | // validate all other fields as well, to always mark both | |
254 | // parties involved in a 'duplicate' error | |
255 | form.isValid(); | |
256 | ||
257 | form.validating = false; | |
258 | curField.validating = false; | |
259 | } else if (curField.validating) { | |
260 | // we'll be validated by the original call in the other | |
261 | // if-branch, avoid double work | |
262 | return true; | |
263 | } | |
264 | ||
265 | if (val === undefined || (val instanceof String && val.length === 0)) { | |
266 | // let this be caught by allowBlank, if at all | |
267 | return true; | |
268 | } | |
269 | ||
270 | let allFields = linkEditor.query(queryString); | |
271 | let err = undefined; | |
272 | Ext.Array.each(allFields, field => { | |
273 | if (field != curField && field.getValue() == val) { | |
274 | err = errorMsg; | |
275 | return false; // break | |
276 | } | |
277 | }); | |
278 | ||
279 | return err || true; | |
280 | }; | |
f6710aac | 281 | }, |
5957f47f SR |
282 | }); |
283 | ||
284 | Ext.define('PVE.form.CorosyncLinkEditor', { | |
285 | extend: 'Ext.panel.Panel', | |
286 | xtype: 'pveCorosyncLinkEditor', | |
287 | ||
288 | controller: 'pveCorosyncLinkEditorController', | |
289 | ||
290 | // only initial config, use setter otherwise | |
291 | allowNumberEdit: true, | |
292 | ||
293 | viewModel: { | |
294 | data: { | |
295 | linkCount: 0, | |
296 | maxLinkCount: 8, | |
297 | networks: null, | |
298 | allowNumberEdit: true, | |
f6710aac | 299 | infoText: '', |
5957f47f SR |
300 | }, |
301 | formulas: { | |
302 | addDisabled: function(get) { | |
303 | return !get('allowNumberEdit') || | |
304 | get('linkCount') >= get('maxLinkCount'); | |
305 | }, | |
306 | dockHidden: function(get) { | |
307 | return !(get('allowNumberEdit') || get('infoText')); | |
f6710aac TL |
308 | }, |
309 | }, | |
5957f47f SR |
310 | }, |
311 | ||
312 | dockedItems: [{ | |
313 | xtype: 'toolbar', | |
314 | dock: 'bottom', | |
8058410f | 315 | defaultButtonUI: 'default', |
61b05a5d TL |
316 | border: false, |
317 | padding: '6 0 6 0', | |
5957f47f | 318 | bind: { |
f6710aac | 319 | hidden: '{dockHidden}', |
5957f47f SR |
320 | }, |
321 | items: [ | |
322 | { | |
323 | xtype: 'button', | |
324 | text: gettext('Add'), | |
325 | bind: { | |
326 | disabled: '{addDisabled}', | |
f6710aac | 327 | hidden: '{!allowNumberEdit}', |
5957f47f | 328 | }, |
f6710aac | 329 | handler: 'addEmptyLink', |
5957f47f SR |
330 | }, |
331 | { | |
332 | xtype: 'label', | |
333 | bind: { | |
f6710aac TL |
334 | text: '{infoText}', |
335 | }, | |
336 | }, | |
337 | ], | |
5957f47f SR |
338 | }], |
339 | ||
340 | setInfoText: function(text) { | |
341 | let me = this; | |
342 | let vm = me.getViewModel(); | |
343 | ||
344 | vm.set('infoText', text || ''); | |
345 | }, | |
346 | ||
347 | setLinks: function(links) { | |
348 | let me = this; | |
349 | let controller = me.getController(); | |
350 | let vm = me.getViewModel(); | |
351 | ||
352 | me.removeAll(); | |
353 | vm.set('linkCount', 0); | |
354 | ||
4ea2cac2 | 355 | Ext.Array.each(links, link => controller.addLink(link)); |
5957f47f SR |
356 | }, |
357 | ||
358 | setDefaultLinks: function() { | |
359 | let me = this; | |
360 | let controller = me.getController(); | |
361 | let vm = me.getViewModel(); | |
362 | ||
363 | me.removeAll(); | |
364 | vm.set('linkCount', 0); | |
365 | controller.addLink(); | |
366 | }, | |
367 | ||
368 | // clears all links | |
369 | setAllowNumberEdit: function(allow) { | |
370 | let me = this; | |
371 | let vm = me.getViewModel(); | |
372 | vm.set('allowNumberEdit', allow); | |
373 | me.removeAll(); | |
374 | vm.set('linkCount', 0); | |
375 | }, | |
376 | ||
377 | items: [{ | |
378 | // No links is never a valid scenario, but can occur during a slow load | |
379 | xtype: 'hiddenfield', | |
380 | submitValue: false, | |
381 | isValid: function() { | |
382 | let me = this; | |
383 | let vm = me.up('pveCorosyncLinkEditor').getViewModel(); | |
384 | return vm.get('linkCount') > 0; | |
f6710aac | 385 | }, |
5957f47f SR |
386 | }], |
387 | ||
388 | initComponent: function() { | |
389 | let me = this; | |
390 | let vm = me.getViewModel(); | |
391 | let controller = me.getController(); | |
392 | ||
393 | vm.set('allowNumberEdit', me.allowNumberEdit); | |
41aa53ea | 394 | vm.set('infoText', me.infoText || ''); |
5957f47f SR |
395 | |
396 | me.callParent(); | |
397 | ||
398 | // Request local node networks to pre-populate first link. | |
399 | Proxmox.Utils.API2Request({ | |
400 | url: '/nodes/localhost/network', | |
401 | method: 'GET', | |
402 | waitMsgTarget: me, | |
403 | success: response => { | |
404 | let data = response.result.data; | |
405 | if (data.length > 0) { | |
406 | data.sort((a, b) => a.iface.localeCompare(b.iface)); | |
407 | let addresses = []; | |
408 | for (let net of data) { | |
409 | if (net.address) { | |
410 | addresses.push(net.address); | |
411 | } | |
412 | if (net.address6) { | |
413 | addresses.push(net.address6); | |
414 | } | |
415 | } | |
416 | ||
417 | vm.set('networks', addresses); | |
418 | } | |
419 | ||
420 | // Always have at least one link, but account for delay in API, | |
421 | // someone might have called 'setLinks' in the meantime - | |
422 | // except if 'allowNumberEdit' is false, in which case we're | |
423 | // probably waiting for the user to input the join info | |
424 | if (vm.get('allowNumberEdit')) { | |
425 | controller.addLinkIfEmpty(); | |
426 | } | |
427 | }, | |
428 | failure: () => { | |
429 | if (vm.get('allowNumberEdit')) { | |
430 | controller.addLinkIfEmpty(); | |
431 | } | |
f6710aac | 432 | }, |
5957f47f | 433 | }); |
f6710aac | 434 | }, |
5957f47f SR |
435 | }); |
436 |