Implements a shared interface selector panel for openfabric and ospf fabrics. This GridPanel combines data from two sources: the node network interfaces (/nodes/<node>/network) and the fabrics section configuration, displaying a merged view of both sources.
It implements the following warning states: - When an interface has an IP address configured in /etc/network/interfaces, we display a warning and disable the input field, prompting users to configure addresses only via the fabrics interface - When addresses exist in both /etc/network/interfaces and /etc/network/interfaces.d/sdn, we show a warning without disabling the field, allowing users to remove the SDN interface configuration while preserving the underlying one Co-authored-by: Gabriel Goller <g.gol...@proxmox.com> Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> --- www/manager6/Makefile | 1 + www/manager6/sdn/fabrics/InterfacePanel.js | 220 +++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 www/manager6/sdn/fabrics/InterfacePanel.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index efb016948..469a1092e 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -308,6 +308,7 @@ JSSRC= \ sdn/zones/VlanEdit.js \ sdn/zones/VxlanEdit.js \ sdn/fabrics/Common.js \ + sdn/fabrics/InterfacePanel.js \ storage/ContentView.js \ storage/BackupView.js \ storage/Base.js \ diff --git a/www/manager6/sdn/fabrics/InterfacePanel.js b/www/manager6/sdn/fabrics/InterfacePanel.js new file mode 100644 index 000000000..0633b3673 --- /dev/null +++ b/www/manager6/sdn/fabrics/InterfacePanel.js @@ -0,0 +1,220 @@ +Ext.define('PVE.sdn.Fabric.InterfacePanel', { + extend: 'Ext.grid.Panel', + mixins: ['Ext.form.field.Field'], + + xtype: 'pveSDNFabricsInterfacePanel', + + nodeInterfaces: {}, + + selModel: { + mode: 'SIMPLE', + type: 'checkboxmodel', + }, + + commonColumns: [ + { + text: gettext('Status'), + dataIndex: 'status', + width: 30, + renderer: function(value, metaData, record) { + let me = this; + + let warning; + let nodeInterface = me.nodeInterfaces[record.data.name]; + + if (!nodeInterface) { + warning = gettext('Interface does not exist on node'); + } else if ((nodeInterface.ip && record.data.ip) || (nodeInterface.ip6 && record.data.ip6)) { + warning = gettext('Interface already has an address configured in /etc/network/interfaces'); + } else if (nodeInterface.ip || nodeInterface.ip6) { + warning = gettext('Configure the IP in the fabric, instead of /etc/network/interfaces'); + } + + if (warning) { + metaData.tdAttr = `data-qtip="${Ext.htmlEncode(Ext.htmlEncode(warning))}"`; + return `<i class="fa warning fa-warning"></i>`; + } + + return ''; + }, + + }, + { + text: gettext('Name'), + dataIndex: 'name', + flex: 2, + }, + { + text: gettext('Type'), + dataIndex: 'type', + flex: 1, + }, + { + text: gettext('IP'), + xtype: 'widgetcolumn', + dataIndex: 'ip', + flex: 1, + widget: { + xtype: 'proxmoxtextfield', + isFormField: false, + bind: { + disabled: '{record.isDisabled}', + }, + }, + }, + ], + + additionalColumns: [], + + controller: { + onValueChange: function(field, value) { + let me = this; + + let record = field.getWidgetRecord(); + + if (!record) { + return; + } + + let column = field.getWidgetColumn(); + + record.set(column.dataIndex, value); + record.commit(); + + me.getView().checkChange(); + }, + + control: { + field: { + change: 'onValueChange', + }, + }, + }, + + listeners: { + selectionchange: function() { + this.checkChange(); + }, + }, + + initComponent: function() { + let me = this; + + Ext.apply(me, { + store: Ext.create("Ext.data.Store", { + model: "Pve.sdn.Interface", + sorters: { + property: 'name', + direction: 'ASC', + }, + }), + columns: me.commonColumns.concat(me.additionalColumns), + }); + + me.callParent(); + + Proxmox.Utils.monStoreErrors(me, me.getStore(), true); + me.initField(); + }, + + setNodeInterfaces: function(interfaces) { + let me = this; + + let nodeInterfaces = {}; + for (const iface of interfaces) { + nodeInterfaces[iface.name] = iface; + } + + me.nodeInterfaces = nodeInterfaces; + + // reset value when setting new available interfaces + me.setValue([]); + }, + + getValue: function() { + let me = this; + + return me.getSelection() + .map((rec) => { + let data = {}; + + for (const [key, value] of Object.entries(rec.data)) { + if (value === '' || value === undefined || value === null) { + continue; + } + + if (['type', 'isDisabled'].includes(key)) { + continue; + } + + data[key] = value; + } + + return PVE.Parser.printPropertyString(data); + }); + }, + + setValue: function(value) { + let me = this; + + let store = me.getStore(); + + let selection = me.getSelectionModel(); + selection.deselectAll(); + + let data = structuredClone(me.nodeInterfaces); + + for (const iface of Object.values(data)) { + iface.isDisabled = iface.ip || iface.ip6; + } + + let selected = []; + let fabricInterfaces = structuredClone(value); + + for (let iface of fabricInterfaces) { + iface = PVE.Parser.parsePropertyString(iface); + + selected.push(iface.name); + + // if the fabric configuration defines an interface that was + // previously disabled, re-enable the field to allow editing of the + // value set in the fabric - we show a warning as well if there is + // already an IP configured in /e/n/i + iface.isDisabled = false; + + if (Object.prototype.hasOwnProperty.call(data, iface.name)) { + data[iface.name] = { + ...data[iface.name], + // fabric properties have precedence + ...iface, + }; + } else { + data[iface.name] = iface; + } + } + + store.setData(Object.values(data)); + + let selected_records = selected.map((name) => store.findRecord('name', name)); + selection.select(selected_records); + + me.resetOriginalValue(); + }, + + getSubmitData: function() { + let me = this; + + let name = me.getName(); + let value = me.getValue(); + + if (value.length === 0 && !me.isCreate) { + return { + 'delete': name, + }; + } + + return { + [name]: value, + }; + }, +}); -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel