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 387781FF168 for <inbox@lore.proxmox.com>; Tue, 4 Mar 2025 11:08:38 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 54FE91A5DC; Tue, 4 Mar 2025 11:08:32 +0100 (CET) Message-ID: <eea3854b-c2a9-4037-8849-86896032fe50@proxmox.com> Date: Tue, 4 Mar 2025 11:07:55 +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-11-g.goller@proxmox.com> Content-Language: en-US From: Stefan Hanreich <s.hanreich@proxmox.com> In-Reply-To: <20250214133951.344500-11-g.goller@proxmox.com> X-SPAM-LEVEL: Spam detection results: 0 AWL 0.668 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. [j.name, i.name, data.store] Subject: Re: [pve-devel] [PATCH pve-manager 10/11] sdn: add fabric edit/delete forms 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> comments inline On 2/14/25 14:39, Gabriel Goller wrote: > Add the add/edit/delete modals for the FabricsView. This allows us to > create, edit, and delete fabrics, nodes, and interfaces. > > Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com> > Signed-off-by: Gabriel Goller <g.goller@proxmox.com> > --- > www/manager6/sdn/fabrics/Common.js | 222 ++++++++++++++++++ > .../sdn/fabrics/openfabric/FabricEdit.js | 67 ++++++ > .../sdn/fabrics/openfabric/InterfaceEdit.js | 92 ++++++++ > .../sdn/fabrics/openfabric/NodeEdit.js | 187 +++++++++++++++ > www/manager6/sdn/fabrics/ospf/FabricEdit.js | 60 +++++ > .../sdn/fabrics/ospf/InterfaceEdit.js | 46 ++++ > www/manager6/sdn/fabrics/ospf/NodeEdit.js | 191 +++++++++++++++ > 7 files changed, 865 insertions(+) > create mode 100644 www/manager6/sdn/fabrics/Common.js > create mode 100644 www/manager6/sdn/fabrics/openfabric/FabricEdit.js > create mode 100644 www/manager6/sdn/fabrics/openfabric/InterfaceEdit.js > create mode 100644 www/manager6/sdn/fabrics/openfabric/NodeEdit.js > create mode 100644 www/manager6/sdn/fabrics/ospf/FabricEdit.js > create mode 100644 www/manager6/sdn/fabrics/ospf/InterfaceEdit.js > create mode 100644 www/manager6/sdn/fabrics/ospf/NodeEdit.js > > diff --git a/www/manager6/sdn/fabrics/Common.js b/www/manager6/sdn/fabrics/Common.js > new file mode 100644 > index 000000000000..72ec093fc928 > --- /dev/null > +++ b/www/manager6/sdn/fabrics/Common.js > @@ -0,0 +1,222 @@ > +Ext.define('PVE.sdn.Fabric.InterfacePanel', { > + extend: 'Ext.grid.Panel', > + mixins: ['Ext.form.field.Field'], > + > + network_interfaces: undefined, > + > + selectionChange: function(_grid, _selection) { > + let me = this; > + me.value = me.getSelection().map((rec) => { > + delete rec.data.cidr; > + delete rec.data.cidr6; > + delete rec.data.selected; > + return PVE.Parser.printPropertyString(rec.data); maybe we could explicitly select the fields we want to include here, so this doesn't break when we add new fields? > + }); > + me.checkChange(); > + }, > + > + getValue: function() { > + let me = this; > + return me.value ?? []; > + }, > + > + setValue: function(value) { > + let me = this; > + > + value ??= []; > + > + me.updateSelectedInterfaces(value); > + > + return me.mixins.field.setValue.call(me, value); > + }, > + > + addInterfaces: function(fabric_interfaces) { > + let me = this; > + if (me.network_interfaces) { > + let node_interfaces = me.network_interfaces > + //.filter((elem) => elem.type === 'eth') > + .map((elem) => { > + const obj = { > + name: elem.iface, > + cidr: elem.cidr, > + cidr6: elem.cidr6, > + }; > + return obj; > + }); > + > + if (fabric_interfaces) { > + node_interfaces = node_interfaces.map(i => { > + let elem = fabric_interfaces.find(j => j.name === i.name); > + return Object.assign(i, elem); > + }); > + let store = me.getStore(); > + store.setData(node_interfaces); > + } else { > + let store = me.getStore(); > + store.setData(node_interfaces); > + } > + } else if (fabric_interfaces) { > + // We could not get the available interfaces of the node, so we display the configured ones only. > + let interfaces = fabric_interfaces.map((elem) => { > + const obj = { > + name: elem.name, > + cidr: 'unknown', > + cidr6: 'unknown', > + ...elem, > + }; > + return obj; > + }); > + > + let store = me.getStore(); > + store.setData(interfaces); > + } else { > + console.warn("no fabric_interfaces and cluster_interfaces available!"); > + } > + }, > + > + updateSelectedInterfaces: function(values) { > + let me = this; > + if (values) { > + let recs = []; > + let store = me.getStore(); > + > + for (const i of values) { > + let rec = store.getById(i.name); > + if (rec) { > + recs.push(rec); > + } > + } > + me.suspendEvent('change'); > + me.setSelection(); > + me.setSelection(recs); > + me.resumeEvent('change'); > + } else { > + me.suspendEvent('change'); > + me.setSelection(); > + me.resumeEvent('change'); > + } could avoid some duplication by moving the methods calls above / below the if/else > + }, > + > + setNetworkInterfaces: function(network_interfaces) { > + this.network_interfaces = network_interfaces; > + }, > + > + getSubmitData: function() { > + let records = this.getSelection().map((record) => { > + // we don't need the cidr, cidr6, and selected parameters > + delete record.data.cidr; > + delete record.data.cidr6; > + delete record.data.selected; > + return Proxmox.Utils.printPropertyString(record.data); same w.r.t selecting only the fields we care about here > + }); > + return { > + 'interfaces': records, > + }; > + }, > + > + controller: { > + onValueChange: function(field, value) { > + let me = this; > + let record = field.getWidgetRecord(); > + let column = field.getWidgetColumn(); > + if (record) { > + record.set(column.dataIndex, value); > + record.commit(); > + > + me.getView().checkChange(); > + me.getView().selectionChange(); > + } > + }, > + > + control: { > + 'field': { > + change: 'onValueChange', > + }, > + }, > + }, > + > + selModel: { > + type: 'checkboxmodel', > + mode: 'SIMPLE', > + }, > + > + listeners: { > + selectionchange: function() { > + this.selectionChange(...arguments); > + }, > + }, > + > + commonColumns: [ > + { > + text: gettext('Name'), > + dataIndex: 'name', > + flex: 2, > + }, > + { > + text: gettext('IPv4'), > + dataIndex: 'cidr', > + flex: 2, > + }, > + { > + text: gettext('IPv6'), > + dataIndex: 'cidr6', > + flex: 2, > + }, > + ], > + > + additionalColumns: [], > + > + 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(); > + }, > +}); > + > + > +Ext.define('Pve.sdn.Fabric', { > + extend: 'Ext.data.Model', > + idProperty: 'name', > + fields: [ > + 'name', > + 'type', > + ], > +}); > + > +Ext.define('Pve.sdn.Node', { > + extend: 'Ext.data.Model', > + idProperty: 'name', > + fields: [ > + 'name', > + 'fabric', > + 'type', > + ], > +}); > + > +Ext.define('Pve.sdn.Interface', { > + extend: 'Ext.data.Model', > + idProperty: 'name', > + fields: [ > + 'name', > + 'cidr', > + 'cidr6', > + 'passive', > + 'hello_interval', > + 'hello_multiplier', > + 'csnp_interval', > + ], > +}); > diff --git a/www/manager6/sdn/fabrics/openfabric/FabricEdit.js b/www/manager6/sdn/fabrics/openfabric/FabricEdit.js > new file mode 100644 > index 000000000000..0431a00e7302 > --- /dev/null > +++ b/www/manager6/sdn/fabrics/openfabric/FabricEdit.js > @@ -0,0 +1,67 @@ > +Ext.define('PVE.sdn.Fabric.OpenFabric.Fabric.Edit', { > + extend: 'Proxmox.window.Edit', > + xtype: 'pveSDNOpenFabricRouteEdit', > + > + subject: gettext('Add OpenFabric'), > + > + url: '/cluster/sdn/fabrics/openfabric', > + type: 'openfabric', > + > + isCreate: undefined, > + > + viewModel: { > + data: { > + isCreate: true, > + }, > + }, > + > + items: [ > + { > + xtype: 'textfield', > + name: 'type', > + value: 'openfabric', > + allowBlank: false, > + hidden: true, > + }, > + { > + xtype: 'textfield', > + fieldLabel: gettext('Name'), > + labelWidth: 120, > + name: 'name', > + allowBlank: false, > + bind: { > + disabled: '{!isCreate}', > + }, > + }, > + { > + xtype: 'numberfield', > + fieldLabel: gettext('Hello Interval'), > + labelWidth: 120, > + name: 'hello_interval', > + allowBlank: true, > + }, > + ], > + > + submitUrl: function(url, values) { > + let me = this; > + return `${me.url}`; > + }, > + > + initComponent: function() { > + let me = this; > + > + let view = me.getViewModel(); > + view.set('isCreate', me.isCreate); > + > + me.method = me.isCreate ? 'POST' : 'PUT'; > + me.callParent(); > + > + if (!me.isCreate) { > + me.load({ > + success: function(response, opts) { > + me.setValues(response.result.data.fabric); > + }, > + }); > + } > + }, > +}); > diff --git a/www/manager6/sdn/fabrics/openfabric/InterfaceEdit.js b/www/manager6/sdn/fabrics/openfabric/InterfaceEdit.js > new file mode 100644 > index 000000000000..ef33c16b784f > --- /dev/null > +++ b/www/manager6/sdn/fabrics/openfabric/InterfaceEdit.js > @@ -0,0 +1,92 @@ > +Ext.define('PVE.sdn.Fabric.OpenFabric.Interface.Edit', { > + extend: 'Proxmox.window.Edit', > + xtype: 'pveSDNOpenFabricInterfaceEdit', > + > + initComponent: function() { > + let me = this; > + > + Ext.apply(me, { > + items: [{ > + xtype: 'inputpanel', > + items: [ > + { > + xtype: 'textfield', > + fieldLabel: gettext('Interface'), > + name: 'name', > + disabled: true, > + }, > + { > + xtype: 'proxmoxcheckbox', > + fieldLabel: gettext('Passive'), > + name: 'passive', > + uncheckedValue: 0, > + }, > + { > + xtype: 'numberfield', > + fieldLabel: gettext('Hello Interval'), > + name: 'hello_interval', > + allowBlank: true, > + }, > + { > + xtype: 'numberfield', > + fieldLabel: gettext('Hello Multiplier'), > + name: 'hello_multiplier', > + allowBlank: true, > + }, > + { > + xtype: 'numberfield', > + fieldLabel: gettext('CSNP Interval'), > + name: 'csnp_interval', > + allowBlank: true, > + }, > + ], > + }], > + }); > + > + me.callParent(); > + }, > +}); > + > +Ext.define('PVE.sdn.Fabric.OpenFabric.InterfacePanel', { > + extend: 'PVE.sdn.Fabric.InterfacePanel', > + > + additionalColumns: [ > + { > + text: gettext('Passive'), > + xtype: 'widgetcolumn', > + dataIndex: 'passive', > + flex: 1, > + widget: { > + xtype: 'checkbox', > + }, > + }, > + { > + text: gettext('Hello Interval'), > + xtype: 'widgetcolumn', > + dataIndex: 'hello_interval', > + flex: 1, > + widget: { > + xtype: 'numberfield', > + }, > + }, > + { > + text: gettext('Hello Multiplier'), > + xtype: 'widgetcolumn', > + dataIndex: 'hello_multiplier', > + flex: 1, > + widget: { > + xtype: 'numberfield', > + }, > + }, > + { > + text: gettext('CSNP Interval'), > + xtype: 'widgetcolumn', > + dataIndex: 'csnp_interval', > + flex: 1, > + widget: { > + xtype: 'numberfield', > + }, > + }, > + ], > +}); > + > diff --git a/www/manager6/sdn/fabrics/openfabric/NodeEdit.js b/www/manager6/sdn/fabrics/openfabric/NodeEdit.js > new file mode 100644 > index 000000000000..ce61f0c15b49 > --- /dev/null > +++ b/www/manager6/sdn/fabrics/openfabric/NodeEdit.js > @@ -0,0 +1,187 @@ > +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); > + return { node: values.node.node, net: values.node.net, interfaces: values.node.interface }; > + } 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.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('Net'), > + labelWidth: 120, > + name: 'net', > + allowBlank: false, > + }, > + me.interface_selector, > + ]; > + me.callParent(); > + }, > +}); > + > +Ext.define('PVE.sdn.Fabric.OpenFabric.Node.Edit', { > + extend: 'Proxmox.window.Edit', > + xtype: 'pveSDNFabricAddNode', > + > + width: 800, > + > + // dummyurl > + url: '/cluster/sdn/fabrics/openfabric', > + > + interface_selector: undefined, > + 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.interface_selector = Ext.create('PVE.sdn.Fabric.OpenFabric.InterfacePanel', { > + name: 'interfaces', > + }); > + > + 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', { > + 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.node.interface = fabricResult.node.interface > + .map(i => PVE.Parser.parsePropertyString(i)); > + fabricResult.network_interfaces = clusterResult; > + // this will also set them as selected > + ipanel.setValues(fabricResult); > + }); > + }); > + } 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.node.interface = fabricResult.node.interface > + .map(i => PVE.Parser.parsePropertyString(i)); > + ipanel.setValues(fabricResult); > + }); > + } > + }); > + } > + > + if (me.isCreate) { > + me.method = 'POST'; > + } else { > + me.method = 'PUT'; > + } > + }, > +}); > + > diff --git a/www/manager6/sdn/fabrics/ospf/FabricEdit.js b/www/manager6/sdn/fabrics/ospf/FabricEdit.js > new file mode 100644 > index 000000000000..2ce88e443cdd > --- /dev/null > +++ b/www/manager6/sdn/fabrics/ospf/FabricEdit.js > @@ -0,0 +1,60 @@ > +Ext.define('PVE.sdn.Fabric.Ospf.Fabric.Edit', { > + extend: 'Proxmox.window.Edit', > + xtype: 'pveSDNOpenFabricRouteEdit', > + > + subject: gettext('Add OSPF'), > + > + url: '/cluster/sdn/fabrics/ospf', > + type: 'ospf', > + > + isCreate: undefined, > + > + viewModel: { > + data: { > + isCreate: true, > + }, > + }, > + > + items: [ > + { > + xtype: 'textfield', > + name: 'type', > + value: 'ospf', > + allowBlank: false, > + hidden: true, > + }, > + { > + xtype: 'textfield', > + fieldLabel: gettext('Area'), > + labelWidth: 120, > + name: 'name', > + allowBlank: false, > + bind: { > + disabled: '{!isCreate}', > + }, > + }, > + ], > + > + submitUrl: function(url, values) { > + let me = this; > + return `${me.url}`; > + }, > + > + initComponent: function() { > + let me = this; > + > + let view = me.getViewModel(); > + view.set('isCreate', me.isCreate); > + > + me.method = me.isCreate ? 'POST' : 'PUT'; > + > + me.callParent(); > + if (!me.isCreate) { > + me.load({ > + success: function(response, opts) { > + me.setValues(response.result.data.fabric); > + }, > + }); > + } > + }, > +}); > diff --git a/www/manager6/sdn/fabrics/ospf/InterfaceEdit.js b/www/manager6/sdn/fabrics/ospf/InterfaceEdit.js > new file mode 100644 > index 000000000000..e7810b3f34c9 > --- /dev/null > +++ b/www/manager6/sdn/fabrics/ospf/InterfaceEdit.js > @@ -0,0 +1,46 @@ > +Ext.define('PVE.sdn.Fabric.Ospf.Interface.Edit', { > + extend: 'Proxmox.window.Edit', > + xtype: 'pveSDNOspfInterfaceEdit', > + > + initComponent: function() { > + let me = this; > + > + Ext.apply(me, { > + items: [{ > + xtype: 'inputpanel', > + items: [ > + { > + xtype: 'textfield', > + fieldLabel: gettext('Interface'), > + name: 'name', > + disabled: true, > + }, > + { > + xtype: 'proxmoxcheckbox', > + fieldLabel: gettext('Passive'), > + name: 'passive', > + uncheckedValue: 0, > + }, > + ], > + }], > + }); > + > + me.callParent(); > + }, > +}); > + > +Ext.define('PVE.sdn.Fabric.Ospf.InterfacePanel', { > + extend: 'PVE.sdn.Fabric.InterfacePanel', > + > + additionalColumns: [ > + { > + text: gettext('Passive'), > + xtype: 'widgetcolumn', > + dataIndex: 'passive', > + flex: 1, > + widget: { > + xtype: 'checkbox', > + }, > + }, > + ], > +}); > diff --git a/www/manager6/sdn/fabrics/ospf/NodeEdit.js b/www/manager6/sdn/fabrics/ospf/NodeEdit.js > new file mode 100644 > index 000000000000..41778e930bfb > --- /dev/null > +++ b/www/manager6/sdn/fabrics/ospf/NodeEdit.js > @@ -0,0 +1,191 @@ > +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); > + return { > + node: values.node.node, > + router_id: values.node.router_id, > + interfaces: values.node.interface, > + }; > + } 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.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('Router-Id'), > + 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, > + > + // dummyurl > + url: '/cluster/sdn/fabrics/ospf', > + > + interface_selector: undefined, > + 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.interface_selector = Ext.create('PVE.sdn.Fabric.Ospf.InterfacePanel', { > + name: 'interfaces', > + }); > + > + 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.node.interface = fabricResult.node.interface > + .map(i => PVE.Parser.parsePropertyString(i)); > + fabricResult.network_interfaces = clusterResult; > + // this will also set them as selected > + ipanel.setValues(fabricResult); > + }); > + }); > + } 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.node.interface = fabricResult.node.interface > + .map(i => PVE.Parser.parsePropertyString(i)); > + ipanel.setValues(fabricResult); > + }); > + } > + }); > + } > + > + if (me.isCreate) { > + me.method = 'POST'; > + } else { > + me.method = 'PUT'; > + } > + }, > +}); _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel