Signed-off-by: Alexandre Derumier <[email protected]>
---
 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


_______________________________________________
pve-devel mailing list
[email protected]
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to