[pve-devel] [PATCH WIP manager 2/2] gui: configure cluster-wide hosts through gui

Leo Nunner l.nunner at proxmox.com
Thu Sep 14 12:03:41 CEST 2023


GUI components for the cluster-wide hosts configuration. It is located
under Datacenter > Hosts, and allows editing, adding and deleting
entries. They are searchable, and the edit window allows adding a
variable number of hostnames to each IP address. There is a separate
"apply" button, which starts a task to sync the hosts config to each
node in the cluster.

Signed-off-by: Leo Nunner <l.nunner at proxmox.com>
---
 www/manager6/Makefile     |   1 +
 www/manager6/dc/Config.js |   6 +
 www/manager6/dc/Hosts.js  | 257 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 264 insertions(+)
 create mode 100644 www/manager6/dc/Hosts.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 59a5d8a7f..f34d5b2f2 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -157,6 +157,7 @@ JSSRC= 							\
 	dc/GroupView.js					\
 	dc/Guests.js					\
 	dc/Health.js					\
+	dc/Hosts.js					\
 	dc/Log.js					\
 	dc/NodeView.js					\
 	dc/NotificationEvents.js			\
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 9ba7b301f..332662516 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -178,6 +178,12 @@ Ext.define('PVE.dc.Config', {
 		iconCls: 'fa fa-bolt',
 		xtype: 'pveFencingView',
 		itemId: 'ha-fencing',
+	    },
+	    {
+		xtype: 'pveClusterHosts',
+		title: gettext('Hosts'),
+		iconCls: 'fa fa-globe',
+		itemId: 'hosts',
 	    });
 	    // always show on initial load, will be hiddea later if the SDN API calls don't exist,
 	    // else it won't be shown at first if the user initially loads with DC selected
