mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-06-12 03:36:37 +00:00

CorosyncLinkEdit is a Panel that contains between one and 8 CorosyncLinkSelectors. These can be added or removed with according buttons. Values submitted to the API are calculated by each ProxmoxNetworkSelector itself. This works because ExtJS searches recursively through all child components for ones with a value to be submitted, i.e. the CorosyncLinkEdit and CorosyncLinkSelector components are not part of data submission at all. Change ClusterEdit.js to use the new component for cluster join and create. To make space in layout, move 'password' field to the side (where the network-selector previously was) and use 'hbox' panel for horizontal layouting to avoid spacing issues with languages where the fieldLabel doesn't fit on one line. Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
366 lines
7.9 KiB
JavaScript
366 lines
7.9 KiB
JavaScript
/*jslint confusion: true*/
|
|
Ext.define('PVE.ClusterCreateWindow', {
|
|
extend: 'Proxmox.window.Edit',
|
|
xtype: 'pveClusterCreateWindow',
|
|
|
|
title: gettext('Create Cluster'),
|
|
width: 600,
|
|
|
|
method: 'POST',
|
|
url: '/cluster/config',
|
|
|
|
isCreate: true,
|
|
subject: gettext('Cluster'),
|
|
showTaskViewer: true,
|
|
|
|
onlineHelp: 'pvecm_create_cluster',
|
|
|
|
items: {
|
|
xtype: 'inputpanel',
|
|
items: [{
|
|
xtype: 'textfield',
|
|
fieldLabel: gettext('Cluster Name'),
|
|
allowBlank: false,
|
|
maxLength: 15,
|
|
name: 'clustername'
|
|
},
|
|
{
|
|
xtype: 'fieldcontainer',
|
|
fieldLabel: gettext("Cluster Links"),
|
|
style: {
|
|
'padding-top': '5px',
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'pveCorosyncLinkEditor',
|
|
style: {
|
|
'padding-bottom': '5px',
|
|
},
|
|
name: 'links'
|
|
},
|
|
{
|
|
xtype: 'label',
|
|
text: gettext("Multiple links are used as failover, lower numbers have higher priority.")
|
|
}
|
|
]
|
|
}]
|
|
}
|
|
});
|
|
|
|
Ext.define('PVE.ClusterInfoWindow', {
|
|
extend: 'Ext.window.Window',
|
|
xtype: 'pveClusterInfoWindow',
|
|
mixins: ['Proxmox.Mixin.CBind'],
|
|
|
|
width: 800,
|
|
modal: true,
|
|
resizable: false,
|
|
title: gettext('Cluster Join Information'),
|
|
|
|
joinInfo: {
|
|
ipAddress: undefined,
|
|
fingerprint: undefined,
|
|
totem: {}
|
|
},
|
|
|
|
items: [
|
|
{
|
|
xtype: 'component',
|
|
border: false,
|
|
padding: '10 10 10 10',
|
|
html: gettext("Copy the Join Information here and use it on the node you want to add.")
|
|
},
|
|
{
|
|
xtype: 'container',
|
|
layout: 'form',
|
|
border: false,
|
|
padding: '0 10 10 10',
|
|
items: [
|
|
{
|
|
xtype: 'textfield',
|
|
fieldLabel: gettext('IP Address'),
|
|
cbind: { value: '{joinInfo.ipAddress}' },
|
|
editable: false
|
|
},
|
|
{
|
|
xtype: 'textfield',
|
|
fieldLabel: gettext('Fingerprint'),
|
|
cbind: { value: '{joinInfo.fingerprint}' },
|
|
editable: false
|
|
},
|
|
{
|
|
xtype: 'textarea',
|
|
inputId: 'pveSerializedClusterInfo',
|
|
fieldLabel: gettext('Join Information'),
|
|
grow: true,
|
|
cbind: { joinInfo: '{joinInfo}' },
|
|
editable: false,
|
|
listeners: {
|
|
afterrender: function(field) {
|
|
if (!field.joinInfo) {
|
|
return;
|
|
}
|
|
var jsons = Ext.JSON.encode(field.joinInfo);
|
|
var base64s = Ext.util.Base64.encode(jsons);
|
|
field.setValue(base64s);
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
],
|
|
dockedItems: [{
|
|
dock: 'bottom',
|
|
xtype: 'toolbar',
|
|
items: [{
|
|
xtype: 'button',
|
|
handler: function(b) {
|
|
var el = document.getElementById('pveSerializedClusterInfo');
|
|
el.select();
|
|
document.execCommand("copy");
|
|
},
|
|
text: gettext('Copy Information')
|
|
}]
|
|
}]
|
|
});
|
|
|
|
Ext.define('PVE.ClusterJoinNodeWindow', {
|
|
extend: 'Proxmox.window.Edit',
|
|
xtype: 'pveClusterJoinNodeWindow',
|
|
|
|
title: gettext('Cluster Join'),
|
|
width: 800,
|
|
|
|
method: 'POST',
|
|
url: '/cluster/config/join',
|
|
|
|
defaultFocus: 'textarea[name=serializedinfo]',
|
|
isCreate: true,
|
|
bind: {
|
|
submitText: '{submittxt}',
|
|
},
|
|
showTaskViewer: true,
|
|
|
|
onlineHelp: 'pvecm_join_node_to_cluster',
|
|
|
|
viewModel: {
|
|
parent: null,
|
|
data: {
|
|
info: {
|
|
fp: '',
|
|
ip: '',
|
|
clusterName: ''
|
|
}
|
|
},
|
|
formulas: {
|
|
submittxt: function(get) {
|
|
let cn = get('info.clusterName');
|
|
if (cn) {
|
|
return Ext.String.format(gettext('Join {0}'), `'${cn}'`);
|
|
}
|
|
return gettext('Join');
|
|
},
|
|
},
|
|
},
|
|
|
|
controller: {
|
|
xclass: 'Ext.app.ViewController',
|
|
control: {
|
|
'#': {
|
|
close: function() {
|
|
delete PVE.Utils.silenceAuthFailures;
|
|
}
|
|
},
|
|
'proxmoxcheckbox[name=assistedEntry]': {
|
|
change: 'onInputTypeChange'
|
|
},
|
|
'textarea[name=serializedinfo]': {
|
|
change: 'recomputeSerializedInfo',
|
|
enable: 'resetField'
|
|
},
|
|
'textfield': {
|
|
disable: 'resetField'
|
|
}
|
|
},
|
|
resetField: function(field) {
|
|
field.reset();
|
|
},
|
|
onInputTypeChange: function(field, assistedInput) {
|
|
let linkEditor = this.lookup('linkEditor');
|
|
|
|
// this also clears all links
|
|
linkEditor.setAllowNumberEdit(!assistedInput);
|
|
|
|
if (!assistedInput) {
|
|
linkEditor.setInfoText();
|
|
linkEditor.setDefaultLinks();
|
|
}
|
|
},
|
|
recomputeSerializedInfo: function(field, value) {
|
|
let vm = this.getViewModel();
|
|
|
|
let assistedEntryBox = this.lookup('assistedEntry');
|
|
if (!assistedEntryBox.getValue()) {
|
|
// not in assisted entry mode, nothing to do
|
|
return;
|
|
}
|
|
|
|
let linkEditor = this.lookup('linkEditor');
|
|
|
|
let jsons = Ext.util.Base64.decode(value);
|
|
let joinInfo = Ext.JSON.decode(jsons, true);
|
|
|
|
let info = {
|
|
fp: '',
|
|
ip: '',
|
|
clusterName: ''
|
|
};
|
|
|
|
if (!(joinInfo && joinInfo.totem)) {
|
|
field.valid = false;
|
|
linkEditor.setLinks([]);
|
|
linkEditor.setInfoText();
|
|
} else {
|
|
let interfaces = joinInfo.totem.interface;
|
|
let links = Object.values(interfaces).map(iface => {
|
|
return {
|
|
number: iface.linknumber,
|
|
value: '',
|
|
text: '',
|
|
allowBlank: false
|
|
};
|
|
});
|
|
|
|
linkEditor.setInfoText();
|
|
if (links.length == 1 && joinInfo.ring_addr !== undefined &&
|
|
joinInfo.ring_addr[0] === joinInfo.ipAddress) {
|
|
|
|
links[0].allowBlank = true;
|
|
linkEditor.setInfoText(gettext("Leave empty to use IP resolved by node's hostname"));
|
|
}
|
|
|
|
linkEditor.setLinks(links);
|
|
|
|
info = {
|
|
ip: joinInfo.ipAddress,
|
|
fp: joinInfo.fingerprint,
|
|
clusterName: joinInfo.totem.cluster_name
|
|
};
|
|
field.valid = true;
|
|
}
|
|
|
|
vm.set('info', info);
|
|
}
|
|
},
|
|
|
|
submit: function() {
|
|
// joining may produce temporarily auth failures, ignore as long the task runs
|
|
PVE.Utils.silenceAuthFailures = true;
|
|
this.callParent();
|
|
},
|
|
|
|
taskDone: function(success) {
|
|
delete PVE.Utils.silenceAuthFailures;
|
|
if (success) {
|
|
// reload always (if user wasn't faster), but wait a bit for pveproxy
|
|
Ext.defer(function() {
|
|
window.location.reload(true);
|
|
}, 5000);
|
|
var txt = gettext('Cluster join task finished, node certificate may have changed, reload GUI!');
|
|
// ensure user cannot do harm
|
|
Ext.getBody().mask(txt, ['pve-static-mask']);
|
|
// TaskView may hide above mask, so tell him directly
|
|
Ext.Msg.show({
|
|
title: gettext('Join Task Finished'),
|
|
icon: Ext.Msg.INFO,
|
|
msg: txt
|
|
});
|
|
}
|
|
},
|
|
|
|
items: [{
|
|
xtype: 'proxmoxcheckbox',
|
|
reference: 'assistedEntry',
|
|
name: 'assistedEntry',
|
|
itemId: 'assistedEntry',
|
|
submitValue: false,
|
|
value: true,
|
|
autoEl: {
|
|
tag: 'div',
|
|
'data-qtip': gettext('Select if join information should be extracted from pasted cluster information, deselect for manual entering')
|
|
},
|
|
boxLabel: gettext('Assisted join: Paste encoded cluster join information and enter password.')
|
|
},
|
|
{
|
|
xtype: 'textarea',
|
|
name: 'serializedinfo',
|
|
submitValue: false,
|
|
allowBlank: false,
|
|
fieldLabel: gettext('Information'),
|
|
emptyText: gettext('Paste encoded Cluster Information here'),
|
|
validator: function(val) {
|
|
return val === '' || this.valid ||
|
|
gettext('Does not seem like a valid encoded Cluster Information!');
|
|
},
|
|
bind: {
|
|
disabled: '{!assistedEntry.checked}',
|
|
hidden: '{!assistedEntry.checked}'
|
|
},
|
|
value: ''
|
|
},
|
|
{
|
|
xtype: 'panel',
|
|
width: 776,
|
|
layout: {
|
|
type: 'hbox',
|
|
align: 'center'
|
|
},
|
|
items: [
|
|
{
|
|
xtype: 'textfield',
|
|
flex: 1,
|
|
margin: '0 5px 0 0',
|
|
fieldLabel: gettext('Peer Address'),
|
|
allowBlank: false,
|
|
bind: {
|
|
value: '{info.ip}',
|
|
readOnly: '{assistedEntry.checked}'
|
|
},
|
|
name: 'hostname'
|
|
},
|
|
{
|
|
xtype: 'textfield',
|
|
flex: 1,
|
|
margin: '0 0 10px 5px',
|
|
inputType: 'password',
|
|
emptyText: gettext("Peer's root password"),
|
|
fieldLabel: gettext('Password'),
|
|
allowBlank: false,
|
|
name: 'password'
|
|
},
|
|
]
|
|
},
|
|
{
|
|
xtype: 'textfield',
|
|
fieldLabel: gettext('Fingerprint'),
|
|
allowBlank: false,
|
|
bind: {
|
|
value: '{info.fp}',
|
|
readOnly: '{assistedEntry.checked}'
|
|
},
|
|
name: 'fingerprint'
|
|
},
|
|
{
|
|
xtype: 'fieldcontainer',
|
|
fieldLabel: gettext("Cluster Links"),
|
|
items: [
|
|
{
|
|
xtype: 'pveCorosyncLinkEditor',
|
|
itemId: 'linkEditor',
|
|
reference: 'linkEditor',
|
|
allowNumberEdit: false
|
|
},
|
|
]
|
|
}]
|
|
});
|