pve-manager/www/manager6/dc/CorosyncLinkEdit.js
Thomas Lamprecht 41aa53eaa0 ui: cluster edit/create: style nits, avoid extra label
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-05-05 18:23:01 +02:00

437 lines
9.5 KiB
JavaScript

Ext.define('PVE.form.CorosyncLinkEditorController', {
extend: 'Ext.app.ViewController',
alias: 'controller.pveCorosyncLinkEditorController',
addLinkIfEmpty: function() {
let view = this.getView();
if (view.items || view.items.length == 0) {
this.addLink();
}
},
addEmptyLink: function() {
// discard parameters to allow being called from 'handler'
this.addLink();
},
addLink: function(link) {
let me = this;
let view = me.getView();
let vm = view.getViewModel();
let linkCount = vm.get('linkCount');
if (linkCount >= vm.get('maxLinkCount')) {
return;
}
link = link || {};
if (link.number === undefined) {
link.number = me.getNextFreeNumber();
}
if (link.value === undefined) {
link.value = me.getNextFreeNetwork();
}
let linkSelector = Ext.create('PVE.form.CorosyncLinkSelector', {
maxLinkNumber: vm.get('maxLinkCount') - 1,
allowNumberEdit: vm.get('allowNumberEdit'),
allowBlankNetwork: link.allowBlank,
initNumber: link.number,
initNetwork: link.value,
text: link.text,
emptyText: link.emptyText,
// needs to be set here, because we need to update the viewmodel
removeBtnHandler: function() {
let curLinkCount = vm.get('linkCount');
if (curLinkCount <= 1) {
return;
}
vm.set('linkCount', curLinkCount - 1);
// 'this' is the linkSelector here
view.remove(this);
me.updateDeleteButtonState();
}
});
view.add(linkSelector);
linkCount++;
vm.set('linkCount', linkCount);
me.updateDeleteButtonState();
},
// ExtJS trips on binding this for some reason, so do it manually
updateDeleteButtonState: function() {
let view = this.getView();
let vm = view.getViewModel();
let disabled = vm.get('linkCount') <= 1;
let deleteButtons = view.query('button[cls=removeLinkBtn]');
Ext.Array.each(deleteButtons, btn => {
btn.setDisabled(disabled);
})
},
getNextFreeNetwork: function() {
let view = this.getView();
let vm = view.getViewModel();
let netsInUse = Ext.Array.map(
view.query('proxmoxNetworkSelector'), selector => selector.value);
// default to empty field, user has to set up link manually
let retval = undefined;
let nets = vm.get('networks');
Ext.Array.each(nets, net => {
if (!Ext.Array.contains(netsInUse, net)) {
retval = net;
return false; // break
}
});
return retval;
},
getNextFreeNumber: function() {
let view = this.getView();
let vm = view.getViewModel();
let numbersInUse = Ext.Array.map(
view.query('numberfield'), field => field.value);
for (let i = 0; i < vm.get('maxLinkCount'); i++) {
if (!Ext.Array.contains(numbersInUse, i)) {
return i;
}
}
// all numbers in use, this should never happen since add button is
// disabled automatically
return 0;
}
});
Ext.define('PVE.form.CorosyncLinkSelector', {
extend: 'Ext.panel.Panel',
xtype: 'pveCorosyncLinkSelector',
mixins: ['Proxmox.Mixin.CBind' ],
cbindData: [],
// config
maxLinkNumber: 7,
allowNumberEdit: true,
allowBlankNetwork: false,
removeBtnHandler: undefined,
emptyText: '',
// values
initNumber: 0,
initNetwork: '',
text: '',
layout: 'hbox',
bodyPadding: 5,
border: 0,
items: [
{
xtype: 'displayfield',
fieldLabel: 'Link',
cbind: {
hidden: '{allowNumberEdit}',
value: '{initNumber}'
},
width: 45,
labelWidth: 30,
allowBlank: false,
},
{
xtype: 'numberfield',
fieldLabel: 'Link',
cbind: {
maxValue: '{maxLinkNumber}',
hidden: '{!allowNumberEdit}',
value: '{initNumber}'
},
width: 80,
labelWidth: 30,
minValue: 0,
submitValue: false, // see getSubmitValue of network selector
allowBlank: false,
},
{
xtype: 'proxmoxNetworkSelector',
cbind: {
allowBlank: '{allowBlankNetwork}',
value: '{initNetwork}',
emptyText: '{emptyText}',
},
autoSelect: false,
valueField: 'address',
displayField: 'address',
width: 220,
margin: '0 5px 0 5px',
getSubmitValue: function() {
// link number is encoded into key, so we need to set field
// name before value retrieval
let me = this;
let numSelect = me.prev('numberfield'); // always the correct one
let linkNumber = numSelect.getValue();
me.name = 'link' + linkNumber;
return me.getValue();
}
},
{
xtype: 'button',
iconCls: 'fa fa-trash-o',
cls: 'removeLinkBtn',
cbind: {
hidden: '{!allowNumberEdit}'
},
handler: function() {
let me = this;
let parent = me.up('pveCorosyncLinkSelector');
if (parent.removeBtnHandler !== undefined) {
parent.removeBtnHandler();
}
}
},
{
xtype: 'label',
margin: '-1px 0 0 5px',
// for muted effect
cls: 'x-form-item-label-default',
cbind: {
text: '{text}'
}
}
],
initComponent: function() {
let me = this;
me.callParent();
let numSelect = me.down('numberfield');
let netSelect = me.down('proxmoxNetworkSelector');
numSelect.validator = this.createNoDuplicatesValidator(
'numberfield',
gettext("Duplicate link number not allowed.")
);
netSelect.validator = this.createNoDuplicatesValidator(
'proxmoxNetworkSelector',
gettext("Duplicate link address not allowed.")
);
},
createNoDuplicatesValidator: function(queryString, errorMsg) {
// linkSelector
let me = this;
return function(val) {
let curField = this;
let form = me.up('form');
let linkEditor = me.up('pveCorosyncLinkEditor');
if (!form.validating) {
// avoid recursion/double validation by setting temporary states
curField.validating = true;
form.validating = true;
// validate all other fields as well, to always mark both
// parties involved in a 'duplicate' error
form.isValid();
form.validating = false;
curField.validating = false;
} else if (curField.validating) {
// we'll be validated by the original call in the other
// if-branch, avoid double work
return true;
}
if (val === undefined || (val instanceof String && val.length === 0)) {
// let this be caught by allowBlank, if at all
return true;
}
let allFields = linkEditor.query(queryString);
let err = undefined;
Ext.Array.each(allFields, field => {
if (field != curField && field.getValue() == val) {
err = errorMsg;
return false; // break
}
});
return err || true;
};
}
});
Ext.define('PVE.form.CorosyncLinkEditor', {
extend: 'Ext.panel.Panel',
xtype: 'pveCorosyncLinkEditor',
controller: 'pveCorosyncLinkEditorController',
// only initial config, use setter otherwise
allowNumberEdit: true,
viewModel: {
data: {
linkCount: 0,
maxLinkCount: 8,
networks: null,
allowNumberEdit: true,
infoText: ''
},
formulas: {
addDisabled: function(get) {
return !get('allowNumberEdit') ||
get('linkCount') >= get('maxLinkCount');
},
dockHidden: function(get) {
return !(get('allowNumberEdit') || get('infoText'));
}
}
},
dockedItems: [{
xtype: 'toolbar',
dock: 'bottom',
defaultButtonUI : 'default',
border: false,
padding: '6 0 6 0',
bind: {
hidden: '{dockHidden}'
},
items: [
{
xtype: 'button',
text: gettext('Add'),
bind: {
disabled: '{addDisabled}',
hidden: '{!allowNumberEdit}'
},
handler: 'addEmptyLink'
},
{
xtype: 'label',
bind: {
text: '{infoText}'
}
}
]
}],
setInfoText: function(text) {
let me = this;
let vm = me.getViewModel();
vm.set('infoText', text || '');
},
setLinks: function(links) {
let me = this;
let controller = me.getController();
let vm = me.getViewModel();
me.removeAll();
vm.set('linkCount', 0);
Ext.Array.each(links, link => controller.addLink(link));
},
setDefaultLinks: function() {
let me = this;
let controller = me.getController();
let vm = me.getViewModel();
me.removeAll();
vm.set('linkCount', 0);
controller.addLink();
},
// clears all links
setAllowNumberEdit: function(allow) {
let me = this;
let vm = me.getViewModel();
vm.set('allowNumberEdit', allow);
me.removeAll();
vm.set('linkCount', 0);
},
items: [{
// No links is never a valid scenario, but can occur during a slow load
xtype: 'hiddenfield',
submitValue: false,
isValid: function() {
let me = this;
let vm = me.up('pveCorosyncLinkEditor').getViewModel();
return vm.get('linkCount') > 0;
}
}],
initComponent: function() {
let me = this;
let vm = me.getViewModel();
let controller = me.getController();
vm.set('allowNumberEdit', me.allowNumberEdit);
vm.set('infoText', me.infoText || '');
me.callParent();
// Request local node networks to pre-populate first link.
Proxmox.Utils.API2Request({
url: '/nodes/localhost/network',
method: 'GET',
waitMsgTarget: me,
success: response => {
let data = response.result.data;
if (data.length > 0) {
data.sort((a, b) => a.iface.localeCompare(b.iface));
let addresses = [];
for (let net of data) {
if (net.address) {
addresses.push(net.address);
}
if (net.address6) {
addresses.push(net.address6);
}
}
vm.set('networks', addresses);
}
// Always have at least one link, but account for delay in API,
// someone might have called 'setLinks' in the meantime -
// except if 'allowNumberEdit' is false, in which case we're
// probably waiting for the user to input the join info
if (vm.get('allowNumberEdit')) {
controller.addLinkIfEmpty();
}
},
failure: () => {
if (vm.get('allowNumberEdit')) {
controller.addLinkIfEmpty();
}
}
});
}
});