diff --git a/www/manager6/dc/Hosts.js b/www/manager6/dc/Hosts.js
new file mode 100644
index 000000000..57bf29054
--- /dev/null
+++ b/www/manager6/dc/Hosts.js
@@ -0,0 +1,257 @@
+Ext.define('pve-etc-hosts-entry', {
+    extend: 'Ext.data.Model',
+    fields: [
+	{ name: 'ip', type: 'string' },
+	{ name: 'hosts', type: 'string' },
+	{ name: 'comment', type: 'string' },
+    ],
+});
+
+Ext.define('PVE.dc.HostsEditWindow', {
+    extend: 'Proxmox.window.Edit',
+
+    width: 600,
+
+    ip: undefined,
+
+    initComponent: function() {
+	var me = this;
+
+	Ext.apply(me, {
+	    subject: "Hosts entry",
+	    defaultFocus: 'textfield[name=ip]',
+	});
+
+	Ext.apply(me, {
+	    items: [
+		{
+		    xtype: 'textfield',
+		    fieldLabel: gettext('IP'),
+		    name: 'ip',
+		    dataIndex: 'ip',
+		    allowBlank: false,
+		    vtype: 'IP64Address',
+		},
+		{
+		    xtype: 'textfield',
+		    fieldLabel: gettext('Comment'),
+		    name: 'comment',
+		    dataIndex: 'comment',
+		    allowBlank: true,
+		},
+		{
+		    xtype: 'fieldcontainer',
+		    fieldLabel: gettext('Hostnames'),
+		    items: [
+			{
+			    xtype: 'proxmoxHostsEditPanel', name: 'hosts',
+			},
+		    ],
+		},
+	    ],
+	});
+
+	let base_url = `/api2/extjs/cluster/hosts`;
+	if (me.isCreate) {
+            me.url = base_url;
+            me.method = 'POST';
+        } else {
+            me.url = base_url + `/${me.ip}`;
+            me.method = 'PUT';
+        }
+
+	me.callParent();
+
+	let hostsPanel = me.down("proxmoxHostsEditPanel");
+	let hostsController = hostsPanel.getController();
+
+	me.setValues({ ip: me.ip });
+
+	if (!me.isCreate) { // do we already have data?
+	    me.load();
+	} else { // if not, we create a single empty host entry
+	    hostsController.addHost();
+	}
+    },
+});
+
+Ext.define('PVE.dc.ClusterHosts', {
+    extend: 'Ext.grid.Panel',
+    xtype: 'pveClusterHosts',
+
+    reload: function() {
+	let me = this;
+	let view = me.getView();
+	view.store.load();
+    },
+
+    layout: 'fit',
+
+    initComponent: function() {
+	let me = this;
+
+	me.store = Ext.create('Ext.data.Store', {
+	    model: 'pve-etc-hosts-entry',
+	    proxy: {
+                type: 'proxmox',
+                url: `/api2/json/cluster/hosts`,
+	    },
+	    sorters: [
+		{
+		    property: 'ip',
+		    direction: 'ASC',
+		},
+	    ],
+	});
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+	var run_editor = function() {
+	    var rec = sm.getSelection()[0];
+	    if (!rec || !(rec.data.ip || rec.data.hosts)) {
+		return;
+	    }
+
+	    let win = Ext.create('PVE.dc.HostsEditWindow', {
+		isCreate: false,
+		ip: rec.data.ip,
+	    });
+	    win.on('destroy', me.reload, me);
+	    win.show();
+	};
+
+	Ext.apply(me, {
+	    tbar: [
+		{
+		    text: gettext('Apply'),
+		    itemId: 'applybtn',
+		    handler: function() {
+			Proxmox.Utils.API2Request({
+			    method: 'PUT',
+			    url: `/cluster/hosts`,
+			    waitMsgTarget: me,
+			    success: function(response, opts) {
+				me.reload();
+			    },
+			    failure: function(response, opts) {
+				Ext.Msg.alert('Error', response.htmlStatus);
+			    },
+			});
+		    },
+		},
+		'|',
+		{
+		    text: gettext('Add'),
+		    itemId: 'addbtn',
+		    handler: function() {
+			let win = Ext.create('PVE.dc.HostsEditWindow', {
+			    isCreate: true,
+			    ip: undefined,
+			});
+			win.on('destroy', me.reload, me);
+			win.show();
+		    },
+		},
+		{
+		    text: gettext('Edit'),
+		    itemId: 'editbtn',
+		    handler: run_editor,
+		},
+		{
+		    text: gettext('Delete'),
+		    itemId: 'deletebtn',
+		    handler: function() {
+			let rec = sm.getSelection()[0];
+
+			Proxmox.Utils.API2Request({
+			    method: 'DELETE',
+			    url: `/cluster/hosts/${rec.data.ip}`,
+			    waitMsgTarget: me,
+			    success: function(response, opts) {
+				me.reload();
+			    },
+			    failure: function(response, opts) {
+				Ext.Msg.alert('Error', response.htmlStatus);
+			    },
+			});
+		    },
+		},
+		'->',
+		gettext('Search') + ':',
+		' ',
+		{
+		    xtype: 'textfield',
+		    width: 200,
+		    enableKeyEvents: true,
+		    emptyText: gettext("IP, FQDN"),
+		    listeners: {
+			keyup: {
+			    buffer: 500,
+			    fn: function(field) {
+				let needle = field.getValue().toLocaleLowerCase();
+				me.store.clearFilter(true);
+				me.store.filter([
+				    {
+					filterFn: ({ data }) =>
+					data.ip?.toLocaleLowerCase().includes(needle) ||
+					data.hosts?.toLocaleLowerCase().includes(needle),
+				    },
+				]);
+			    },
+			},
+			change: function(field, newValue, oldValue) {
+			    if (newValue !== this.originalValue) {
+				this.triggers.clear.setVisible(true);
+			    }
+			},
+		    },
+		    triggers: {
+			clear: {
+			    cls: 'pmx-clear-trigger',
+			    weight: -1,
+			    hidden: true,
+			    handler: function() {
+				this.triggers.clear.setVisible(false);
+				this.setValue(this.originalValue);
+				me.store.clearFilter();
+			    },
+			},
+		    },
+		},
+	    ],
+	});
+
+	Ext.apply(me, {
+	    bodystyle: {
+		width: '100% !important',
+	    },
+	    columns: [
+		{
+		    text: gettext('IP'),
+		    dataIndex: 'ip',
+		    width: 150,
+		},
+		{
+		    text: gettext('Hosts'),
+		    dataIndex: 'hosts',
+		    flex: 1,
+		},
+		{
+		    text: gettext('Comment'),
+		    dataIndex: 'comment',
+		    flex: 1,
+		},
+	    ],
+	});
+
+	Ext.apply(me, {
+	    selModel: sm,
+	    listeners: {
+		itemdblclick: run_editor,
+	    },
+	});
+
+	me.callParent();
+	me.reload();
+    },
+});
-- 
2.39.2






More information about the pve-devel mailing list