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