[pve-devel] [PATCH v12 pve-manager 1/4] sdn: add subnet/ipam/sdn management

Alexandre Derumier aderumier at odiso.com
Wed Apr 21 23:53:36 CEST 2021


Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 www/manager6/Makefile                    |  16 +++
 www/manager6/Utils.js                    | 104 ++++++++++++++
 www/manager6/dc/Config.js                |  18 +--
 www/manager6/form/SDNDnsSelector.js      |  52 +++++++
 www/manager6/form/SDNIpamSelector.js     |  52 +++++++
 www/manager6/form/SDNVnetSelector.js     |  68 +++++++++
 www/manager6/sdn/Browser.js              |   4 +-
 www/manager6/sdn/ControllerView.js       |  43 +++++-
 www/manager6/sdn/DnsView.js              | 132 ++++++++++++++++++
 www/manager6/sdn/IpamView.js             | 133 ++++++++++++++++++
 www/manager6/sdn/OptionsPanel.js         |  41 ++++++
 www/manager6/sdn/Status.js               |   2 +-
 www/manager6/sdn/SubnetEdit.js           | 104 ++++++++++++++
 www/manager6/sdn/SubnetView.js           | 169 +++++++++++++++++++++++
 www/manager6/sdn/VnetEdit.js             |  37 ++---
 www/manager6/sdn/VnetPanel.js            |  39 ++++++
 www/manager6/sdn/VnetView.js             |  77 ++++++-----
 www/manager6/sdn/ZoneContentView.js      |  15 +-
 www/manager6/sdn/ZoneView.js             |  76 +++++++++-
 www/manager6/sdn/controllers/BgpEdit.js  |  62 +++++++++
 www/manager6/sdn/controllers/EvpnEdit.js |  13 --
 www/manager6/sdn/dns/Base.js             |  73 ++++++++++
 www/manager6/sdn/dns/PowerdnsEdit.js     |  52 +++++++
 www/manager6/sdn/ipams/Base.js           |  73 ++++++++++
 www/manager6/sdn/ipams/NetboxEdit.js     |  46 ++++++
 www/manager6/sdn/ipams/PVEIpamEdit.js    |  34 +++++
 www/manager6/sdn/ipams/PhpIpamEdit.js    |  52 +++++++
 www/manager6/sdn/zones/Base.js           |  60 ++++++--
 www/manager6/sdn/zones/EvpnEdit.js       | 105 +++++++-------
 www/manager6/sdn/zones/SimpleEdit.js     |  16 +--
 www/manager6/sdn/zones/VlanEdit.js       |  14 +-
 www/manager6/sdn/zones/VxlanEdit.js      |  81 ++++++-----
 32 files changed, 1646 insertions(+), 217 deletions(-)
 create mode 100644 www/manager6/form/SDNDnsSelector.js
 create mode 100644 www/manager6/form/SDNIpamSelector.js
 create mode 100644 www/manager6/form/SDNVnetSelector.js
 create mode 100644 www/manager6/sdn/DnsView.js
 create mode 100644 www/manager6/sdn/IpamView.js
 create mode 100644 www/manager6/sdn/OptionsPanel.js
 create mode 100644 www/manager6/sdn/SubnetEdit.js
 create mode 100644 www/manager6/sdn/SubnetView.js
 create mode 100644 www/manager6/sdn/VnetPanel.js
 create mode 100644 www/manager6/sdn/controllers/BgpEdit.js
 create mode 100644 www/manager6/sdn/dns/Base.js
 create mode 100644 www/manager6/sdn/dns/PowerdnsEdit.js
 create mode 100644 www/manager6/sdn/ipams/Base.js
 create mode 100644 www/manager6/sdn/ipams/NetboxEdit.js
 create mode 100644 www/manager6/sdn/ipams/PVEIpamEdit.js
 create mode 100644 www/manager6/sdn/ipams/PhpIpamEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index a2f7be6d..878c0796 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -52,6 +52,9 @@ JSSRC= 							\
 	form/QemuBiosSelector.js			\
 	form/SDNControllerSelector.js			\
 	form/SDNZoneSelector.js				\
+	form/SDNVnetSelector.js				\
+	form/SDNIpamSelector.js				\
+	form/SDNDnsSelector.js				\
 	form/ScsiHwSelector.js				\
 	form/SecurityGroupSelector.js			\
 	form/SnapshotSelector.js			\
@@ -227,10 +230,23 @@ JSSRC= 							\
 	sdn/StatusView.js				\
 	sdn/VnetEdit.js					\
 	sdn/VnetView.js					\
+	sdn/VnetPanel.js				\
+	sdn/SubnetEdit.js				\
+	sdn/SubnetView.js				\
 	sdn/ZoneContentView.js				\
 	sdn/ZoneView.js					\
+	sdn/OptionsPanel.js				\
 	sdn/controllers/Base.js				\
 	sdn/controllers/EvpnEdit.js			\
+	sdn/controllers/BgpEdit.js			\
+	sdn/IpamView.js					\
+	sdn/ipams/Base.js				\
+	sdn/ipams/NetboxEdit.js				\
+	sdn/ipams/PVEIpamEdit.js			\
+	sdn/ipams/PhpIpamEdit.js			\
+	sdn/DnsView.js					\
+	sdn/dns/Base.js					\
+	sdn/dns/PowerdnsEdit.js				\
 	sdn/zones/Base.js				\
 	sdn/zones/EvpnEdit.js				\
 	sdn/zones/QinQEdit.js				\
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index af8b77e7..644d9a6f 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -184,6 +184,54 @@ Ext.define('PVE.Utils', {
 	'HEALTH_ERR': 'critical',
     },
 
+    render_sdn_pending: function(rec,value,key, index) {
+	if (rec.data.state === undefined || rec.data.state === null) {
+	    return value;
+	}
+
+	if (rec.data.state === 'deleted') {
+	    if (value === undefined) {
+		return ' ';
+	    } else {
+		return '<div style="text-decoration: line-through;">'+ value +'</div>';
+	    }
+	} else {
+
+	    if (rec.data.pending[key] !== undefined && rec.data.pending[key] !== null) {
+		if (rec.data.pending[key] === 'deleted') {
+		    return ' ';
+		} else {
+		    return rec.data.pending[key];
+		}
+	    } else {
+		return value;
+	    }
+	}
+	return value;
+    },
+
+    render_sdn_pending_state: function(rec,value) {
+
+	if (value === undefined || value === null) {
+	    return ' ';
+	}
+
+	let icon = `<i class="fa fa-fw fa-refresh warning"></i>`;
+
+	if (value === 'deleted') {
+	    return '<span>' + icon + value + '</span>';
+	}
+
+	let tip = 'Pending apply: <br>';
+
+	for (const [key, keyvalue] of Object.entries(rec.data.pending)) {
+	    if (((rec.data[key] !== undefined && rec.data.pending[key] !== rec.data[key]) || rec.data[key] === undefined)) {
+		tip = tip + `${key}: ${keyvalue} <br>`;
+	    }
+	}
+	return '<span data-qtip="' + tip + '">'+ icon + value + '</span>';
+    },
+
     render_ceph_health: function(healthObj) {
 	var state = {
 	    iconCls: PVE.Utils.get_health_icon(),
@@ -862,6 +910,46 @@ Ext.define('PVE.Utils', {
 	    ipanel: 'EvpnInputPanel',
 	    faIcon: 'crosshairs',
 	},
+	bgp: {
+	    name: 'bgp',
+	    ipanel: 'BgpInputPanel',
+	    faIcon: 'crosshairs'
+	},
+    },
+
+    sdnipamSchema: {
+	ipam: {
+	     name: 'ipam',
+	     hideAdd: true
+	},
+	pve: {
+	    name: 'PVE',
+	    ipanel: 'PVEIpamInputPanel',
+	    faIcon: 'th',
+	    hideAdd: true
+	},
+	netbox: {
+	    name: 'Netbox',
+	    ipanel: 'NetboxInputPanel',
+	    faIcon: 'th'
+	},
+	phpipam: {
+	    name: 'PhpIpam',
+	    ipanel: 'PhpIpamInputPanel',
+	    faIcon: 'th'
+	},
+    },
+
+    sdndnsSchema: {
+	dns: {
+	     name: 'dns',
+	     hideAdd: true
+	},
+	powerdns: {
+	    name: 'powerdns',
+	    ipanel: 'PowerdnsInputPanel',
+	    faIcon: 'th'
+	},
     },
 
     format_sdnvnet_type: function(value, md, record) {
@@ -888,6 +976,22 @@ Ext.define('PVE.Utils', {
 	return Proxmox.Utils.unknownText;
     },
 
+    format_sdnipam_type: function(value, md, record) {
+	var schema = PVE.Utils.sdnipamSchema[value];
+	if (schema) {
+	    return schema.name;
+	}
+	return Proxmox.Utils.unknownText;
+    },
+
+    format_sdndns_type: function(value, md, record) {
+	var schema = PVE.Utils.sdndnsSchema[value];
+	if (schema) {
+	    return schema.name;
+	}
+	return Proxmox.Utils.unknownText;
+    },
+
     format_storage_type: function(value, md, record) {
 	if (value === 'rbd') {
 	    value = !record || record.get('monhost') ? 'rbd' : 'pveceph';
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 24ecabb6..8c850a23 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -153,14 +153,6 @@ Ext.define('PVE.dc.Config', {
 		    itemId: 'sdn',
 		    expandedOnInit: true,
 		},
-		{
-		    xtype: 'pveSDNControllerView',
-		    groups: ['sdn'],
-		    title: gettext('Controllers'),
-		    hidden: true,
-		    iconCls: 'fa fa-crosshairs',
-		    itemId: 'sdncontroller',
-		},
 		{
 		    xtype: 'pveSDNZoneView',
 		    groups: ['sdn'],
@@ -170,12 +162,20 @@ Ext.define('PVE.dc.Config', {
 		    itemId: 'sdnzone',
 		},
 		{
-		    xtype: 'pveSDNVnetView',
+		    xtype: 'pveSDNVnet',
 		    groups: ['sdn'],
 		    title: gettext('Vnets'),
 		    hidden: true,
 		    iconCls: 'fa fa-network-wired',
 		    itemId: 'sdnvnet',
+		},
+		{
+		    xtype: 'pveSDNOptions',
+		    groups: ['sdn'],
+		    title: gettext('Options'),
+		    hidden: true,
+		    iconCls: 'fa fa-gear',
+		    itemId: 'sdnoptions'
 		});
 	    }
 
diff --git a/www/manager6/form/SDNDnsSelector.js b/www/manager6/form/SDNDnsSelector.js
new file mode 100644
index 00000000..7abb1f01
--- /dev/null
+++ b/www/manager6/form/SDNDnsSelector.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.form.SDNDnsSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: ['widget.pveSDNDnsSelector'],
+
+    allowBlank: false,
+    valueField: 'dns',
+    displayField: 'dns',
+
+    initComponent: function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-dns',
+            sorters: {
+                property: 'dns',
+                order: 'DESC'
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    autoSelect: false,
+            listConfig: {
+		columns: [
+		    {
+			header: gettext('dns'),
+			sortable: true,
+			dataIndex: 'dns',
+			flex: 1
+		    },
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-sdn-dns', {
+	extend: 'Ext.data.Model',
+	fields: [ 'dns' ],
+	proxy: {
+            type: 'proxmox',
+	    url: "/api2/json/cluster/sdn/dns"
+	},
+	idProperty: 'dns'
+    });
+
+});
diff --git a/www/manager6/form/SDNIpamSelector.js b/www/manager6/form/SDNIpamSelector.js
new file mode 100644
index 00000000..5520d0fe
--- /dev/null
+++ b/www/manager6/form/SDNIpamSelector.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.form.SDNIpamSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: ['widget.pveSDNIpamSelector'],
+
+    allowBlank: false,
+    valueField: 'ipam',
+    displayField: 'ipam',
+
+    initComponent: function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-ipam',
+            sorters: {
+                property: 'ipam',
+                order: 'DESC'
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    autoSelect: false,
+            listConfig: {
+		columns: [
+		    {
+			header: gettext('Ipam'),
+			sortable: true,
+			dataIndex: 'ipam',
+			flex: 1
+		    },
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-sdn-ipam', {
+	extend: 'Ext.data.Model',
+	fields: [ 'ipam' ],
+	proxy: {
+            type: 'proxmox',
+	    url: "/api2/json/cluster/sdn/ipams"
+	},
+	idProperty: 'ipam'
+    });
+
+});
diff --git a/www/manager6/form/SDNVnetSelector.js b/www/manager6/form/SDNVnetSelector.js
new file mode 100644
index 00000000..0f9a6613
--- /dev/null
+++ b/www/manager6/form/SDNVnetSelector.js
@@ -0,0 +1,68 @@
+Ext.define('PVE.form.SDNVnetSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: ['widget.pveSDNVnetSelector'],
+
+    allowBlank: false,
+    valueField: 'vnet',
+    displayField: 'vnet',
+
+    initComponent: function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-vnet',
+            sorters: {
+                property: 'vnet',
+                order: 'DESC'
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    autoSelect: false,
+            listConfig: {
+		columns: [
+		    {
+			header: gettext('Vnet'),
+			sortable: true,
+			dataIndex: 'vnet',
+			flex: 1
+		    },
+		    {
+			header: gettext('Alias'),
+			flex: 1,
+			dataIndex: 'alias',
+		    },
+		    {
+			header: gettext('Tag'),
+			flex: 1,
+			dataIndex: 'tag',
+		    }
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-sdn-vnet', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    'alias',
+	    'tag',
+	    'type',
+	    'vnet',
+	    'zone',
+	],
+	proxy: {
+            type: 'proxmox',
+	    url: "/api2/json/cluster/sdn/vnets"
+	},
+	idProperty: 'vnet'
+    });
+
+});
diff --git a/www/manager6/sdn/Browser.js b/www/manager6/sdn/Browser.js
index 35a5d13a..bb30cbe0 100644
--- a/www/manager6/sdn/Browser.js
+++ b/www/manager6/sdn/Browser.js
@@ -2,8 +2,10 @@ Ext.define('PVE.sdn.Browser', {
     extend: 'PVE.panel.Config',
     alias: 'widget.PVE.sdn.Browser',
 
+    onlineHelp: 'chapter_pvesdn',
+
     initComponent: function() {
-        var me = this;
+	var me = this;
 
 	var nodename = me.pveSelNode.data.node;
 	if (!nodename) {
diff --git a/www/manager6/sdn/ControllerView.js b/www/manager6/sdn/ControllerView.js
index 90a76df3..ee4b6853 100644
--- a/www/manager6/sdn/ControllerView.js
+++ b/www/manager6/sdn/ControllerView.js
@@ -2,7 +2,7 @@ Ext.define('PVE.sdn.ControllerView', {
     extend: 'Ext.grid.GridPanel',
     alias: ['widget.pveSDNControllerView'],
 
-    onlineHelp: 'pvesdn_controller_plugins',
+    onlineHelp: 'pvesdn_config_controllers',
 
     stateful: true,
     stateId: 'grid-sdn-controller',
@@ -30,8 +30,8 @@ Ext.define('PVE.sdn.ControllerView', {
 	var store = new Ext.data.Store({
 	    model: 'pve-sdn-controller',
 	    proxy: {
-                type: 'proxmox',
-		url: "/api2/json/cluster/sdn/controllers",
+		type: 'proxmox',
+		url: "/api2/json/cluster/sdn/controllers?pending=1",
 	    },
 	    sorters: {
 		property: 'controller',
@@ -45,6 +45,16 @@ Ext.define('PVE.sdn.ControllerView', {
 
 	var sm = Ext.create('Ext.selection.RowModel', {});
 
+	var set_button_status = function() {
+	    var rec = me.selModel.getSelection()[0];
+
+	    if (!rec || rec.data.state === 'deleted') {
+		edit_btn.disable();
+		remove_btn.disable();
+		return;
+	    }
+	};
+
 	var run_editor = function() {
 	    var rec = sm.getSelection()[0];
 	    if (!rec) {
@@ -110,21 +120,44 @@ Ext.define('PVE.sdn.ControllerView', {
 		    flex: 2,
 		    sortable: true,
 		    dataIndex: 'controller',
+		    dataIndex: 'controller',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'controller', 1);
+		    }
 		},
 		{
 		    header: gettext('Type'),
 		    flex: 1,
 		    sortable: true,
 		    dataIndex: 'type',
-		    renderer: PVE.Utils.format_sdncontroller_type,
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'type', 1);
+		    }
 		},
+		{
+		    header: gettext('Node'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'node',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'node', 1);
+		    }
+		},
+		{
+		    header: gettext('State'),
+		    width: 100,
+		    dataIndex: 'state',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending_state(rec, value);
+		    }
+		}
 	    ],
 	    listeners: {
 		activate: reload,
 		itemdblclick: run_editor,
 	    },
 	});
-
+	store.load();
 	me.callParent();
     },
 });
diff --git a/www/manager6/sdn/DnsView.js b/www/manager6/sdn/DnsView.js
new file mode 100644
index 00000000..2b36b0e0
--- /dev/null
+++ b/www/manager6/sdn/DnsView.js
@@ -0,0 +1,132 @@
+Ext.define('PVE.sdn.DnsView', {
+    extend: 'Ext.grid.GridPanel',
+    alias: ['widget.pveSDNDnsView'],
+
+    stateful: true,
+    stateId: 'grid-sdn-dns',
+
+    createSDNEditWindow: function(type, sid) {
+	let schema = PVE.Utils.sdndnsSchema[type];
+	if (!schema || !schema.ipanel) {
+	    throw "no editor registered for dns type: " + type;
+	}
+
+	Ext.create('PVE.sdn.dns.BaseEdit', {
+	    paneltype: 'PVE.sdn.dns.' + schema.ipanel,
+	    type: type,
+	    dns: sid,
+	    autoShow: true,
+	    listeners: {
+		destroy: this.reloadStore
+	    }
+	});
+    },
+
+    initComponent : function() {
+	let me = this;
+
+	let store = new Ext.data.Store({
+	    model: 'pve-sdn-dns',
+	    proxy: {
+		type: 'proxmox',
+		url: "/api2/json/cluster/sdn/dns"
+	    },
+		sorters: {
+		property: 'dns',
+		order: 'DESC'
+	    },
+	});
+
+	let reload = function() {
+	    store.load();
+	};
+
+	let sm = Ext.create('Ext.selection.RowModel', {});
+
+	let run_editor = function() {
+	    let rec = sm.getSelection()[0];
+	    if (!rec) {
+		return;
+	    }
+	    let type = rec.data.type,
+		dns = rec.data.dns;
+
+	    me.createSDNEditWindow(type, dns);
+	};
+
+	let edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor
+	});
+
+	let remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: '/cluster/sdn/dns/',
+	    callback: reload
+	});
+
+	// else we cannot dynamically generate the add menu handlers
+	let addHandleGenerator = function(type) {
+	    return function() { me.createSDNEditWindow(type); };
+	};
+	let addMenuItems = [], type;
+
+	for (type in PVE.Utils.sdndnsSchema) {
+	    let dns = PVE.Utils.sdndnsSchema[type];
+	    if (dns.hideAdd) {
+		continue;
+	    }
+	    addMenuItems.push({
+		text:  PVE.Utils.format_sdndns_type(type),
+		iconCls: 'fa fa-fw fa-' + dns.faIcon,
+		handler: addHandleGenerator(type)
+	    });
+	}
+
+	Ext.apply(me, {
+	    store: store,
+	    reloadStore: reload,
+	    selModel: sm,
+	    viewConfig: {
+		trackOver: false
+	    },
+	    tbar: [
+		{
+		    text: gettext('Add'),
+		    menu: new Ext.menu.Menu({
+			items: addMenuItems
+		    })
+		},
+		remove_btn,
+		edit_btn,
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    dataIndex: 'dns'
+		},
+		{
+		    header: gettext('Type'),
+		    flex: 1,
+		    dataIndex: 'type',
+		    renderer: PVE.Utils.format_sdndns_type
+		},
+		{
+		    header: 'url',
+		    flex: 1,
+		    dataIndex: 'url',
+		},
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor
+	    }
+	});
+
+	store.load();
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/IpamView.js b/www/manager6/sdn/IpamView.js
new file mode 100644
index 00000000..0abf144a
--- /dev/null
+++ b/www/manager6/sdn/IpamView.js
@@ -0,0 +1,133 @@
+Ext.define('PVE.sdn.IpamView', {
+    extend: 'Ext.grid.GridPanel',
+    alias: ['widget.pveSDNIpamView'],
+
+    stateful: true,
+    stateId: 'grid-sdn-ipam',
+
+    createSDNEditWindow: function(type, sid) {
+	let schema = PVE.Utils.sdnipamSchema[type];
+	if (!schema || !schema.ipanel) {
+	    throw "no editor registered for ipam type: " + type;
+	}
+
+	Ext.create('PVE.sdn.ipams.BaseEdit', {
+	    paneltype: 'PVE.sdn.ipams.' + schema.ipanel,
+	    type: type,
+	    ipam: sid,
+	    autoShow: true,
+	    listeners: {
+		destroy: this.reloadStore
+	    }
+	});
+    },
+
+    initComponent : function() {
+	let me = this;
+
+	let store = new Ext.data.Store({
+	    model: 'pve-sdn-ipam',
+	    proxy: {
+		type: 'proxmox',
+		url: "/api2/json/cluster/sdn/ipams"
+	    },
+	    sorters: {
+		property: 'ipam',
+		order: 'DESC'
+	    },
+	});
+
+	let reload = function() {
+	    store.load();
+	};
+
+	let sm = Ext.create('Ext.selection.RowModel', {});
+
+	let run_editor = function() {
+	    let rec = sm.getSelection()[0];
+	    if (!rec) {
+		return;
+	    }
+	    let type = rec.data.type,
+	        ipam = rec.data.ipam;
+
+	    me.createSDNEditWindow(type, ipam);
+	};
+
+	let edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor
+	});
+
+	let remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: '/cluster/sdn/ipams/',
+	    callback: reload
+	});
+
+	// else we cannot dynamically generate the add menu handlers
+	let addHandleGenerator = function(type) {
+	    return function() { me.createSDNEditWindow(type); };
+	};
+	let addMenuItems = [], type;
+
+	for (type in PVE.Utils.sdnipamSchema) {
+	    let ipam = PVE.Utils.sdnipamSchema[type];
+	    if (ipam.hideAdd) {
+		continue;
+	    }
+	    addMenuItems.push({
+		text:  PVE.Utils.format_sdnipam_type(type),
+		iconCls: 'fa fa-fw fa-' + ipam.faIcon,
+		handler: addHandleGenerator(type)
+	    });
+	}
+
+	Ext.apply(me, {
+	    store: store,
+	    reloadStore: reload,
+	    selModel: sm,
+	    viewConfig: {
+		trackOver: false
+	    },
+	    tbar: [
+		{
+		    text: gettext('Add'),
+		    menu: new Ext.menu.Menu({
+			items: addMenuItems
+		    })
+		},
+		remove_btn,
+		edit_btn,
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    dataIndex: 'ipam'
+		},
+		{
+		    header: gettext('Type'),
+		    flex: 1,
+		    dataIndex: 'type',
+		    renderer: PVE.Utils.format_sdnipam_type
+		},
+		{
+		    header: 'url',
+		    flex: 1,
+		    dataIndex: 'url',
+		},
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor
+	    }
+	});
+
+	store.load();
+	me.callParent();
+
+    }
+});
diff --git a/www/manager6/sdn/OptionsPanel.js b/www/manager6/sdn/OptionsPanel.js
new file mode 100644
index 00000000..5f3b583f
--- /dev/null
+++ b/www/manager6/sdn/OptionsPanel.js
@@ -0,0 +1,41 @@
+Ext.define('PVE.sdn.Options', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pveSDNOptions',
+
+    title: 'Options',
+
+    layout: {
+	type: 'vbox',
+	align: 'stretch'
+    },
+
+    onlineHelp: 'pvesdn_config_controllers',
+
+    initComponent: function() {
+	var me = this;
+
+	me.items = [
+	{
+	    xtype: 'pveSDNControllerView',
+	    title: gettext('Controllers'),
+	    border: 0,
+	    collapsible: true,
+	    padding: '0 0 20 0'
+	},
+	{
+	    xtype: 'pveSDNIpamView',
+	    title: gettext('Ipams'),
+	    border: 0,
+	    collapsible: true,
+	    padding: '0 0 20 0'
+	},{
+	    xtype: 'pveSDNDnsView',
+	    flex: 1,
+	    collapsible: true,
+	    title: gettext('Dns'),
+	    border: 0,
+	}];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/Status.js b/www/manager6/sdn/Status.js
index 858d09bd..0fdcfda0 100644
--- a/www/manager6/sdn/Status.js
+++ b/www/manager6/sdn/Status.js
@@ -18,7 +18,7 @@ Ext.define('PVE.sdn.Status', {
 	    storeid: 'pve-store-' + ++Ext.idSeed,
 	    groupField: 'type',
 	    proxy: {
-                type: 'proxmox',
+		type: 'proxmox',
 		url: '/api2/json/cluster/resources',
 	    },
 	});
diff --git a/www/manager6/sdn/SubnetEdit.js b/www/manager6/sdn/SubnetEdit.js
new file mode 100644
index 00000000..83c6961c
--- /dev/null
+++ b/www/manager6/sdn/SubnetEdit.js
@@ -0,0 +1,104 @@
+Ext.define('PVE.sdn.SubnetInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    onGetValues: function(values) {
+	let me = this;
+
+	if (me.isCreate) {
+	    values.type = 'subnet';
+	    values.subnet = values.cidr;
+	    delete values.cidr;
+	}
+
+	if (!values.gateway) {
+	    delete values.gateway;
+	}
+	if (!values.snat) {
+	    delete values.snat;
+	}
+
+	return values;
+    },
+
+    items: [
+	{
+	    xtype: 'pmxDisplayEditField',
+	    name: 'cidr',
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	    flex: 1,
+	    allowBlank: false,
+	    fieldLabel: gettext('Subnet'),
+	},
+	{
+	    xtype: 'textfield',
+	    name: 'gateway',
+	    vtype: 'IP64Address',
+	    fieldLabel: gettext('Gateway'),
+	    allowBlank: true,
+	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    name: 'snat',
+	    uncheckedValue: 0,
+	    checked: false,
+	    fieldLabel: 'SNAT'
+	},
+	{
+	    xtype: 'proxmoxtextfield',
+	    name: 'dnszoneprefix',
+	    skipEmptyText: true,
+	    fieldLabel: gettext('DNS zone prefix'),
+	    allowBlank: true
+	}
+    ]
+});
+
+Ext.define('PVE.sdn.SubnetEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    subject: gettext('Subnet'),
+
+    subnet: undefined,
+
+    width: 350,
+
+    base_url: undefined,
+
+    initComponent: function() {
+	var me = this;
+
+	me.isCreate = me.subnet === undefined;
+
+	if (me.isCreate) {
+	    me.url = me.base_url;
+	    me.method = 'POST';
+	} else {
+	    me.url = me.base_url + '/' + me.subnet;
+	    me.method = 'PUT';
+	}
+
+	let ipanel = Ext.create('PVE.sdn.SubnetInputPanel', {
+	    isCreate: me.isCreate,
+	});
+
+	Ext.apply(me, {
+	    items: [
+		ipanel,
+	    ],
+	});
+
+	me.callParent();
+
+	if (!me.isCreate) {
+	    me.load({
+		success: function(response, options) {
+		    let values = response.result.data;
+		    ipanel.setValues(values);
+		},
+	    });
+	}
+    },
+});
diff --git a/www/manager6/sdn/SubnetView.js b/www/manager6/sdn/SubnetView.js
new file mode 100644
index 00000000..9a85bbbc
--- /dev/null
+++ b/www/manager6/sdn/SubnetView.js
@@ -0,0 +1,169 @@
+Ext.define('PVE.sdn.SubnetView', {
+    extend: 'Ext.grid.GridPanel',
+    alias: 'widget.pveSDNSubnetView',
+
+    stateful: true,
+    stateId: 'grid-sdn-subnet',
+
+    base_url: undefined,
+
+    remove_btn: undefined,
+
+    setBaseUrl: function(url) {
+        var me = this;
+
+        me.base_url = url;
+
+        if (url === undefined) {
+            me.store.removeAll();
+        } else {
+            me.remove_btn.baseurl = url + '/';
+            me.store.setProxy({
+                type: 'proxmox',
+                url: '/api2/json/' + url + '?pending=1'
+            });
+
+            me.store.load();
+        }
+    },
+
+    initComponent : function() {
+	let me = this;
+
+        var store = new Ext.data.Store({
+            model: 'pve-sdn-subnet'
+        });
+
+        var reload = function() {
+            store.load();
+        };
+
+	let sm = Ext.create('Ext.selection.RowModel', {});
+
+	var set_button_status = function() {
+	    var rec = me.selModel.getSelection()[0];
+
+	    if (!rec || rec.data.state === 'deleted') {
+		edit_btn.disable();
+		remove_btn.disable();
+		return;
+	    }
+	};
+
+        let run_editor = function() {
+	    let rec = sm.getSelection()[0];
+
+	    let win = Ext.create('PVE.sdn.SubnetEdit',{
+		autoShow: true,
+		subnet: rec.data.subnet,
+		base_url: me.base_url,
+	    });
+	    win.on('destroy', reload);
+        };
+
+	let edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor,
+	});
+
+	me.remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: me.base_url + '/',
+            callback: function() {
+                reload();
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    reloadStore: reload,
+	    selModel: sm,
+	    viewConfig: {
+		trackOver: false
+	    },
+	    tbar: [
+		{
+		    text: gettext('Create'),
+		    handler: function() {
+			let win = Ext.create('PVE.sdn.SubnetEdit', {
+			    autoShow: true,
+			    base_url: me.base_url,
+			    type: 'subnet',
+			});
+			win.on('destroy', reload);
+		    }
+		},
+		me.remove_btn,
+		edit_btn,
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    dataIndex: 'cidr',
+                    renderer: function(value, metaData, rec) {
+                        return PVE.Utils.render_sdn_pending(rec, value, 'cidr', 1);
+                    }
+		},
+		{
+		    header: gettext('Gateway'),
+		    flex: 1,
+		    dataIndex: 'gateway',
+                    renderer: function(value, metaData, rec) {
+                        return PVE.Utils.render_sdn_pending(rec, value, 'gateway');
+                    }
+		},
+		{
+		    header: 'SNAT',
+		    flex: 1,
+		    dataIndex: 'snat',
+                    renderer: function(value, metaData, rec) {
+                        return PVE.Utils.render_sdn_pending(rec, value, 'snat');
+                    }
+		},
+		{
+		    header: gettext('Dns prefix'),
+		    flex: 1,
+		    dataIndex: 'dnszoneprefix',
+                    renderer: function(value, metaData, rec) {
+                        return PVE.Utils.render_sdn_pending(rec, value, 'dnszoneprefix');
+		    }
+                },
+		{
+		    header: gettext('State'),
+		    width: 100,
+		    dataIndex: 'state',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending_state(rec, value);
+		    }
+		}
+
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor,
+                selectionchange: set_button_status
+	    }
+	});
+
+	me.callParent();
+
+        if (me.base_url) {
+            me.setBaseUrl(me.base_url); // load
+        }
+    }
+}, function() {
+
+    Ext.define('pve-sdn-subnet', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    'cidr',
+	    'gateway',
+	    'snat',
+	],
+	idProperty: 'subnet'
+    });
+
+});
diff --git a/www/manager6/sdn/VnetEdit.js b/www/manager6/sdn/VnetEdit.js
index e959ffd1..1ed20994 100644
--- a/www/manager6/sdn/VnetEdit.js
+++ b/www/manager6/sdn/VnetEdit.js
@@ -9,12 +9,10 @@ Ext.define('PVE.sdn.VnetInputPanel', {
 	    values.type = 'vnet';
 	}
 
-	if (!values.ipv6) {
-	    delete values.ipv6;
-	}
-	if (!values.ipv4) {
-	    delete values.ipv4;
+	if (!values.vlanaware) {
+	    delete values.vlanaware;
 	}
+
 	if (!values.mac) {
 	    delete values.mac;
 	}
@@ -61,35 +59,16 @@ Ext.define('PVE.sdn.VnetInputPanel', {
 	    uncheckedValue: 0,
 	    checked: false,
 	    fieldLabel: gettext('VLAN Aware'),
-	},
-	{
-	    xtype: 'textfield',
-	    name: 'mac',
-	    fieldLabel: gettext('MAC Address'),
-	    vtype: 'MacAddress',
-	    skipEmptyText: true,
-	    allowBlank: true,
-	    emptyText: 'auto',
-	},
+	}
     ],
     advancedItems: [
 	{
 	    xtype: 'textfield',
-	    name: 'ipv4',
-	    vtype: 'IPCIDRAddress',
-	    fieldLabel: 'IPv4/CIDR', // do not localize
-	    emptyText: 'Optional anycast addr. for BGP',
-	    skipEmptyText: true,
-	    allowBlank: true,
-	},
-	{
-	    xtype: 'textfield',
-	    name: 'ipv6',
-	    vtype: 'IP6CIDRAddress',
-	    fieldLabel: 'IPv6/CIDR', // do not localize
-	    emptyText: 'Optional anycast addr. for BGP',
-	    skipEmptyText: true,
+	    name: 'mac',
+	    fieldLabel: gettext('MAC address'),
+	    vtype: 'MacAddress',
 	    allowBlank: true,
+	    emptyText: 'auto'
 	},
     ],
 });
diff --git a/www/manager6/sdn/VnetPanel.js b/www/manager6/sdn/VnetPanel.js
new file mode 100644
index 00000000..414b6095
--- /dev/null
+++ b/www/manager6/sdn/VnetPanel.js
@@ -0,0 +1,39 @@
+Ext.define('PVE.sdn.Vnet', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pveSDNVnet',
+
+    title: 'Vnet',
+
+    onlineHelp: 'pvesdn_config_vnet',
+
+    initComponent: function() {
+	var me = this;
+
+	var subnetview_panel = Ext.createWidget('pveSDNSubnetView', {
+	    title: gettext('Subnets'),
+	    region: 'center',
+	    border: false
+	});
+
+	var vnetview_panel = Ext.createWidget('pveSDNVnetView', {
+	    title: 'Vnets',
+	    region: 'west',
+	    subnetview_panel: subnetview_panel,
+	    width: '50%',
+	    border: false,
+	    split: true
+	});
+
+	Ext.apply(me, {
+	    layout: 'border',
+	    items: [ vnetview_panel, subnetview_panel ],
+	    listeners: {
+		show: function() {
+		    subnetview_panel.fireEvent('show', subnetview_panel);
+		}
+	    }
+	});
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/VnetView.js b/www/manager6/sdn/VnetView.js
index e2897c9f..e8ad4410 100644
--- a/www/manager6/sdn/VnetView.js
+++ b/www/manager6/sdn/VnetView.js
@@ -7,6 +7,8 @@ Ext.define('PVE.sdn.VnetView', {
     stateful: true,
     stateId: 'grid-sdn-vnet',
 
+    subnetview_panel: undefined,
+
     initComponent: function() {
 	let me = this;
 
@@ -14,17 +16,28 @@ Ext.define('PVE.sdn.VnetView', {
 	    model: 'pve-sdn-vnet',
 	    proxy: {
                 type: 'proxmox',
-		url: "/api2/json/cluster/sdn/vnets",
+		url: "/api2/json/cluster/sdn/vnets?pending=1",
 	    },
 	    sorters: {
 		property: 'vnet',
 		order: 'DESC',
 	    },
 	});
+
 	let reload = () => store.load();
 
 	let sm = Ext.create('Ext.selection.RowModel', {});
 
+	var set_button_status = function() {
+	    var rec = me.selModel.getSelection()[0];
+
+	    if (!rec || rec.data.state === 'deleted') {
+		edit_btn.disable();
+		remove_btn.disable();
+		return;
+	    }
+	};
+
         let run_editor = function() {
 	    let rec = sm.getSelection()[0];
 
@@ -76,64 +89,66 @@ Ext.define('PVE.sdn.VnetView', {
 		    header: 'ID',
 		    flex: 2,
 		    dataIndex: 'vnet',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'vnet', 1);
+		    }
 		},
 		{
 		    header: gettext('Alias'),
 		    flex: 1,
 		    dataIndex: 'alias',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'alias');
+		    }
 		},
 		{
 		    header: gettext('Zone'),
 		    flex: 1,
 		    dataIndex: 'zone',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'zone');
+		    }
 		},
 		{
 		    header: gettext('Tag'),
 		    flex: 1,
 		    dataIndex: 'tag',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'tag');
+		    }
 		},
 		{
 		    header: gettext('VLAN Aware'),
 		    flex: 1,
 		    dataIndex: 'vlanaware',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'vlanaware');
+		    }
 		},
 		{
-		    header: 'IPv4/CIDR',
-		    flex: 1,
-		    dataIndex: 'ipv4',
-		},
-		{
-		    header: 'IPv6/CIDR',
-		    flex: 1,
-		    dataIndex: 'ipv6',
-		},
-		{
-		    header: 'MAC',
-		    flex: 1,
-		    dataIndex: 'mac',
-		},
+		    header: gettext('State'),
+		    width: 100,
+		    dataIndex: 'state',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending_state(rec, value);
+		    }
+		}
 	    ],
 	    listeners: {
 		activate: reload,
 		itemdblclick: run_editor,
+		selectionchange: set_button_status,
+		show: reload,
+		select: function(sm, rec) {
+		    var url = '/cluster/sdn/vnets/' + rec.data.vnet + '/subnets';
+		    me.subnetview_panel.setBaseUrl(url);
+		},
+		deselect: function() {
+		    me.subnetview_panel.setBaseUrl(undefined);
+		},
 	    },
 	});
-
+	store.load();
 	me.callParent();
     },
-}, function() {
-    Ext.define('pve-sdn-vnet', {
-	extend: 'Ext.data.Model',
-	fields: [
-	    'alias',
-	    'ipv4',
-	    'ipv6',
-	    'mac',
-	    'tag',
-	    'type',
-	    'vnet',
-	    'zone',
-	],
-	idProperty: 'vnet',
-    });
 });
diff --git a/www/manager6/sdn/ZoneContentView.js b/www/manager6/sdn/ZoneContentView.js
index ac9f58fc..d2fb605e 100644
--- a/www/manager6/sdn/ZoneContentView.js
+++ b/www/manager6/sdn/ZoneContentView.js
@@ -32,7 +32,7 @@ Ext.define('PVE.sdn.ZoneContentView', {
 	    model: 'pve-sdnzone-content',
 	    groupField: 'content',
 	    proxy: {
-                type: 'proxmox',
+		type: 'proxmox',
 		url: '/api2/json' + baseurl,
 	    },
 	    sorters: {
@@ -57,18 +57,25 @@ Ext.define('PVE.sdn.ZoneContentView', {
 	    columns: [
 		{
 		    header: 'VNet',
-		    flex: 1,
+		    width: 100,
 		    sortable: true,
 		    dataIndex: 'vnet',
 		},
+		{
+		    header: 'Alias',
+		    width: 300,
+		    sortable: true,
+		    dataIndex: 'alias'
+		},
 		{
 		    header: gettext('Status'),
-		    width: 20,
+		    width: 100,
+		    sortable: true,
 		    dataIndex: 'status',
 		},
 		{
 		    header: gettext('Details'),
-		    width: 20,
+		    flex: 1,
 		    dataIndex: 'statusmsg',
 		},
 	    ],
diff --git a/www/manager6/sdn/ZoneView.js b/www/manager6/sdn/ZoneView.js
index 4d03a7e7..7016948f 100644
--- a/www/manager6/sdn/ZoneView.js
+++ b/www/manager6/sdn/ZoneView.js
@@ -2,7 +2,7 @@ Ext.define('PVE.sdn.ZoneView', {
     extend: 'Ext.grid.GridPanel',
     alias: ['widget.pveSDNZoneView'],
 
-    onlineHelp: 'pvesdn_zone_plugins',
+    onlineHelp: 'pvesdn_config_zone',
 
     stateful: true,
     stateId: 'grid-sdn-zone',
@@ -31,7 +31,7 @@ Ext.define('PVE.sdn.ZoneView', {
 	    model: 'pve-sdn-zone',
 	    proxy: {
                 type: 'proxmox',
-		url: "/api2/json/cluster/sdn/zones",
+		url: "/api2/json/cluster/sdn/zones?pending=1",
 	    },
 	    sorters: {
 		property: 'zone',
@@ -45,13 +45,23 @@ Ext.define('PVE.sdn.ZoneView', {
 
 	let sm = Ext.create('Ext.selection.RowModel', {});
 
+	var set_button_status = function() {
+	    var rec = me.selModel.getSelection()[0];
+
+	    if (!rec || rec.data.state === 'deleted') {
+		edit_btn.disable();
+		remove_btn.disable();
+		return;
+	    }
+	};
+
 	let run_editor = function() {
 	    let rec = sm.getSelection()[0];
 	    if (!rec) {
 		return;
 	    }
 	    let type = rec.data.type,
-	        zone = rec.data.zone;
+		zone = rec.data.zone;
 
 	    me.createSDNEditWindow(type, zone);
 	};
@@ -107,29 +117,81 @@ Ext.define('PVE.sdn.ZoneView', {
 	    columns: [
 		{
 		    header: 'ID',
-		    flex: 2,
+		    width: 100,
 		    dataIndex: 'zone',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'zone', 1);
+		    }
 		},
 		{
 		    header: gettext('Type'),
-		    flex: 1,
+		    width: 100,
 		    dataIndex: 'type',
-		    renderer: PVE.Utils.format_sdnzone_type,
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'type', 1);
+		    }
 		},
 		{
 		    header: 'MTU',
-		    flex: 1,
+		    width: 50,
 		    dataIndex: 'mtu',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'mtu');
+		    }
+		},
+		{
+		    header: 'Ipam',
+		    flex: 3,
+		    dataIndex: 'ipam',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'ipam');
+		    }
+		},
+		{
+		    header: gettext('Domain'),
+		    flex: 3,
+		    dataIndex: 'dnszone',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'dnszone');
+		    }
+		},
+		{
+		    header: gettext('Dns'),
+		    flex: 3,
+		    dataIndex: 'dns',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'dns');
+		    }
+		},
+		{
+		    header: gettext('Reverse dns'),
+		    flex: 3,
+		    dataIndex: 'reversedns',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'reversedns');
+		    }
 		},
 		{
 		    header: gettext('Nodes'),
 		    flex: 3,
 		    dataIndex: 'nodes',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending(rec, value, 'nodes');
+		    }
 		},
+		{
+		    header: gettext('State'),
+		    width: 100,
+		    dataIndex: 'state',
+		    renderer: function(value, metaData, rec) {
+			return PVE.Utils.render_sdn_pending_state(rec, value);
+		    }
+		}
 	    ],
 	    listeners: {
 		activate: reload,
 		itemdblclick: run_editor,
+		selectionchange: set_button_status
 	    },
 	});
 
diff --git a/www/manager6/sdn/controllers/BgpEdit.js b/www/manager6/sdn/controllers/BgpEdit.js
new file mode 100644
index 00000000..2af7a7bd
--- /dev/null
+++ b/www/manager6/sdn/controllers/BgpEdit.js
@@ -0,0 +1,62 @@
+Ext.define('PVE.sdn.controllers.BgpInputPanel', {
+    extend: 'PVE.panel.SDNControllerBase',
+
+    onlineHelp: 'pvesdn_controller_plugin_evpn',
+
+    initComponent : function() {
+	var me = this;
+
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'controller',
+		maxLength: 8,
+		value: me.controllerid || '',
+		fieldLabel: 'ID',
+		allowBlank: false
+	    },
+	    {
+		xtype: 'proxmoxintegerfield',
+		name: 'asn',
+		minValue: 1,
+		maxValue: 4294967295,
+		value: 65000,
+		fieldLabel: 'ASN #',
+		allowBlank: false
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'peers',
+		fieldLabel: gettext('Peers'),
+		allowBlank: false
+	    },
+	    {
+		xtype: 'proxmoxcheckbox',
+		name: 'ebgp',
+		uncheckedValue: 0,
+		checked: false,
+		fieldLabel: 'EBGP'
+	    },
+	    {
+		xtype: 'pveNodeSelector',
+		name: 'node',
+		fieldLabel: gettext('Node'),
+		multiSelect: false,
+		autoSelect: false,
+		allowBlank: false
+	    },
+
+	];
+
+	me.advancedItems = [
+
+	    {
+		xtype: 'textfield',
+		name: 'loopback',
+		fieldLabel: gettext('Loopback Interface'),
+	    },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/controllers/EvpnEdit.js b/www/manager6/sdn/controllers/EvpnEdit.js
index 6c90f818..d04b3e54 100644
--- a/www/manager6/sdn/controllers/EvpnEdit.js
+++ b/www/manager6/sdn/controllers/EvpnEdit.js
@@ -30,19 +30,6 @@ Ext.define('PVE.sdn.controllers.EvpnInputPanel', {
 		fieldLabel: gettext('Peers'),
 		allowBlank: false,
 	    },
-	    {
-		xtype: 'textfield',
-		name: 'gateway-external-peers',
-		fieldLabel: gettext('External Gateway Peers'),
-		allowBlank: true,
-	    },
-	    {
-		xtype: 'pveNodeSelector',
-		name: 'gateway-nodes',
-		fieldLabel: gettext('Gateway Nodes'),
-		multiSelect: true,
-		autoSelect: false,
-	    },
 	];
 
 	me.callParent();
diff --git a/www/manager6/sdn/dns/Base.js b/www/manager6/sdn/dns/Base.js
new file mode 100644
index 00000000..18d93c9f
--- /dev/null
+++ b/www/manager6/sdn/dns/Base.js
@@ -0,0 +1,73 @@
+Ext.define('PVE.panel.SDNDnsBase', {
+    extend: 'Proxmox.panel.InputPanel',
+
+    type: '',
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.dns;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.sdn.dns.BaseEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.dns;
+
+	if (me.isCreate) {
+	    me.url = '/api2/extjs/cluster/sdn/dns';
+	    me.method = 'POST';
+	} else {
+	    me.url = '/api2/extjs/cluster/sdn/dns/' + me.dns;
+	    me.method = 'PUT';
+	}
+
+	var ipanel = Ext.create(me.paneltype, {
+	    type: me.type,
+	    isCreate: me.isCreate,
+	    dns: me.dns
+	});
+
+	Ext.apply(me, {
+            subject: PVE.Utils.format_sdndns_type(me.type),
+	    isAdd: true,
+	    items: [ ipanel ]
+	});
+
+	me.callParent();
+
+	if (!me.isCreate) {
+	    me.load({
+		success:  function(response, options) {
+		    var values = response.result.data;
+		    var ctypes = values.content || '';
+
+		    values.content = ctypes.split(',');
+
+		    if (values.nodes) {
+			values.nodes = values.nodes.split(',');
+		    }
+		    values.enable = values.disable ? 0 : 1;
+
+		    ipanel.setValues(values);
+		}
+	    });
+	}
+    }
+});
diff --git a/www/manager6/sdn/dns/PowerdnsEdit.js b/www/manager6/sdn/dns/PowerdnsEdit.js
new file mode 100644
index 00000000..3834693c
--- /dev/null
+++ b/www/manager6/sdn/dns/PowerdnsEdit.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.sdn.dns.PowerdnsInputPanel', {
+    extend: 'PVE.panel.SDNDnsBase',
+
+    onlineHelp: 'pvesdn_dns_plugin_powerdns',
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.dns;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'dns',
+		maxLength: 10,
+		value: me.dns || '',
+		fieldLabel: 'ID',
+		allowBlank: false
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'url',
+		fieldLabel: 'url',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'key',
+		fieldLabel: gettext('api key'),
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'proxmoxintegerfield',
+		name: 'ttl',
+		fieldLabel: 'ttl',
+		allowBlank: true
+	    },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/ipams/Base.js b/www/manager6/sdn/ipams/Base.js
new file mode 100644
index 00000000..0874eaf3
--- /dev/null
+++ b/www/manager6/sdn/ipams/Base.js
@@ -0,0 +1,73 @@
+Ext.define('PVE.panel.SDNIpamBase', {
+    extend: 'Proxmox.panel.InputPanel',
+
+    type: '',
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.ipam;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.sdn.ipams.BaseEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.ipam;
+
+	if (me.isCreate) {
+	    me.url = '/api2/extjs/cluster/sdn/ipams';
+	    me.method = 'POST';
+	} else {
+	    me.url = '/api2/extjs/cluster/sdn/ipams/' + me.ipam;
+	    me.method = 'PUT';
+	}
+
+	var ipanel = Ext.create(me.paneltype, {
+	    type: me.type,
+	    isCreate: me.isCreate,
+	    ipam: me.ipam
+	});
+
+	Ext.apply(me, {
+	    subject: PVE.Utils.format_sdnipam_type(me.type),
+	    isAdd: true,
+	    items: [ ipanel ]
+	});
+
+	me.callParent();
+
+	if (!me.isCreate) {
+	    me.load({
+		success:  function(response, options) {
+		    var values = response.result.data;
+		    var ctypes = values.content || '';
+
+		    values.content = ctypes.split(',');
+
+		    if (values.nodes) {
+			values.nodes = values.nodes.split(',');
+		    }
+		    values.enable = values.disable ? 0 : 1;
+
+		    ipanel.setValues(values);
+		}
+	    });
+	}
+    }
+});
diff --git a/www/manager6/sdn/ipams/NetboxEdit.js b/www/manager6/sdn/ipams/NetboxEdit.js
new file mode 100644
index 00000000..50de571a
--- /dev/null
+++ b/www/manager6/sdn/ipams/NetboxEdit.js
@@ -0,0 +1,46 @@
+Ext.define('PVE.sdn.ipams.NetboxInputPanel', {
+    extend: 'PVE.panel.SDNIpamBase',
+
+    onlineHelp: 'pvesdn_ipam_plugin_netbox',
+
+    onGetValues: function(values) {
+        var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.ipam;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'ipam',
+		maxLength: 10,
+		value: me.zone || '',
+		fieldLabel: 'ID',
+		allowBlank: false
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'url',
+		fieldLabel: gettext('Url'),
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'token',
+		fieldLabel: gettext('Token'),
+		allowBlank: false,
+	    },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/ipams/PVEIpamEdit.js b/www/manager6/sdn/ipams/PVEIpamEdit.js
new file mode 100644
index 00000000..43b70106
--- /dev/null
+++ b/www/manager6/sdn/ipams/PVEIpamEdit.js
@@ -0,0 +1,34 @@
+Ext.define('PVE.sdn.ipams.PVEIpamInputPanel', {
+    extend: 'PVE.panel.SDNIpamBase',
+
+    onlineHelp: 'pvesdn_ipam_plugin_pveipam',
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.ipam;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'ipam',
+		maxLength: 10,
+		value: me.zone || '',
+		fieldLabel: 'ID',
+		allowBlank: false
+	    },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/ipams/PhpIpamEdit.js b/www/manager6/sdn/ipams/PhpIpamEdit.js
new file mode 100644
index 00000000..8a974fb3
--- /dev/null
+++ b/www/manager6/sdn/ipams/PhpIpamEdit.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.sdn.ipams.PhpIpamInputPanel', {
+    extend: 'PVE.panel.SDNIpamBase',
+
+    onlineHelp: 'pvesdn_ipam_plugin_phpipam',
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.ipam;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'ipam',
+		maxLength: 10,
+		value: me.zone || '',
+		fieldLabel: 'ID',
+		allowBlank: false
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'url',
+		fieldLabel: gettext('Url'),
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'token',
+		fieldLabel: gettext('Token'),
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'section',
+		fieldLabel: gettext('Section'),
+		allowBlank: false,
+	    },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/zones/Base.js b/www/manager6/sdn/zones/Base.js
index 1d99ca64..168ba54b 100644
--- a/www/manager6/sdn/zones/Base.js
+++ b/www/manager6/sdn/zones/Base.js
@@ -4,28 +4,59 @@ Ext.define('PVE.panel.SDNZoneBase', {
     type: '',
 
     onGetValues: function(values) {
-        var me = this;
+	var me = this;
 
-        if (me.isCreate) {
-            values.type = me.type;
-        } else {
-            delete values.zone;
-        }
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.zone;
+	}
 
-        return values;
+	return values;
     },
 
     initComponent: function() {
-        var me = this;
+	var me = this;
 
-        me.callParent();
-    },
+	me.advancedItems = [
+	    {
+		xtype: 'pveSDNIpamSelector',
+		fieldLabel: gettext('Ipam'),
+		name: 'ipam',
+		value: 'pve',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'pveSDNDnsSelector',
+		fieldLabel: gettext('Dns server'),
+		name: 'dns',
+		value: '',
+		allowBlank: true,
+	    },
+	    {
+		xtype: 'pveSDNDnsSelector',
+		fieldLabel: gettext('Reverse Dns server'),
+		name: 'reversedns',
+		value: '',
+		allowBlank: true,
+	    },
+	    {
+		xtype: 'proxmoxtextfield',
+		name: 'dnszone',
+		skipEmptyText: true,
+		fieldLabel: gettext('DNS zone'),
+		allowBlank: true
+	    },
+	];
+
+	me.callParent();
+    }
 });
 
 Ext.define('PVE.sdn.zones.BaseEdit', {
     extend: 'Proxmox.window.Edit',
 
-    initComponent: function() {
+    initComponent : function() {
 	var me = this;
 
 	me.isCreate = !me.zone;
@@ -45,7 +76,7 @@ Ext.define('PVE.sdn.zones.BaseEdit', {
 	});
 
 	Ext.apply(me, {
-            subject: PVE.Utils.format_sdnzone_type(me.type),
+	    subject: PVE.Utils.format_sdnzone_type(me.type),
 	    isAdd: true,
 	    items: [ipanel],
 	});
@@ -63,6 +94,11 @@ Ext.define('PVE.sdn.zones.BaseEdit', {
 		    if (values.nodes) {
 			values.nodes = values.nodes.split(',');
 		    }
+
+		    if (values.exitnodes) {
+			values.exitnodes = values.exitnodes.split(',');
+		    }
+
 		    values.enable = values.disable ? 0 : 1;
 
 		    ipanel.setValues(values);
diff --git a/www/manager6/sdn/zones/EvpnEdit.js b/www/manager6/sdn/zones/EvpnEdit.js
index d930b95d..65777190 100644
--- a/www/manager6/sdn/zones/EvpnEdit.js
+++ b/www/manager6/sdn/zones/EvpnEdit.js
@@ -4,62 +4,69 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', {
     onlineHelp: 'pvesdn_zone_plugin_evpn',
 
     onGetValues: function(values) {
-        var me = this;
+	var me = this;
 
-        if (me.isCreate) {
-            values.type = me.type;
-        } else {
-            delete values.zone;
-        }
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.zone;
+	}
 
-        return values;
+	return values;
     },
 
     initComponent: function() {
 	var me = this;
 
-        me.items = [
-           {
-            xtype: me.isCreate ? 'textfield' : 'displayfield',
-            name: 'zone',
-            maxLength: 8,
-            value: me.zone || '',
-            fieldLabel: 'ID',
-            allowBlank: false,
-          },
-	  {
-	    xtype: 'proxmoxintegerfield',
-	    name: 'vrf-vxlan',
-	    minValue: 1,
-	    maxValue: 16000000,
-	    fieldLabel: 'VRF-VXLAN Tag',
-	    allowBlank: false,
-	  },
-	  {
-	    xtype: 'pveSDNControllerSelector',
-	    fieldLabel: gettext('Controller'),
-	    name: 'controller',
-	    value: '',
-	    allowBlank: false,
-	  },
-          {
-            xtype: 'proxmoxintegerfield',
-            name: 'mtu',
-            minValue: 100,
-            maxValue: 65000,
-            fieldLabel: 'MTU',
-            skipEmptyText: true,
-            allowBlank: true,
-            emptyText: 'auto',
-          },
-          {
-            xtype: 'pveNodeSelector',
-            name: 'nodes',
-            fieldLabel: gettext('Nodes'),
-            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
-            multiSelect: true,
-            autoSelect: false,
-          },
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		name: 'zone',
+		maxLength: 8,
+		value: me.zone || '',
+		fieldLabel: 'ID',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'proxmoxintegerfield',
+		name: 'vrf-vxlan',
+		minValue: 1,
+		maxValue: 16000000,
+		fieldLabel: 'VRF-VXLAN Tag',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'pveNodeSelector',
+		name: 'exitnodes',
+		fieldLabel: gettext('Exit Nodes'),
+		multiSelect: true,
+		autoSelect: false
+	    },
+	    {
+		xtype: 'pveSDNControllerSelector',
+		fieldLabel: gettext('Controller'),
+		name: 'controller',
+		value: '',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'proxmoxintegerfield',
+		name: 'mtu',
+		minValue: 100,
+		maxValue: 65000,
+		fieldLabel: 'MTU',
+		skipEmptyText: true,
+		allowBlank: true,
+		emptyText: 'auto',
+	    },
+	    {
+		xtype: 'pveNodeSelector',
+		name: 'nodes',
+		fieldLabel: gettext('Nodes'),
+		emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+		multiSelect: true,
+		autoSelect: false,
+	    },
 
 	];
 
diff --git a/www/manager6/sdn/zones/SimpleEdit.js b/www/manager6/sdn/zones/SimpleEdit.js
index 8cff5e16..56df7952 100644
--- a/www/manager6/sdn/zones/SimpleEdit.js
+++ b/www/manager6/sdn/zones/SimpleEdit.js
@@ -1,18 +1,18 @@
 Ext.define('PVE.sdn.zones.SimpleInputPanel', {
     extend: 'PVE.panel.SDNZoneBase',
 
-    //onlineHelp: 'pvesdn_zone_plugin_simple', // FIXME uncomment once doc-gen is updated
+    onlineHelp: 'pvesdn_zone_plugin_simple',
 
     onGetValues: function(values) {
-        var me = this;
+	var me = this;
 
-        if (me.isCreate) {
-            values.type = me.type;
-        } else {
-            delete values.zone;
-        }
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.zone;
+	}
 
-        return values;
+	return values;
     },
 
     initComponent: function() {
diff --git a/www/manager6/sdn/zones/VlanEdit.js b/www/manager6/sdn/zones/VlanEdit.js
index db1587b7..93d2bede 100644
--- a/www/manager6/sdn/zones/VlanEdit.js
+++ b/www/manager6/sdn/zones/VlanEdit.js
@@ -4,15 +4,15 @@ Ext.define('PVE.sdn.zones.VlanInputPanel', {
     onlineHelp: 'pvesdn_zone_plugin_vlan',
 
     onGetValues: function(values) {
-        var me = this;
+	var me = this;
 
-        if (me.isCreate) {
-            values.type = me.type;
-        } else {
-            delete values.zone;
-        }
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.zone;
+	}
 
-        return values;
+	return values;
     },
 
     initComponent: function() {
diff --git a/www/manager6/sdn/zones/VxlanEdit.js b/www/manager6/sdn/zones/VxlanEdit.js
index 5c5b2675..41cc7e68 100644
--- a/www/manager6/sdn/zones/VxlanEdit.js
+++ b/www/manager6/sdn/zones/VxlanEdit.js
@@ -4,56 +4,55 @@ Ext.define('PVE.sdn.zones.VxlanInputPanel', {
     onlineHelp: 'pvesdn_zone_plugin_vxlan',
 
     onGetValues: function(values) {
-        var me = this;
+	var me = this;
 
-        if (me.isCreate) {
-            values.type = me.type;
-        } else {
-            delete values.zone;
-        }
+	if (me.isCreate) {
+	    values.type = me.type;
+	} else {
+	    delete values.zone;
+	}
 
 	delete values.mode;
 
-        return values;
+	return values;
     },
 
     initComponent: function() {
 	var me = this;
 
-        me.items = [
-           {
-            xtype: me.isCreate ? 'textfield' : 'displayfield',
-	    maxLength: 8,
-            name: 'zone',
-            value: me.zone || '',
-            fieldLabel: 'ID',
-            allowBlank: false,
-          },
-	  {
-	    xtype: 'textfield',
-	    name: 'peers',
-	    fieldLabel: gettext('Peer Address List'),
-	    allowBlank: false,
-	  },
-          {
-            xtype: 'proxmoxintegerfield',
-            name: 'mtu',
-            minValue: 100,
-            maxValue: 65000,
-            fieldLabel: 'MTU',
-            skipEmptyText: true,
-            allowBlank: true,
-            emptyText: 'auto',
-          },
-          {
-            xtype: 'pveNodeSelector',
-            name: 'nodes',
-            fieldLabel: gettext('Nodes'),
-            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
-            multiSelect: true,
-            autoSelect: false,
-          },
-
+	me.items = [
+	    {
+		xtype: me.isCreate ? 'textfield' : 'displayfield',
+		maxLength: 8,
+		name: 'zone',
+		value: me.zone || '',
+		fieldLabel: 'ID',
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'textfield',
+		name: 'peers',
+		fieldLabel: gettext('Peer Address List'),
+		allowBlank: false,
+	    },
+	    {
+		xtype: 'proxmoxintegerfield',
+		name: 'mtu',
+		minValue: 100,
+		maxValue: 65000,
+		fieldLabel: 'MTU',
+		skipEmptyText: true,
+		allowBlank: true,
+		emptyText: 'auto',
+	    },
+	    {
+		xtype: 'pveNodeSelector',
+		name: 'nodes',
+		fieldLabel: gettext('Nodes'),
+		emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+		multiSelect: true,
+		autoSelect: false,
+	    },
 	];
 
 	me.callParent();
-- 
2.20.1





More information about the pve-devel mailing list