[pve-devel] [PATCH pve-manager 5/8] ui: resource tree: add network resource
Stefan Hanreich
s.hanreich at proxmox.com
Thu Oct 30 16:48:39 CET 2025
From: Gabriel Goller <g.goller at proxmox.com>
Add the newly introduced network resource to the resource tree, so the
the status of fabrics can be displayed in the UI. For this matter, a
new NetworkBrowser widget is added, which is responsible for showing
the contents of a network resource.
The NetworkBrowser widget also contains code for handling the zone
type, which is currently still contained in the sdn resource type.
This ensures a smooth transition when moving the zones into the new
network resource type.
Co-authored-by: Stefan Hanreich <s.hanreich at proxmox.com>
Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
---
www/manager6/Makefile | 2 +
www/manager6/Utils.js | 11 ++
www/manager6/Workspace.js | 1 +
www/manager6/sdn/FabricsContentView.js | 77 ++++++++++++
www/manager6/sdn/NetworkBrowser.js | 167 +++++++++++++++++++++++++
www/manager6/tree/ResourceTree.js | 6 +
6 files changed, 264 insertions(+)
create mode 100644 www/manager6/sdn/FabricsContentView.js
create mode 100644 www/manager6/sdn/NetworkBrowser.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 85f9268d1..ba762578e 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -278,6 +278,7 @@ JSSRC= \
qemu/USBEdit.js \
qemu/VirtiofsEdit.js \
sdn/Browser.js \
+ sdn/NetworkBrowser.js \
sdn/ControllerView.js \
sdn/Status.js \
sdn/StatusView.js \
@@ -313,6 +314,7 @@ JSSRC= \
sdn/zones/VlanEdit.js \
sdn/zones/VxlanEdit.js \
sdn/FabricsView.js \
+ sdn/FabricsContentView.js \
sdn/fabrics/Common.js \
sdn/fabrics/InterfacePanel.js \
sdn/fabrics/NodeEdit.js \
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index c48ee0b25..2f812a442 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -1276,6 +1276,12 @@ Ext.define('PVE.Utils', {
// templates
objType = 'template';
status = type;
+ } else if (type === 'network') {
+ const network_type_mapping = {
+ fabric: 'fa fa-road',
+ };
+
+ return network_type_mapping[record.network_type] ?? '';
} else if (type === 'storage' && record.content === 'import') {
return 'fa fa-cloud-download';
} else {
@@ -1299,6 +1305,11 @@ Ext.define('PVE.Utils', {
var cls = PVE.Utils.get_object_icon_class(value, record.data);
var fa = '<i class="fa-fw x-grid-icon-custom ' + cls + '"></i> ';
+
+ if (value === 'network') {
+ return fa + record.data.network_type;
+ }
+
return fa + value;
},
diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
index a25746582..9f1e807d5 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -250,6 +250,7 @@ Ext.define('PVE.StdWorkspace', {
lxc: 'pveLXCConfig',
storage: 'PVE.storage.Browser',
sdn: 'PVE.sdn.Browser',
+ network: 'PVE.network.Browser',
pool: 'pvePoolConfig',
tag: 'pveTagConfig',
};
diff --git a/www/manager6/sdn/FabricsContentView.js b/www/manager6/sdn/FabricsContentView.js
new file mode 100644
index 000000000..47e8bce7f
--- /dev/null
+++ b/www/manager6/sdn/FabricsContentView.js
@@ -0,0 +1,77 @@
+Ext.define('PVE.sdn.FabricRoutesContentView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: 'widget.pveSDNFabricRoutesContentView',
+
+ columns: [
+ {
+ header: gettext('Route'),
+ sortable: true,
+ dataIndex: 'route',
+ flex: 1,
+ },
+ {
+ header: gettext('Via'),
+ sortable: true,
+ dataIndex: 'via',
+ renderer: (value) => {
+ if (Ext.isArray(value)) {
+ return value.join('<br>');
+ }
+ return value || '';
+ },
+ flex: 1,
+ },
+ ],
+});
+
+Ext.define('PVE.sdn.FabricNeighborsContentView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: 'widget.pveSDNFabricNeighborsContentView',
+
+ columns: [
+ {
+ header: gettext('Neighbor'),
+ sortable: true,
+ dataIndex: 'neighbor',
+ flex: 1,
+ },
+ {
+ header: gettext('Status'),
+ sortable: true,
+ dataIndex: 'status',
+ flex: 0.5,
+ },
+ {
+ header: gettext('Uptime'),
+ sortable: true,
+ dataIndex: 'uptime',
+ flex: 0.5,
+ },
+ ],
+});
+
+Ext.define('PVE.sdn.FabricInterfacesContentView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: 'widget.pveSDNFabricInterfacesContentView',
+
+ columns: [
+ {
+ header: gettext('Name'),
+ sortable: true,
+ dataIndex: 'name',
+ flex: 1,
+ },
+ {
+ header: gettext('Type'),
+ sortable: true,
+ dataIndex: 'type',
+ flex: 1,
+ },
+ {
+ header: gettext('State'),
+ sortable: true,
+ dataIndex: 'state',
+ flex: 1,
+ },
+ ],
+});
diff --git a/www/manager6/sdn/NetworkBrowser.js b/www/manager6/sdn/NetworkBrowser.js
new file mode 100644
index 000000000..ad024e6a1
--- /dev/null
+++ b/www/manager6/sdn/NetworkBrowser.js
@@ -0,0 +1,167 @@
+Ext.define('PVE.network.Browser', {
+ extend: 'PVE.panel.Config',
+ alias: 'widget.PVE.network.Browser',
+
+ initComponent: function () {
+ let me = this;
+ let data = me.pveSelNode.data;
+
+ let node = data.node;
+ if (!node) {
+ throw 'no node name specified';
+ }
+
+ let name = data.network;
+ if (!name) {
+ throw 'no name specified';
+ }
+
+ let networkType = data.network_type;
+ if (!name) {
+ throw 'no type specified';
+ }
+
+ me.items = [];
+
+ if (networkType === 'fabric') {
+ me.onlineHelp = 'pvesdn_config_fabrics';
+
+ me.items.push({
+ nodename: node,
+ fabricId: name,
+ protocol: me.pveSelNode.data.protocol,
+ xtype: 'pveSDNFabricRoutesContentView',
+ title: gettext('Routes'),
+ iconCls: 'fa fa-exchange',
+ itemId: 'routes',
+ width: '100%',
+ store: {
+ proxy: {
+ type: 'proxmox',
+ url: `/api2/json/nodes/${node}/sdn/fabrics/${name}/routes`,
+ reader: {
+ type: 'json',
+ rootProperty: 'data',
+ },
+ },
+ autoLoad: true,
+ },
+ });
+
+ me.items.push({
+ nodename: node,
+ fabricId: name,
+ protocol: me.pveSelNode.data.protocol,
+ xtype: 'pveSDNFabricNeighborsContentView',
+ title: gettext('Neighbors'),
+ iconCls: 'fa fa-handshake-o',
+ itemId: 'neighbors',
+ width: '100%',
+ store: {
+ proxy: {
+ type: 'proxmox',
+ url: `/api2/json/nodes/${node}/sdn/fabrics/${name}/neighbors`,
+ reader: {
+ type: 'json',
+ rootProperty: 'data',
+ },
+ },
+ autoLoad: true,
+ },
+ });
+
+ me.items.push({
+ nodename: node,
+ fabricId: name,
+ protocol: me.pveSelNode.data.protocol,
+ xtype: 'pveSDNFabricInterfacesContentView',
+ title: gettext('Interfaces'),
+ iconCls: 'fa fa-upload',
+ itemId: 'interfaces',
+ width: '100%',
+ store: {
+ proxy: {
+ type: 'proxmox',
+ url: `/api2/json/nodes/${node}/sdn/fabrics/${name}/interfaces`,
+ reader: {
+ type: 'json',
+ rootProperty: 'data',
+ },
+ },
+ autoLoad: true,
+ },
+ });
+ } else if (networkType === 'zone') {
+ const caps = Ext.state.Manager.get('GuiCap');
+
+ me.items.push({
+ nodename: node,
+ zone: name,
+ xtype: 'pveSDNZoneContentPanel',
+ title: gettext('Content'),
+ iconCls: 'fa fa-th',
+ itemId: 'content',
+ });
+
+ if (caps.sdn['Permissions.Modify']) {
+ me.items.push({
+ xtype: 'pveACLView',
+ title: gettext('Permissions'),
+ iconCls: 'fa fa-unlock',
+ itemId: 'permissions',
+ path: `/sdn/zones/${name}`,
+ });
+ }
+
+ me.items.push({
+ nodename: node,
+ zone: name,
+ xtype: 'pveSDNZoneBridgePanel',
+ title: gettext('Bridges'),
+ iconCls: 'fa fa-network-wired x-fa-sdn-treelist',
+ itemId: 'bridges',
+ });
+
+ if (data.zone_type && data.zone_type === 'evpn') {
+ me.items.push({
+ nodename: node,
+ zone: name,
+ xtype: 'pveSDNEvpnZoneIpVrfPanel',
+ title: gettext('IP-VRF'),
+ iconCls: 'fa fa-th-list',
+ itemId: 'ip-vrf',
+ });
+
+ me.items.push({
+ nodename: node,
+ zone: name,
+ xtype: 'pveSDNEvpnZoneMacVrfPanel',
+ title: gettext('MAC-VRFs'),
+ iconCls: 'fa fa-th-list',
+ itemId: 'mac-vrfs',
+ });
+ }
+ } else {
+ me.items.push({
+ xtype: 'container',
+ title: gettext('Content'),
+ iconCls: 'fa fa-th',
+ itemId: 'content',
+ html: `unknown network type: ${networkType}`,
+ width: '100%',
+ });
+ }
+
+ Ext.apply(me, {
+ title: Ext.String.format(
+ gettext('{0} {1} on node {2}'),
+ `${networkType}`,
+ `'${name}'`,
+ `'${node}'`,
+ ),
+ hstateid: 'networktab',
+ });
+
+ me.callParent();
+ },
+});
diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js
index e83ccfc85..b6ab9a1e9 100644
--- a/www/manager6/tree/ResourceTree.js
+++ b/www/manager6/tree/ResourceTree.js
@@ -25,6 +25,10 @@ Ext.define('PVE.tree.ResourceTree', {
iconCls: 'fa fa-th',
text: gettext('SDN'),
},
+ network: {
+ iconCls: 'fa fa-globe',
+ text: gettext('Network'),
+ },
qemu: {
iconCls: 'fa fa-desktop',
text: gettext('Virtual Machine'),
@@ -55,6 +59,8 @@ Ext.define('PVE.tree.ResourceTree', {
return 2;
case 'sdn':
return 3;
+ case 'network':
+ return 3.5;
case 'storage':
return 4;
default:
--
2.47.3
More information about the pve-devel
mailing list