From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <pve-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 64ACB1FF164 for <inbox@lore.proxmox.com>; Fri, 28 Mar 2025 18:16:34 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4CF298E7D; Fri, 28 Mar 2025 18:14:15 +0100 (CET) From: Gabriel Goller <g.goller@proxmox.com> To: pve-devel@lists.proxmox.com Date: Fri, 28 Mar 2025 18:13:37 +0100 Message-Id: <20250328171340.885413-50-g.goller@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250328171340.885413-1-g.goller@proxmox.com> References: <20250328171340.885413-1-g.goller@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -0.025 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH pve-manager 5/7] fabrics: add NodeEdit components X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/> List-Post: <mailto:pve-devel@lists.proxmox.com> List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Proxmox VE development discussion <pve-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" <pve-devel-bounces@lists.proxmox.com> Add NodeEdit components for OpenFabric and OSPF. These allow creation and editing of Nodes and their interfaces. Shows an interface-panel with all the available and configured interfaces. If a node is not available but it is configured, it can still be viewed (we also show a warning hint). Signed-off-by: Gabriel Goller <g.goller@proxmox.com> Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com> --- www/manager6/Makefile | 2 + .../sdn/fabrics/openfabric/NodeEdit.js | 205 +++++++++++++++++ www/manager6/sdn/fabrics/ospf/NodeEdit.js | 207 ++++++++++++++++++ 3 files changed, 414 insertions(+) create mode 100644 www/manager6/sdn/fabrics/openfabric/NodeEdit.js create mode 100644 www/manager6/sdn/fabrics/ospf/NodeEdit.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 04f00b270fba..7ed2839d9557 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -305,8 +305,10 @@ JSSRC= \ sdn/zones/VxlanEdit.js \ sdn/fabrics/Common.js \ sdn/fabrics/openfabric/FabricEdit.js \ + sdn/fabrics/openfabric/NodeEdit.js \ sdn/fabrics/openfabric/InterfaceEdit.js \ sdn/fabrics/ospf/FabricEdit.js \ + sdn/fabrics/ospf/NodeEdit.js \ sdn/fabrics/ospf/InterfaceEdit.js \ storage/ContentView.js \ storage/BackupView.js \ diff --git a/www/manager6/sdn/fabrics/openfabric/NodeEdit.js b/www/manager6/sdn/fabrics/openfabric/NodeEdit.js new file mode 100644 index 000000000000..f2d204c22542 --- /dev/null +++ b/www/manager6/sdn/fabrics/openfabric/NodeEdit.js @@ -0,0 +1,205 @@ +Ext.define('PVE.sdn.Fabric.OpenFabric.Node.InputPanel', { + extend: 'Proxmox.panel.InputPanel', + + viewModel: {}, + + isCreate: undefined, + loadClusterInterfaces: undefined, + + interface_selector: undefined, + node_not_accessible_warning: undefined, + + onSetValues: function(values) { + let me = this; + me.interface_selector.setNetworkInterfaces(values.network_interfaces); + if (values.node) { + // this means we are in edit mode and we have a config + me.interface_selector.addInterfaces(values.node.interface); + me.interface_selector.updateSelectedInterfaces(values.node.interface); + me.interface_selector.originalValue = values.node.interface; + return { + node: values.node.node_id.split("_")[1], + router_id: values.node.router_id, + interfaces: values.node.interface, + }; + } else if (values.router_id) { + // if only a single router_id is set, we only want to update + // that parameter, without reloading the interface panel. + return { router_id: values.router_id }; + } else { + // this means we are in create mode, so don't select any interfaces + me.interface_selector.addInterfaces(null); + me.interface_selector.updateSelectedInterfaces(null); + return {}; + } + }, + + initComponent: function() { + let me = this; + me.interface_selector = Ext.create('PVE.sdn.Fabric.OpenFabric.InterfacePanel', { + name: 'interfaces', + parentClass: me.isCreate ? me : undefined, + }); + me.items = [ + { + xtype: 'pveNodeSelector', + reference: 'nodeselector', + fieldLabel: gettext('Node'), + labelWidth: 120, + name: 'node', + allowBlank: false, + disabled: !me.isCreate, + onlineValidator: me.isCreate, + autoSelect: me.isCreate, + listeners: { + change: function(f, value) { + if (me.isCreate) { + me.loadClusterInterfaces(value, (result) => { + me.setValues({ network_interfaces: result }); + }); + } + }, + }, + listConfig: { + columns: [ + { + header: gettext('Node'), + dataIndex: 'node', + sortable: true, + hideable: false, + flex: 1, + }, + ], + }, + + }, + me.node_not_accessible_warning, + { + xtype: 'textfield', + fieldLabel: gettext('Loopback IP'), + labelWidth: 120, + name: 'router_id', + allowBlank: false, + }, + me.interface_selector, + ]; + me.callParent(); + }, +}); + +Ext.define('PVE.sdn.Fabric.OpenFabric.Node.Edit', { + extend: 'Proxmox.window.Edit', + xtype: 'pveSDNFabricAddNode', + + width: 800, + + url: '/cluster/sdn/fabrics/openfabric', + + onlineHelp: 'pvesdn_openfabric_fabric', + + isCreate: undefined, + + controller: { + xclass: 'Ext.app.ViewController', + }, + + + submitUrl: function(url, values) { + let me = this; + return `${me.url}/${me.extraRequestParams.fabric}/node/${values.node}`; + }, + + loadClusterInterfaces: function(node, onSuccess) { + Proxmox.Utils.API2Request({ + url: `/api2/extjs/nodes/${node}/network`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + // No failure callback because this api call can't fail, it + // just hangs the request :) (if the node doesn't exist it gets proxied) + }); + }, + loadFabricInterfaces: function(fabric, node, onSuccess, onFailure) { + Proxmox.Utils.API2Request({ + url: `/cluster/sdn/fabrics/openfabric/${fabric}/node/${node}`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + failure: onFailure, + }); + }, + loadAllAvailableNodes: function(onSuccess) { + Proxmox.Utils.API2Request({ + url: `/cluster/config/nodes`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + }); + }, + + initComponent: function() { + let me = this; + + me.node_not_accessible_warning = Ext.create('Proxmox.form.field.DisplayEdit', { + userCls: 'pmx-hint', + value: gettext('The node is not accessible.'), + hidden: true, + }); + + let ipanel = Ext.create('PVE.sdn.Fabric.OpenFabric.Node.InputPanel', { + node_not_accessible_warning: me.node_not_accessible_warning, + isCreate: me.isCreate, + loadClusterInterfaces: me.loadClusterInterfaces, + }); + + Ext.apply(me, { + subject: gettext('Node'), + items: [ipanel], + }); + + me.callParent(); + + if (!me.isCreate) { + me.loadAllAvailableNodes((allNodes) => { + if (allNodes.some(i => i.name === me.node)) { + me.loadClusterInterfaces(me.node, (clusterResult) => { + me.loadFabricInterfaces(me.fabric, me.node, (fabricResult) => { + fabricResult.interface = fabricResult.interface + .map(i => PVE.Parser.parsePropertyString(i)); + + let data = { + node: fabricResult, + network_interfaces: clusterResult, + }; + + ipanel.setValues(data); + }); + }); + } else { + me.node_not_accessible_warning.setHidden(false); + // If the node is not currently in the cluster and not available (we can't get it's interfaces). + me.loadFabricInterfaces(me.fabric, me.node, (fabricResult) => { + fabricResult.interface = fabricResult.interface + .map(i => PVE.Parser.parsePropertyString(i)); + + let data = { + node: fabricResult, + }; + + ipanel.setValues(data); + }); + } + }); + } + + if (me.isCreate) { + me.method = 'POST'; + } else { + me.method = 'PUT'; + } + }, +}); + diff --git a/www/manager6/sdn/fabrics/ospf/NodeEdit.js b/www/manager6/sdn/fabrics/ospf/NodeEdit.js new file mode 100644 index 000000000000..d022272b5428 --- /dev/null +++ b/www/manager6/sdn/fabrics/ospf/NodeEdit.js @@ -0,0 +1,207 @@ +Ext.define('PVE.sdn.Fabric.Ospf.Node.InputPanel', { + extend: 'Proxmox.panel.InputPanel', + + viewModel: {}, + + isCreate: undefined, + loadClusterInterfaces: undefined, + + interface_selector: undefined, + node_not_accessible_warning: undefined, + + onSetValues: function(values) { + let me = this; + me.interface_selector.setNetworkInterfaces(values.network_interfaces); + if (values.node) { + // this means we are in edit mode and we have a config + me.interface_selector.addInterfaces(values.node.interface); + me.interface_selector.updateSelectedInterfaces(values.node.interface); + me.interface_selector.originalValue = values.node.interface; + return { + node: values.node.node_id.split("_")[1], + router_id: values.node.router_id, + interfaces: values.node.interface, + }; + } else if (values.router_id) { + // if only a single router_id is set, we only want to update + // that parameter, without reloading the interface panel. + return { router_id: values.router_id }; + } else { + // this means we are in create mode, so don't select any interfaces + me.interface_selector.addInterfaces(null); + me.interface_selector.updateSelectedInterfaces(null); + return {}; + } + }, + + initComponent: function() { + let me = this; + + me.interface_selector = Ext.create('PVE.sdn.Fabric.Ospf.InterfacePanel', { + name: 'interfaces', + parentClass: me.isCreate ? me : undefined, + }); + + me.items = [ + { + xtype: 'pveNodeSelector', + reference: 'nodeselector', + fieldLabel: gettext('Node'), + labelWidth: 120, + name: 'node', + allowBlank: false, + disabled: !me.isCreate, + onlineValidator: me.isCreate, + autoSelect: me.isCreate, + listeners: { + change: function(f, value) { + if (me.isCreate) { + me.loadClusterInterfaces(value, (result) => { + me.setValues({ network_interfaces: result }); + }); + } + }, + }, + listConfig: { + columns: [ + { + header: gettext('Node'), + dataIndex: 'node', + sortable: true, + hideable: false, + flex: 1, + }, + ], + }, + + }, + me.node_not_accessible_warning, + { + xtype: 'textfield', + fieldLabel: gettext('Loopback IP'), + labelWidth: 120, + name: 'router_id', + allowBlank: false, + }, + me.interface_selector, + ]; + me.callParent(); + }, +}); + +Ext.define('PVE.sdn.Fabric.Ospf.Node.Edit', { + extend: 'Proxmox.window.Edit', + xtype: 'pveSDNFabricAddNode', + + width: 800, + + url: '/cluster/sdn/fabrics/ospf', + onlineHelp: 'pvesdn_ospf_node', + + + isCreate: undefined, + + controller: { + xclass: 'Ext.app.ViewController', + }, + + submitUrl: function(url, values) { + let me = this; + return `${me.url}/${me.extraRequestParams.fabric}/node/${values.node}`; + }, + + loadClusterInterfaces: function(node, onSuccess) { + Proxmox.Utils.API2Request({ + url: `/api2/extjs/nodes/${node}/network`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + // No failure callback because this api call can't fail, it + // just hangs the request :) (if the node doesn't exist it gets proxied) + }); + }, + loadFabricInterfaces: function(fabric, node, onSuccess, onFailure) { + Proxmox.Utils.API2Request({ + url: `/cluster/sdn/fabrics/ospf/${fabric}/node/${node}`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + failure: onFailure, + }); + }, + loadAllAvailableNodes: function(onSuccess) { + Proxmox.Utils.API2Request({ + url: `/cluster/config/nodes`, + method: 'GET', + success: function(response, _opts) { + onSuccess(response.result.data); + }, + }); + }, + + initComponent: function() { + let me = this; + + me.node_not_accessible_warning = Ext.create('Proxmox.form.field.DisplayEdit', { + userCls: 'pmx-hint', + value: gettext('The node is not accessible.'), + hidden: true, + }); + + + let ipanel = Ext.create('PVE.sdn.Fabric.Ospf.Node.InputPanel', { + interface_selector: me.interface_selector, + node_not_accessible_warning: me.node_not_accessible_warning, + isCreate: me.isCreate, + loadClusterInterfaces: me.loadClusterInterfaces, + }); + + Ext.apply(me, { + subject: gettext('Node'), + items: [ipanel], + }); + + me.callParent(); + + if (!me.isCreate) { + me.loadAllAvailableNodes((allNodes) => { + if (allNodes.some(i => i.name === me.node)) { + me.loadClusterInterfaces(me.node, (clusterResult) => { + me.loadFabricInterfaces(me.fabric, me.node, (fabricResult) => { + fabricResult.interface = fabricResult.interface + .map(i => PVE.Parser.parsePropertyString(i)); + + let data = { + node: fabricResult, + network_interfaces: clusterResult, + }; + + ipanel.setValues(data); + }); + }); + } else { + me.node_not_accessible_warning.setHidden(false); + // If the node is not currently in the cluster and not available (we can't get it's interfaces). + me.loadFabricInterfaces(me.fabric, me.node, (fabricResult) => { + fabricResult.interface = fabricResult.interface + .map(i => PVE.Parser.parsePropertyString(i)); + + let data = { + node: fabricResult, + }; + + ipanel.setValues(data); + }); + } + }); + } + + if (me.isCreate) { + me.method = 'POST'; + } else { + me.method = 'PUT'; + } + }, +}); -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel