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