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.hanre...@proxmox.com> Signed-off-by: Gabriel Goller <g.gol...@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("_"); + 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(); + }, + }, +}); -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel