[pve-devel] [PATCH pve-manager 10/11] sdn: add fabric edit/delete forms
Stefan Hanreich
s.hanreich at proxmox.com
Tue Mar 4 11:07:55 CET 2025
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 at proxmox.com>
> Signed-off-by: Gabriel Goller <g.goller at 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';
> + }
> + },
> +});
More information about the pve-devel
mailing list