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