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 [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id CB6151FF168 for <inbox@lore.proxmox.com>; Tue, 4 Mar 2025 10:57:56 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 71CE41A389; Tue, 4 Mar 2025 10:57:51 +0100 (CET) Message-ID: <cf2dc325-2835-43c9-9b12-6f0afebc2e79@proxmox.com> Date: Tue, 4 Mar 2025 10:57:46 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird To: Gabriel Goller <g.goller@proxmox.com>, pve-devel@lists.proxmox.com References: <20250214133951.344500-1-g.goller@proxmox.com> <20250214133951.344500-10-g.goller@proxmox.com> Content-Language: en-US From: Stefan Hanreich <s.hanreich@proxmox.com> In-Reply-To: <20250214133951.344500-10-g.goller@proxmox.com> X-SPAM-LEVEL: Spam detection results: 0 AWL 0.669 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [cluster.pm, fabric.name, node.net, network.pm, data.name, node.name] Subject: Re: [pve-devel] [PATCH pve-manager 09/11] sdn: add Fabrics view 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> On 2/14/25 14:39, Gabriel Goller wrote: > Add the FabricsView in the sdn category of the datacenter view. The > FabricsView allows to show all the fabrics on all the nodes of the > cluster. > > Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com> > Signed-off-by: Gabriel Goller <g.goller@proxmox.com> > --- > PVE/API2/Cluster.pm | 7 +- > PVE/API2/Network.pm | 7 +- > www/manager6/.lint-incremental | 0 > www/manager6/Makefile | 8 + > www/manager6/dc/Config.js | 8 + > www/manager6/sdn/FabricsView.js | 359 ++++++++++++++++++++++++++++++++ > 6 files changed, 379 insertions(+), 10 deletions(-) > create mode 100644 www/manager6/.lint-incremental > create mode 100644 www/manager6/sdn/FabricsView.js > > diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm > index a0e5c11b6e8e..7730aab82a25 100644 > --- a/PVE/API2/Cluster.pm > +++ b/PVE/API2/Cluster.pm > @@ -35,11 +35,8 @@ use PVE::API2::Firewall::Cluster; > use PVE::API2::HAConfig; > use PVE::API2::ReplicationConfig; > > -my $have_sdn; > -eval { > - require PVE::API2::Network::SDN; > - $have_sdn = 1; > -}; > +my $have_sdn = 1; > +require PVE::API2::Network::SDN; > > use base qw(PVE::RESTHandler); > > diff --git a/PVE/API2/Network.pm b/PVE/API2/Network.pm > index cfccdd9e3da3..3c45fe2fb7bf 100644 > --- a/PVE/API2/Network.pm > +++ b/PVE/API2/Network.pm > @@ -16,11 +16,8 @@ use IO::File; > > use base qw(PVE::RESTHandler); > > -my $have_sdn; > -eval { > - require PVE::Network::SDN; > - $have_sdn = 1; > -}; > +my $have_sdn = 1; > +require PVE::Network::SDN; > > my $iflockfn = "/etc/network/.pve-interfaces.lock"; > > diff --git a/www/manager6/.lint-incremental b/www/manager6/.lint-incremental > new file mode 100644 > index 000000000000..e69de29bb2d1 > diff --git a/www/manager6/Makefile b/www/manager6/Makefile > index c94a5cdfbf70..224b6079e833 100644 > --- a/www/manager6/Makefile > +++ b/www/manager6/Makefile > @@ -303,6 +303,14 @@ JSSRC= \ > sdn/zones/SimpleEdit.js \ > sdn/zones/VlanEdit.js \ > sdn/zones/VxlanEdit.js \ > + sdn/FabricsView.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 \ > storage/Base.js \ > diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js > index 74728c8320e9..68f7be8d6042 100644 > --- a/www/manager6/dc/Config.js > +++ b/www/manager6/dc/Config.js > @@ -229,6 +229,14 @@ Ext.define('PVE.dc.Config', { > hidden: true, > iconCls: 'fa fa-shield', > itemId: 'sdnfirewall', > + }, > + { > + xtype: 'pveSDNFabricView', > + groups: ['sdn'], > + title: gettext('Fabrics'), > + hidden: true, > + iconCls: 'fa fa-road', > + itemId: 'sdnfabrics', > }); > } > > diff --git a/www/manager6/sdn/FabricsView.js b/www/manager6/sdn/FabricsView.js > new file mode 100644 > index 000000000000..f090ee894b75 > --- /dev/null > +++ b/www/manager6/sdn/FabricsView.js > @@ -0,0 +1,359 @@ > +const FABRIC_PANELS = { > + 'openfabric': 'PVE.sdn.Fabric.OpenFabric.Fabric.Edit', > + 'ospf': 'PVE.sdn.Fabric.Ospf.Fabric.Edit', > +}; > + > +const NODE_PANELS = { > + 'openfabric': 'PVE.sdn.Fabric.OpenFabric.Node.Edit', > + 'ospf': 'PVE.sdn.Fabric.Ospf.Node.Edit', > +}; > + > +const INTERFACE_PANELS = { > + 'openfabric': 'PVE.sdn.Fabric.OpenFabric.Interface.Edit', > + 'ospf': 'PVE.sdn.Fabric.Ospf.Interface.Edit', > +}; > + > +Ext.define('PVE.sdn.Fabric.View', { > + extend: 'Ext.tree.Panel', > + > + xtype: 'pveSDNFabricView', > + > + columns: [ > + { > + xtype: 'treecolumn', > + text: gettext('Name'), > + dataIndex: 'name', > + width: 200, > + }, > + { > + text: gettext('Identifier'), > + dataIndex: 'identifier', > + width: 200, > + }, > + { > + text: gettext('Action'), > + xtype: 'actioncolumn', > + dataIndex: 'text', > + width: 150, > + items: [ > + { > + handler: 'addAction', > + getTip: (_v, _m, _rec) => gettext('Add'), > + getClass: (_v, _m, { data }) => { > + if (data.type === 'fabric') { > + return 'fa fa-plus-square'; > + } > + > + return 'pmx-hidden'; > + }, > + isActionDisabled: (_v, _r, _c, _i, { data }) => data.type !== 'fabric', > + }, > + { > + tooltip: gettext('Edit'), > + handler: 'editAction', > + getClass: (_v, _m, { data }) => { > + // the fabric type (openfabric, ospf, etc.) cannot be edited > + if (data.type) { > + return 'fa fa-pencil fa-fw'; > + } > + > + return 'pmx-hidden'; > + }, > + isActionDisabled: (_v, _r, _c, _i, { data }) => !data.type, > + }, > + { > + tooltip: gettext('Delete'), > + handler: 'deleteAction', > + getClass: (_v, _m, { data }) => { > + // the fabric type (openfabric, ospf, etc.) cannot be deleted > + if (data.type) { > + return 'fa critical fa-trash-o'; > + } > + > + return 'pmx-hidden'; > + }, > + isActionDisabled: (_v, _r, _c, _i, { data }) => !data.type, > + }, > + ], > + }, > + ], > + > + store: { > + sorters: ['name'], > + }, > + > + layout: 'fit', > + rootVisible: false, > + animate: false, > + > + tbar: [ > + { > + text: gettext('Add Fabric'), > + menu: [ > + { > + text: gettext('OpenFabric'), > + handler: 'openAddOpenFabricWindow', > + }, > + { > + text: gettext('OSPF'), > + handler: 'openAddOspfWindow', > + }, > + ], > + }, > + { > + xtype: 'proxmoxButton', > + text: gettext('Reload'), > + handler: 'reload', > + }, > + ], > + > + controller: { > + xclass: 'Ext.app.ViewController', > + > + reload: function() { > + let me = this; > + > + Proxmox.Utils.API2Request({ > + url: `/cluster/sdn/fabrics/`, > + method: 'GET', > + success: function(response, opts) { > + let ospf = Object.entries(response.result.data.ospf); > + let openfabric = Object.entries(response.result.data.openfabric); > + > + // add some metadata so we can merge the objects later and still know the protocol/type > + ospf = ospf.map(x => { > + if (x["1"].fabric) { > + return Object.assign(x["1"].fabric, { _protocol: "ospf", _type: "fabric", name: x["0"] }); > + } else if (x["1"].node) { > + let id = x["0"].split("_"); I think we already talked about this, but I don't really remember the outcome. Can we return this already from the API so we don't have to parse it in the frontend? > + return Object.assign(x["1"].node, > + { > + _protocol: "ospf", > + _type: "node", > + node: id[1], > + fabric: id[0], > + }, > + ); > + } else { > + return x; > + } > + }); > + openfabric = openfabric.map(x => { > + if (x["1"].fabric) { > + return Object.assign(x["1"].fabric, { _protocol: "openfabric", _type: "fabric", name: x["0"] }); > + } else if (x["1"].node) { > + let id = x["0"].split("_"); > + return Object.assign(x["1"].node, > + { > + _protocol: "openfabric", > + _type: "node", > + node: id[1], > + fabric: id[0], > + }, > + ); > + } else { > + return x; > + } > + }); > + > + let data = {}; > + data.ospf = ospf; > + data.openfabric = openfabric; > + > + let fabrics = Object.entries(data).map((protocol) => { > + let protocol_entry = {}; > + protocol_entry.children = protocol["1"].filter(e => e._type === "fabric").map(fabric => { > + fabric.children = protocol["1"].filter(e => e._type === "node") > + .filter((node) => > + node.fabric === fabric.name && node._protocol === fabric._protocol) > + .map((node) => { > + node.children = node.interface > + .map((nic) => { > + let parsed = PVE.Parser.parsePropertyString(nic); > + parsed.leaf = true; > + parsed.type = 'interface'; > + // Add meta information that we need to edit and remove > + parsed._protocol = node._protocol; > + parsed._fabric = fabric.name; > + parsed._node = node.node; > + parsed.iconCls = 'x-tree-icon-none'; > + return parsed; > + }); > + > + node.expanded = true; > + node.type = 'node'; > + node.name = node.node; > + node._fabric = fabric.name; > + node.identifier = node.net || node.router_id; > + node.iconCls = 'fa fa-desktop x-fa-treepanel'; > + > + return node; > + }); > + > + fabric.type = 'fabric'; > + fabric.expanded = true; > + fabric.iconCls = 'fa fa-road x-fa-treepanel'; > + > + return fabric; > + }); > + protocol_entry.name = protocol["0"]; > + protocol_entry.expanded = true; > + return protocol_entry; > + }); > + > + me.getView().setRootNode({ > + name: '__root', > + expanded: true, > + children: fabrics, > + }); > + }, > + }); > + }, > + > + getFabricEditPanel: function(type) { > + return FABRIC_PANELS[type]; > + }, > + > + getNodeEditPanel: function(type) { > + return NODE_PANELS[type]; > + }, > + > + getInterfaceEditPanel: function(type) { > + return INTERFACE_PANELS[type]; > + }, > + > + addAction: function(_grid, _rI, _cI, _item, _e, rec) { > + let me = this; > + > + let component = me.getNodeEditPanel(rec.data._protocol); > + > + if (!component) { > + console.warn(`unknown protocol ${rec.data._protocol}`); > + return; > + } > + > + let extraRequestParams = { > + type: rec.data.type, > + protocol: rec.data._protocol, > + fabric: rec.data.name, > + }; > + > + let window = Ext.create(component, { > + autoShow: true, > + isCreate: true, > + autoLoad: false, > + extraRequestParams, > + }); > + > + window.on('destroy', () => me.reload()); > + }, > + > + editAction: function(_grid, _rI, _cI, _item, _e, rec) { > + let me = this; > + > + let component = ''; > + let url = ''; > + let autoLoad = true; > + > + if (rec.data.type === 'fabric') { > + component = me.getFabricEditPanel(rec.data._protocol); > + url = `/cluster/sdn/fabrics/${rec.data._protocol}/${rec.data.name}`; > + } else if (rec.data.type === 'node') { > + component = me.getNodeEditPanel(rec.data._protocol); > + // no url, every request is done manually > + url = `/cluster/sdn/fabrics/${rec.data._protocol}/${rec.data._fabric}/node/${rec.data.node}`; > + autoLoad = false; > + } else if (rec.data.type === 'interface') { > + component = me.getInterfaceEditPanel(rec.data._protocol); > + url = `/cluster/sdn/fabrics/${rec.data._protocol}/${rec.data._fabric}/node\ > + /${rec.data._node}/interface/${rec.data.name}`; > + } > + > + if (!component) { > + console.warn(`unknown protocol ${rec.data._protocol} or unknown type ${rec.data.type}`); > + return; > + } > + > + let window = Ext.create(component, { > + autoShow: true, > + autoLoad: autoLoad, > + isCreate: false, > + submitUrl: url, > + loadUrl: url, > + fabric: rec.data._fabric, > + node: rec.data.node, > + }); > + > + window.on('destroy', () => me.reload()); > + }, > + > + deleteAction: function(table, rI, cI, item, e, { data }) { > + let me = this; > + let view = me.getView(); > + > + Ext.Msg.show({ > + title: gettext('Confirm'), > + icon: Ext.Msg.WARNING, > + message: Ext.String.format(gettext('Are you sure you want to remove the fabric {0}?'), `${data.name}`), > + buttons: Ext.Msg.YESNO, > + defaultFocus: 'no', > + callback: function(btn) { > + if (btn !== 'yes') { > + return; > + } > + > + let url; > + if (data.type === "node") { > + url = `/cluster/sdn/fabrics/${data._protocol}/${data._fabric}/node/${data.name}`; > + } else if (data.type === "fabric") { > + url = `/cluster/sdn/fabrics/${data._protocol}/${data.name}`; > + } else if (data.type === "interface") { > + url = `/cluster/sdn/fabrics/${data._protocol}/${data._fabric}/node/\ > + ${data._node}/interface/${data.name}`; > + } else { > + console.warn("deleteAction: missing type"); > + } > + > + Proxmox.Utils.API2Request({ > + url, > + method: 'DELETE', > + waitMsgTarget: view, > + failure: function(response, opts) { > + Ext.Msg.alert(gettext('Error'), response.htmlStatus); > + }, > + callback: me.reload.bind(me), > + }); > + }, > + }); > + }, > + > + openAddOpenFabricWindow: function() { > + let me = this; > + > + let window = Ext.create('PVE.sdn.Fabric.OpenFabric.Fabric.Edit', { > + autoShow: true, > + autoLoad: false, > + isCreate: true, > + }); > + > + window.on('destroy', () => me.reload()); > + }, > + > + openAddOspfWindow: function() { > + let me = this; > + > + let window = Ext.create('PVE.sdn.Fabric.Ospf.Fabric.Edit', { > + autoShow: true, > + autoLoad: false, > + isCreate: true, > + }); > + > + window.on('destroy', () => me.reload()); > + }, > + > + init: function(view) { > + let me = this; > + me.reload(); > + }, > + }, > +}); _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel