[pve-devel] [WIP manager] WIP: dc/cluster ui for cluster join, create and information

Thomas Lamprecht t.lamprecht at proxmox.com
Thu Feb 8 18:35:36 CET 2018


This is a WIP mock up of a possible cluster management component for
our WebUI. It's *not* final, and not intended for review - I mean if
you see something obvious wrong it won't hurt to tell.

I've plans to improve visuals still a bit, making a clear tab
separation between fully manual, and assisted join.

The assisted join should get some additional displayed info.
I.e., cluster name we're joining, its IP type, how many rings it has
configured, ...

I sent this mainly to make testing the backend part, using the API
only, easier.

Oh and I found out that i missed adding

protected => 1,

For the cluster create API call (POST /cluster/config) in v6 of the
backend part, easy to fix for testing this, though, so waiting with a
v7 :)

cheers,


Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---
 www/manager6/Makefile      |   1 +
 www/manager6/dc/Cluster.js | 456 +++++++++++++++++++++++++++++++++++++++++++++
 www/manager6/dc/Config.js  |  13 +-
 3 files changed, 466 insertions(+), 4 deletions(-)
 create mode 100644 www/manager6/dc/Cluster.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index aec62613..e2145085 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -187,6 +187,7 @@ JSSRC= 				                 	\
 	dc/SecurityGroups.js				\
 	dc/Config.js					\
 	dc/NodeView.js					\
+	dc/Cluster.js					\
 	Workspace.js
 
 lint: ${JSSRC}
diff --git a/www/manager6/dc/Cluster.js b/www/manager6/dc/Cluster.js
new file mode 100644
index 00000000..40c1e07b
--- /dev/null
+++ b/www/manager6/dc/Cluster.js
@@ -0,0 +1,456 @@
+/*jslint confusion: true*/
+Ext.define('pve-cluster-nodes', {
+    extend: 'Ext.data.Model',
+    fields: [
+	'node', { type: 'integer', name: 'nodeid' }, 'ring0_addr', 'ring1_addr',
+	{ type: 'integer', name: 'quorum_votes' }
+    ],
+    proxy: {
+        type: 'proxmox',
+	url: "/api2/json/cluster/config/nodes"
+    },
+    idProperty: 'nodeid'
+});
+
+Ext.define('pve-cluster-info', {
+    extend: 'Ext.data.Model',
+    proxy: {
+        type: 'proxmox',
+	url: "/api2/json/cluster/config/join"
+    }
+});
+
+Ext.define('PVE.ClusterCreateWindow', {
+    extend: 'Proxmox.window.Edit',
+    xtype: 'pveClusterCreateWindow',
+
+    title: gettext('Create Cluster'),
+    width: 800,
+
+    method: 'POST',
+    url: '/cluster/config',
+
+    isCreate: true,
+    subject: gettext('Cluster'),
+    showProgress: true,
+
+    items: [
+	{
+	    xtype: 'textfield',
+	    fieldLabel: gettext('Cluster Name'),
+	    name: 'clustername'
+	},
+	{
+	    xtype: 'proxmoxtextfield',
+	    fieldLabel: gettext('Ring 0 Address'),
+	    emptyText: gettext("IP resolved by node's hostname"),
+	    name: 'ring0_addr',
+	    skipEmptyText: true
+	}
+    ]
+});
+
+Ext.define('PVE.ClusterJoinNodeWindow', {
+    extend: 'Proxmox.window.Edit',
+    xtype: 'pveClusterJoinNodeWindow',
+
+    title: gettext('Cluster Join'),
+    width: 800,
+
+    method: 'POST',
+    url: '/cluster/config/join',
+
+    isCreate: true,
+    submitText: gettext('Join'),
+
+    viewModel: {
+	parent: null,
+	data: {
+	    peerIP: '',
+	    peerFP: '',
+	    ringsNeeded: 1,
+	    ipVersion: 'any',
+	    useSerializedInfo: true
+	}
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+	control: {
+	    'radiofield[name=inputtype]': {
+		change: 'onInputTypeChange'
+	    },
+	    'textarea[name=serializedinfo]': {
+		change: 'recomputeSerializedInfo',
+		disable: 'clearOnDisable'
+	    },
+	    'proxmoxtextfield': {
+		disable: 'clearOnDisable'
+	    },
+	    'textfield': {
+		disable: 'clearOnDisable'
+	    }
+	},
+	clearOnDisable: function(field) {
+	    field.reset();
+	},
+	onInputTypeChange: function(field, value) {
+	    var vm = this.getViewModel();
+	    var auto = (field.inputValue === 'auto');
+	    vm.set('useSerializedInfo', !auto);
+	},
+	recomputeSerializedInfo: function(field, value) {
+	    var jsons = Ext.util.Base64.decode(value);
+	    var joinInfo = Ext.JSON.decode(jsons, true);
+
+	    if (joinInfo === null) {
+		return;
+	    }
+
+	    var vm = this.getViewModel();
+	    vm.set('peerIP', joinInfo.ipAddress);
+	    vm.set('peerFP', joinInfo.fingerprint);
+	}
+    },
+
+    items: [{
+	xtype: 'inputpanel',
+	column1: [
+	    {
+		xtype: 'radiofield',
+		name: 'inputtype',
+		submitValue: false,
+		inputValue: 'auto',
+		boxLabel: gettext('Use encoded join information'),
+		checked: true
+	    },
+	    {
+		xtype: 'textfield',
+		fieldLabel: gettext('Peer Address'),
+		allowBlank: false,
+		bind: {
+		    value: '{peerIP}',
+		    disabled: '{useSerializedInfo}'
+		},
+		name: 'hostname'
+	    },
+	    {
+		xtype: 'textfield',
+		inputType: 'password',
+		fieldLabel: gettext('Password'),
+		allowBlank: false,
+		name: 'password'
+	    }
+	],
+	column2: [
+	    {
+		xtype: 'radiofield',
+		name: 'inputtype',
+		inputValue: 'manual',
+		submitValue: false,
+		boxLabel: gettext('Manual edit Join Information')
+	    },
+	    {
+		xtype: 'proxmoxtextfield',
+		fieldLabel: gettext('Corosync Ring 0'),
+		emptyText: gettext("IP resolved by node's hostname"),
+		skipEmptyText: true,
+		name: 'ring0_addr'
+	    },
+	    {
+		xtype: 'proxmoxtextfield',
+		fieldLabel: gettext('Corosync Ring 1'),
+		emptyText: gettext("Optional second ring"),
+		skipEmptyText: true,
+		bind: {
+		    disabled: '{useSerializedInfo}'
+		},
+		name: 'ring1_addr'
+	    }
+	],
+	columnB: [
+	    {
+		xtype: 'textfield',
+		fieldLabel: gettext('Fingerprint'),
+		allowBlank: false,
+		bind: {
+		    value: '{peerFP}',
+		    disabled: '{useSerializedInfo}'
+		},
+		name: 'fingerprint'
+	    },
+	    {
+		xtype: 'textarea',
+		name: 'serializedinfo',
+		submitValue: false,
+		fieldLabel: gettext('Information'),
+		emptyText: 'Enter encoded Cluster Information',
+		bind: {
+		    disabled: '{!useSerializedInfo}'
+		},
+		value: ''
+	    }
+	]
+    }]
+});
+
+Ext.define('PVE.ClusterInfoWindow', {
+    extend: 'Ext.window.Window',
+    xtype: 'pveClusterInfoWindow',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    width: 800,
+    modal: true,
+    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('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')
+	}]
+    }]
+});
+
+/* bind is a function and object */
+Ext.define('PVE.ClusterAdministration', {
+    extend: 'Ext.panel.Panel',
+    xtype: 'pveClusterAdministration',
+
+    title: gettext('Cluster Administration'),
+
+    border: false,
+    defaults: { border: false },
+
+    viewModel: {
+	parent: null,
+	data: {
+	    totem: {},
+	    nodelist: [],
+	    preferred_node: {
+		name: '',
+		fp: '',
+		addr: ''
+	    },
+	    nodecount: 0
+	}
+    },
+
+    items: [
+	{
+	    xtype: 'grid',
+	    title: gettext('Cluster Information'),
+	    controller: {
+		xclass: 'Ext.app.ViewController',
+
+		init: function(view) {
+		    view.store.on('load', this.onLoad, this);
+		    //PVE.Utils.monStoreErrors(view, view.getStore());
+		},
+
+		onLoad: function(store, records, success) {
+		    if (!success || !records || !records[0].data) {
+			return;
+		    }
+		    var vm = this.getViewModel();
+		    var data = records[0].data;
+		    vm.set('totem', data.totem);
+		    vm.set('nodelist', data.nodelist);
+
+		    var nodeinfo = Ext.Array.findBy(data.nodelist, function (el) {
+			return el.name === data.preferred_node;
+		    });
+
+		    vm.set('preferred_node', {
+			name: data.preferred_node,
+			addr: nodeinfo.pve_addr,
+			fp: nodeinfo.pve_fp
+		    });
+		},
+
+		onCreate: function() {
+		    var win = Ext.create('PVE.ClusterCreateWindow', {});
+		    win.show();
+		},
+
+		onJoin: function() {
+		    var win = Ext.create('PVE.ClusterJoinNodeWindow', {});
+		    win.show();
+		    win.on('destroy', function() {
+			// fixme: logout
+		    });
+		},
+
+		onClusterInfo: function() {
+		    var vm = this.getViewModel();
+		    var win = Ext.create('PVE.ClusterInfoWindow', {
+			joinInfo: {
+			    ipAddress: vm.get('preferred_node.addr'),
+			    fingerprint: vm.get('preferred_node.fp'),
+			    totem: vm.get('totem')
+			}
+		    });
+		    win.show();
+		}
+	    },
+	    store: {
+		autoLoad: true,
+		model: 'pve-cluster-info'
+	    },
+	    tbar: [
+		{
+		    text: gettext('Create'),
+		    reference: 'createButton',
+		    handler: 'onCreate',
+		    bind: {
+			disabled: '{totem.cluster_name}'
+		    }
+		},
+		{
+		    text: gettext('Cluster Information'),
+		    reference: 'addButton',
+		    handler: 'onClusterInfo',
+		    bind: {
+			disabled: '{!nodecount}'
+		    }
+		},
+		{
+		    text: gettext('Join'),
+		    reference: 'joinButton',
+		    handler: 'onJoin',
+		    bind: {
+			disabled: '{totem.cluster_name}'
+		    }
+		}
+	    ],
+	    columns: [
+		{
+		    header: gettext('TODO'),
+		    flex: 1,
+		    dataIndex: 'cluster_name'
+		}
+	    ]
+	},
+	{
+	    xtype: 'grid',
+	    title: gettext('Nodes'),
+	    controller: {
+		xclass: 'Ext.app.ViewController',
+
+		init: function(view) {
+		    view.store.on('load', this.onLoad, this);
+		    //Proxmox.Utils.monStoreErrors(view, view.getStore());
+		},
+
+		onLoad: function(store, records, success) {
+		    var vm = this.getViewModel();
+		    if (!success || !records) {
+			return;
+		    }
+		    vm.set('nodecount', records.length);
+		}
+	    },
+	    store: {
+		autoLoad: true,
+		model: 'pve-cluster-nodes'
+	    },
+	    tbar: [
+		{
+		    text: gettext('Isolate Node'),
+		    reference: 'isolateButton',
+		    disabled: true
+		    //bind: {
+			//disabled: '{!nodecount}'
+		    //}
+		    //handler: alert('TODO')
+		}
+	    ],
+	    columns: [
+		{
+		    header: gettext('Nodename'),
+		    width: 150,
+		    dataIndex: 'name'
+		},
+		{
+		    header: gettext('ID'),
+		    width: 80,
+		    dataIndex: 'nodeid'
+		},
+		{
+		    header: gettext('Votes'),
+		    width: 80,
+		    dataIndex: 'quorum_votes'
+		},
+		{
+		    // FIXME
+		    header: gettext('Ring 0'),
+		    width: 150,
+		    dataIndex: 'ring0_addr'
+		},
+		{
+		    header: gettext('Ring 1'),
+		    width: 150,
+		    dataIndex: 'ring1_addr'
+		}
+	    ]
+	}
+    ]
+});
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 17d7a96a..e574f5db 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -22,13 +22,18 @@ Ext.define('PVE.dc.Config', {
 
 	if (caps.dc['Sys.Audit']) {
 	    me.items.push({
-	    title: gettext('Summary'),
+		title: gettext('Summary'),
 		xtype: 'pveDcSummary',
 		iconCls: 'fa fa-book',
 		itemId: 'summary'
-	    });
-
-	    me.items.push({
+	    },
+	    {
+		title: gettext('Cluster'),
+		xtype: 'pveClusterAdministration',
+		iconCls: 'fa fa-server',
+		itemId: 'cluster'
+	    },
+	    {
 		xtype: 'pveDcOptionView',
 		title: gettext('Options'),
 		iconCls: 'fa fa-gear',
-- 
2.14.2





More information about the pve-devel mailing list