[pve-devel] [PATCH v2 pve-manager 4/4] add sdn gui

Alexandre Derumier aderumier at odiso.com
Tue Nov 26 10:01:27 CET 2019


Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 www/manager6/Makefile                      |  17 +++
 www/manager6/Makefile.rej                  |   9 ++
 www/manager6/StateProvider.js              |   4 +-
 www/manager6/Utils.js                      |  70 +++++++++
 www/manager6/Workspace.js                  |   1 +
 www/manager6/dc/Config.js                  |  33 +++++
 www/manager6/dc/PoolEdit.js                |  13 +-
 www/manager6/form/SDNControllerSelector.js |  52 +++++++
 www/manager6/form/SDNZoneSelector.js       |  52 +++++++
 www/manager6/sdn/Browser.js                |  49 +++++++
 www/manager6/sdn/ControllerView.js         | 146 +++++++++++++++++++
 www/manager6/sdn/Status.js                 |  36 +++++
 www/manager6/sdn/StatusView.js             |  98 +++++++++++++
 www/manager6/sdn/VnetEdit.js               | 143 ++++++++++++++++++
 www/manager6/sdn/VnetView.js               | 159 +++++++++++++++++++++
 www/manager6/sdn/ZoneContentView.js        | 105 ++++++++++++++
 www/manager6/sdn/ZoneView.js               | 158 ++++++++++++++++++++
 www/manager6/sdn/controllers/Base.js       |  73 ++++++++++
 www/manager6/sdn/controllers/EvpnEdit.js   |  55 +++++++
 www/manager6/sdn/zones/Base.js             |  73 ++++++++++
 www/manager6/sdn/zones/EvpnEdit.js         |  64 +++++++++
 www/manager6/sdn/zones/QinQEdit.js         |  63 ++++++++
 www/manager6/sdn/zones/VlanEdit.js         |  49 +++++++
 www/manager6/sdn/zones/VxlanEdit.js        | 106 ++++++++++++++
 www/manager6/tree/ResourceTree.js          |   4 +
 25 files changed, 1630 insertions(+), 2 deletions(-)
 create mode 100644 www/manager6/Makefile.rej
 create mode 100644 www/manager6/form/SDNControllerSelector.js
 create mode 100644 www/manager6/form/SDNZoneSelector.js
 create mode 100644 www/manager6/sdn/Browser.js
 create mode 100644 www/manager6/sdn/ControllerView.js
 create mode 100644 www/manager6/sdn/Status.js
 create mode 100644 www/manager6/sdn/StatusView.js
 create mode 100644 www/manager6/sdn/VnetEdit.js
 create mode 100644 www/manager6/sdn/VnetView.js
 create mode 100644 www/manager6/sdn/ZoneContentView.js
 create mode 100644 www/manager6/sdn/ZoneView.js
 create mode 100644 www/manager6/sdn/controllers/Base.js
 create mode 100644 www/manager6/sdn/controllers/EvpnEdit.js
 create mode 100644 www/manager6/sdn/zones/Base.js
 create mode 100644 www/manager6/sdn/zones/EvpnEdit.js
 create mode 100644 www/manager6/sdn/zones/QinQEdit.js
 create mode 100644 www/manager6/sdn/zones/VlanEdit.js
 create mode 100644 www/manager6/sdn/zones/VxlanEdit.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index b7ffc44b..2ebd5a0e 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -68,6 +68,8 @@ JSSRC= 				                 	\
 	form/CephPoolSelector.js			\
 	form/PermPathSelector.js			\
 	form/SpiceEnhancementSelector.js		\
+	form/SDNZoneSelector.js   	                \
+	form/SDNControllerSelector.js  	                \
 	dc/Tasks.js					\
 	dc/Log.js					\
 	panel/StatusPanel.js				\
@@ -191,6 +193,21 @@ JSSRC= 				                 	\
 	storage/RBDEdit.js				\
 	storage/ZFSEdit.js				\
 	storage/ZFSPoolEdit.js				\
+	sdn/Browser.js					\
+	sdn/Status.js					\
+	sdn/StatusView.js				\
+	sdn/ZoneView.js					\
+	sdn/ZoneContentView.js				\
+	sdn/VnetView.js					\
+	sdn/VnetEdit.js					\
+	sdn/zones/Base.js				\
+	sdn/zones/VlanEdit.js				\
+	sdn/zones/VxlanEdit.js				\
+	sdn/zones/QinQEdit.js				\
+	sdn/zones/EvpnEdit.js				\
+	sdn/ControllerView.js				\
+	sdn/controllers/Base.js				\
+	sdn/controllers/EvpnEdit.js			\
 	ha/StatusView.js				\
 	ha/Status.js					\
 	ha/GroupSelector.js				\
diff --git a/www/manager6/Makefile.rej b/www/manager6/Makefile.rej
new file mode 100644
index 00000000..e8f2f84d
--- /dev/null
+++ b/www/manager6/Makefile.rej
@@ -0,0 +1,9 @@
+diff a/www/manager6/Makefile b/www/manager6/Makefile	(rejected hunks)
+@@ -66,6 +66,7 @@ JSSRC= 				                 	\
+ 	form/CalendarEvent.js				\
+ 	form/CephPoolSelector.js				\
+ 	form/PermPathSelector.js				\
++	form/SDNTransportSelector.js			\
+ 	dc/Tasks.js					\
+ 	dc/Log.js					\
+ 	panel/StatusPanel.js				\
diff --git a/www/manager6/StateProvider.js b/www/manager6/StateProvider.js
index 81c76401..821c27d2 100644
--- a/www/manager6/StateProvider.js
+++ b/www/manager6/StateProvider.js
@@ -40,6 +40,7 @@ Ext.define('PVE.StateProvider', {
 	['ltab', 'tasks'],
 	['nodetab', ''],
 	['storagetab', ''],
+	['sdntab', ''],
 	['pooltab', ''],
 	['kvmtab', ''],
 	['lxctab', ''],
@@ -49,6 +50,7 @@ Ext.define('PVE.StateProvider', {
     hprefix: 'v1',
 
     compDict: {
+	sdn: 53,
 	cloudinit: 52,
 	replication: 51,
 	system: 50,
@@ -221,7 +223,7 @@ Ext.define('PVE.StateProvider', {
 	} else {
 	    data = me.callParent(arguments);
 	    if (!data && name === 'GuiCap') {
-		data = { vms: {}, storage: {}, access: {}, nodes: {}, dc: {} };
+		data = { vms: {}, storage: {}, access: {}, nodes: {}, dc: {}, sdn: {} };
 	    }
 	}
 
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index ad75c871..8edfbe2b 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -619,6 +619,76 @@ Ext.define('PVE.Utils', { utilities: {
 	}
     },
 
+    sdnvnetSchema: {
+	vnet: {
+	    name: 'vnet',
+	    faIcon: 'folder'
+	},
+    },
+
+    sdnzoneSchema: {
+	zone: {
+	     name: 'zone',
+	     hideAdd: true
+	},
+	vlan: {
+	    name: 'vlan',
+	    ipanel: 'VlanInputPanel',
+	    faIcon: 'folder'
+	},
+	qinq: {
+	    name: 'qinq',
+	    ipanel: 'QinQInputPanel',
+	    faIcon: 'folder'
+	},
+	vxlan: {
+	    name: 'vxlan',
+	    ipanel: 'VxlanInputPanel',
+	    faIcon: 'folder'
+	},
+	evpn: {
+	    name: 'evpn',
+	    ipanel: 'EvpnInputPanel',
+	    faIcon: 'folder'
+	},
+    },
+
+    sdncontrollerSchema: {
+	controller: {
+	     name: 'controller',
+	     hideAdd: true
+	},
+	evpn: {
+	    name: 'evpn',
+	    ipanel: 'EvpnInputPanel',
+	    faIcon: 'folder'
+	},
+    },
+
+    format_sdnvnet_type: function(value, md, record) {
+	var schema = PVE.Utils.sdnvnetSchema[value];
+	if (schema) {
+	    return schema.name;
+	}
+	return Proxmox.Utils.unknownText;
+    },
+
+    format_sdnzone_type: function(value, md, record) {
+	var schema = PVE.Utils.sdnzoneSchema[value];
+	if (schema) {
+	    return schema.name;
+	}
+	return Proxmox.Utils.unknownText;
+    },
+
+    format_sdncontroller_type: function(value, md, record) {
+	var schema = PVE.Utils.sdncontrollerSchema[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/Workspace.js b/www/manager6/Workspace.js
index 335e7e61..1cc86999 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -210,6 +210,7 @@ Ext.define('PVE.StdWorkspace', {
 				qemu: 'PVE.qemu.Config',
 				lxc: 'PVE.lxc.Config',
 				storage: 'PVE.storage.Browser',
+				sdn: 'PVE.sdn.Browser',
 				pool: 'pvePoolConfig'
 			    };
 			    var comp = {
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 52cd106f..bbc4f5c7 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -56,6 +56,39 @@ Ext.define('PVE.dc.Config', {
 	    });
 	}
 
+	if (caps.dc['Sys.Audit']) {
+
+	    me.items.push({
+		xtype: 'pveSDNStatus',
+		title: gettext('SDN'),
+		iconCls: 'fa fa-unlock',
+		itemId: 'sdn',
+		expandedOnInit: true
+	    });
+
+	    me.items.push({
+		xtype: 'pveSDNControllerView',
+		groups: ['sdn'],
+		title: gettext('Controllers'),
+		iconCls: 'fa fa-database',
+		itemId: 'sdncontroller'
+	    });
+	    me.items.push({
+		xtype: 'pveSDNZoneView',
+		groups: ['sdn'],
+		title: gettext('Zones'),
+		iconCls: 'fa fa-database',
+		itemId: 'sdnzone'
+	    });
+	    me.items.push({
+		xtype: 'pveSDNVnetView',
+		groups: ['sdn'],
+		title: gettext('Vnets'),
+		iconCls: 'fa fa-database',
+		itemId: 'sdnvnet'
+	    });
+	}
+
 	if (caps.dc['Sys.Audit']) {
 	    me.items.push({
 		xtype: 'pveDcBackupView',
diff --git a/www/manager6/dc/PoolEdit.js b/www/manager6/dc/PoolEdit.js
index afc24a66..b17d0b8a 100644
--- a/www/manager6/dc/PoolEdit.js
+++ b/www/manager6/dc/PoolEdit.js
@@ -43,6 +43,17 @@ Ext.define('PVE.dc.PoolEdit', {
 
         if (!me.isCreate) {
             me.load();
-        }
+        } else {
+	    me.type = 'vnet'
+/*
+                    for (i = 0; i < 100; i++) {
+                        confid = 'net' + i.toString();
+                        if (!Ext.isDefined(me.vmconfig[confid])) {
+                            me.confid = confid;
+                            break;
+                        }
+                    }
+*/
+	}
     }
 });
diff --git a/www/manager6/form/SDNControllerSelector.js b/www/manager6/form/SDNControllerSelector.js
new file mode 100644
index 00000000..81ba0f16
--- /dev/null
+++ b/www/manager6/form/SDNControllerSelector.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.form.SDNControllerSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: ['widget.pveSDNControllerSelector'],
+
+    allowBlank: false,
+    valueField: 'controller',
+    displayField: 'controller',
+
+    initComponent: function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-controller',
+            sorters: {
+                property: 'controller',
+                order: 'DESC'
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    autoSelect: false,
+            listConfig: {
+		columns: [
+		    {
+			header: gettext('Controller'),
+			sortable: true,
+			dataIndex: 'controller',
+			flex: 1
+		    },
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-sdn-controller', {
+	extend: 'Ext.data.Model',
+	fields: [ 'controller' ],
+	proxy: {
+            type: 'proxmox',
+	    url: "/api2/json/cluster/sdn/controllers"
+	},
+	idProperty: 'controller'
+    });
+
+});
diff --git a/www/manager6/form/SDNZoneSelector.js b/www/manager6/form/SDNZoneSelector.js
new file mode 100644
index 00000000..da5804ec
--- /dev/null
+++ b/www/manager6/form/SDNZoneSelector.js
@@ -0,0 +1,52 @@
+Ext.define('PVE.form.SDNZoneSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: ['widget.pveSDNZoneSelector'],
+
+    allowBlank: false,
+    valueField: 'zone',
+    displayField: 'zone',
+
+    initComponent: function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-zone',
+            sorters: {
+                property: 'zone',
+                order: 'DESC'
+            },
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    autoSelect: false,
+            listConfig: {
+		columns: [
+		    {
+			header: gettext('Zone'),
+			sortable: true,
+			dataIndex: 'zone',
+			flex: 1
+		    },
+		]
+	    }
+	});
+
+        me.callParent();
+
+	store.load();
+    }
+
+}, function() {
+
+    Ext.define('pve-sdn-zone', {
+	extend: 'Ext.data.Model',
+	fields: [ 'zone' ],
+	proxy: {
+            type: 'proxmox',
+	    url: "/api2/json/cluster/sdn/zones"
+	},
+	idProperty: 'zone'
+    });
+
+});
diff --git a/www/manager6/sdn/Browser.js b/www/manager6/sdn/Browser.js
new file mode 100644
index 00000000..339fedb1
--- /dev/null
+++ b/www/manager6/sdn/Browser.js
@@ -0,0 +1,49 @@
+Ext.define('PVE.sdn.Browser', {
+    extend: 'PVE.panel.Config',
+    alias: 'widget.PVE.sdn.Browser',
+
+    initComponent: function() {
+        var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) {
+	    throw "no node name specified";
+	}
+
+	var sdnid = me.pveSelNode.data.sdn;
+	if (!sdnid) {
+	    throw "no sdn ID specified";
+	}
+
+	me.items = [];
+
+	var caps = Ext.state.Manager.get('GuiCap');
+
+	Ext.apply(me, {
+	    title: Ext.String.format(gettext("Zone {0} on node {1}"),
+				     "'" + sdnid + "'", "'" + nodename + "'"),
+	    hstateid: 'sdntab'
+	});
+
+//	if (caps.sdn['SDN.Audit']) {
+	    me.items.push({
+		xtype: 'pveSDNZoneContentView',
+		title: gettext('Content'),
+		iconCls: 'fa fa-th',
+		itemId: 'content'
+	    });
+//	}
+
+//	if (caps.sdn['Permissions.Modify']) {
+	    me.items.push({
+		xtype: 'pveACLView',
+		title: gettext('Permissions'),
+		iconCls: 'fa fa-unlock',
+		itemId: 'permissions',
+		path: '/sdn/zones/' + sdnid
+	    });
+//	}
+
+	me.callParent();
+   }
+});
diff --git a/www/manager6/sdn/ControllerView.js b/www/manager6/sdn/ControllerView.js
new file mode 100644
index 00000000..68a1bb5b
--- /dev/null
+++ b/www/manager6/sdn/ControllerView.js
@@ -0,0 +1,146 @@
+Ext.define('PVE.sdn.ControllerView', {
+    extend: 'Ext.grid.GridPanel',
+
+    alias: ['widget.pveSDNControllerView'],
+
+    stateful: true,
+    stateId: 'grid-sdn-controller',
+
+    createSDNControllerEditWindow: function(type, sid) {
+	var schema = PVE.Utils.sdncontrollerSchema[type];
+	if (!schema || !schema.ipanel) {
+	    throw "no editor registered for controller type: " + type;
+	}
+
+	Ext.create('PVE.sdn.controllers.BaseEdit', {
+	    paneltype: 'PVE.sdn.controllers.' + schema.ipanel,
+	    type: type,
+	    controllerid: sid,
+	    autoShow: true,
+	    listeners: {
+		destroy: this.reloadStore
+	    }
+	});
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-controller',
+	    proxy: {
+                type: 'proxmox',
+		url: "/api2/json/cluster/sdn/controllers"
+	    },
+	    sorters: {
+		property: 'controller',
+		order: 'DESC'
+	    },
+	});
+
+	var reload = function() {
+	    store.load();
+	};
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+	var run_editor = function() {
+	    var rec = sm.getSelection()[0];
+	    if (!rec) {
+		return;
+	    }
+	    var type = rec.data.type,
+	        controller = rec.data.controller;
+
+	    me.createSDNControllerEditWindow(type, controller);
+	};
+
+	var edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor
+	});
+
+	var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: '/cluster/sdn/controllers/',
+	    callback: reload
+	});
+
+	// else we cannot dynamically generate the add menu handlers
+	var addHandleGenerator = function(type) {
+	    return function() { me.createSDNControllerEditWindow(type); };
+	};
+	var addMenuItems = [], type;
+	/*jslint forin: true */
+
+	for (type in PVE.Utils.sdncontrollerSchema) {
+	    var controller = PVE.Utils.sdncontrollerSchema[type];
+	    if (controller.hideAdd) {
+		continue;
+	    }
+	    addMenuItems.push({
+		text:  PVE.Utils.format_sdncontroller_type(type),
+		iconCls: 'fa fa-fw fa-' + controller.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,
+                {
+                    text: gettext('Revert'),
+                    handler: function() {
+                        Proxmox.Utils.API2Request({
+                            url: '/cluster/sdn/controllers/',
+                            method: 'DELETE',
+                            waitMsgTarget: me,
+                            callback: function() {
+                                reload();
+                            },
+                            failure: function(response, opts) {
+                                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+                            }
+                        });
+                    }
+                },
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    sortable: true,
+		    dataIndex: 'controller'
+		},
+		{
+		    header: gettext('Type'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'type',
+		    renderer: PVE.Utils.format_sdncontroller_type
+		},
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor
+	    }
+	});
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/Status.js b/www/manager6/sdn/Status.js
new file mode 100644
index 00000000..1fe8155c
--- /dev/null
+++ b/www/manager6/sdn/Status.js
@@ -0,0 +1,36 @@
+Ext.define('PVE.sdn.Status', {
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pveSDNStatus',
+
+    layout: {
+	type: 'vbox',
+	align: 'stretch'
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	me.rstore = Ext.create('Proxmox.data.ObjectStore', {
+	    interval: me.interval,
+	    model: 'pve-sdn-status',
+	    storeid: 'pve-store-' + (++Ext.idSeed),
+	    groupField: 'type',
+	    proxy: {
+                type: 'proxmox',
+		url: '/api2/json/cluster/resources'
+	    }
+	});
+
+	me.items = [{
+	    xtype: 'pveSDNStatusView',
+	    title: gettext('Status'),
+	    rstore: me.rstore,
+	    border: 0,
+	    collapsible: true,
+	    padding: '0 0 20 0'
+	}];
+
+	me.callParent();
+	me.on('activate', me.rstore.startUpdate);
+    }
+});
diff --git a/www/manager6/sdn/StatusView.js b/www/manager6/sdn/StatusView.js
new file mode 100644
index 00000000..b0fc903e
--- /dev/null
+++ b/www/manager6/sdn/StatusView.js
@@ -0,0 +1,98 @@
+Ext.define('PVE.sdn.StatusView', {
+    extend: 'Ext.grid.GridPanel',
+    alias: ['widget.pveSDNStatusView'],
+
+    sortPriority: {
+	quorum: 1,
+	master: 2,
+	lrm: 3,
+	service: 4
+    },
+    
+    initComponent : function() {
+	var me = this;
+
+	if (!me.rstore) {
+	    throw "no rstore given";
+	}
+
+	Proxmox.Utils.monStoreErrors(me, me.rstore);
+
+	var store = Ext.create('Proxmox.data.DiffStore', {
+	    rstore: me.rstore,
+	    sortAfterUpdate: true,
+	    sorters: [{
+		sorterFn: function(rec1, rec2) {
+		    var p1 = me.sortPriority[rec1.data.type];
+		    var p2 = me.sortPriority[rec2.data.type];
+		    return (p1 !== p2) ? ((p1 > p2) ? 1 : -1) : 0;
+		}
+	    }],
+	    filters: {
+		property: 'type',
+		value: 'sdn',
+		operator: '=='
+	    }
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    stateful: false,
+            tbar: [
+                {
+                    text: gettext('Apply'),
+                    handler: function() {
+                        Proxmox.Utils.API2Request({
+                            url: '/cluster/sdn/',
+                            method: 'PUT',
+                            waitMsgTarget: me,
+                            failure: function(response, opts) {
+                                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+                            }
+                        });
+                    }
+                },
+            ],
+	    viewConfig: {
+		trackOver: false
+	    },
+	    columns: [
+		{
+		    header: gettext('sdn'),
+		    width: 80,
+		    dataIndex: 'sdn'
+		},
+		{
+		    header: gettext('node'),
+		    width: 80,
+		    dataIndex: 'node'
+		},
+		{
+		    header: gettext('Status'),
+		    width: 80,
+		    flex: 1,
+		    dataIndex: 'status'
+		}
+	    ]
+	});
+
+	me.callParent();
+
+	me.on('activate', me.rstore.startUpdate);
+	me.on('destroy', me.rstore.stopUpdate);	
+
+    }
+}, function() {
+
+    Ext.define('pve-sdn-status', {
+	extend: 'Ext.data.Model',
+	fields: [ 
+	    'id', 'type', 'node', 'status', 'sid',
+	    'state', 'group', 'comment',
+	    'max_restart', 'max_relocate', 'type',
+	    'crm_state', 'request_state'
+	],
+	idProperty: 'id'
+    });
+
+});
diff --git a/www/manager6/sdn/VnetEdit.js b/www/manager6/sdn/VnetEdit.js
new file mode 100644
index 00000000..965aaa05
--- /dev/null
+++ b/www/manager6/sdn/VnetEdit.js
@@ -0,0 +1,143 @@
+Ext.define('PVE.sdn.VnetInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+
+    vnet: undefined,
+
+    onGetValues: function(values) {
+	var me = this;
+
+	if (me.isCreate) {
+	    values.type = 'vnet';
+	}
+
+	if (!values.ipv6) {
+	    delete values.ipv6;
+	}
+
+	if (!values.ipv4) {
+	    delete values.ipv4;
+	}
+
+	if (!values.mac) {
+	    delete values.mac;
+	}
+
+	return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+            me.items = [
+                {
+                    xtype: me.isCreate ? 'proxmoxtextfield' : 'displayfield',
+                    name: 'vnet',
+                    value: me.vnet,
+                    maxLength: 10,
+                    allowBlank: false,
+                    fieldLabel: gettext('Name')
+                },
+                {
+                    xtype: 'textfield',
+                    name: 'alias',
+                    fieldLabel: gettext('alias'),
+                    allowBlank: true
+                },
+                {
+                    xtype: 'pveSDNZoneSelector',
+                    fieldLabel: gettext('Zone'),
+                    name: 'zone',
+                    value: '',
+                    allowBlank: false
+                },
+                {
+                    xtype: 'proxmoxintegerfield',
+                    name: 'tag',
+                    minValue: 1,
+                    maxValue: 16000000,
+                    fieldLabel: gettext('tag'),
+                    allowBlank: false
+                },
+                {
+                    xtype: 'proxmoxintegerfield',
+                    name: 'mtu',
+                    minValue: 100,
+                    maxValue: 65000,
+                    fieldLabel: gettext('mtu'),
+                    skipEmptyText: true,
+                    allowBlank: true,
+                    emptyText: 'auto'
+                },
+                {
+                    xtype: 'textfield',
+                    name: 'ipv4',
+                    vtype: 'IPCIDRAddress',
+                    fieldLabel: gettext('ipv4'),
+                    fieldLabel: 'IPv4/CIDR', // do not localize
+                    skipEmptyText: true,
+                    allowBlank: true,
+                },
+                {
+                    xtype: 'textfield',
+                    name: 'ipv6',
+                    vtype: 'IP6CIDRAddress',
+                    fieldLabel: 'IPv6/CIDR', // do not localize
+                    skipEmptyText: true,
+                    allowBlank: true,
+                },
+                {
+                    xtype: 'textfield',
+                    name: 'mac',
+                    fieldLabel: gettext('MAC address'),
+                    vtype: 'MacAddress',
+                    skipEmptyText: true,
+                    allowBlank: true,
+                    emptyText: 'auto'
+                },
+	     ];
+
+	me.callParent();
+    }
+});
+
+Ext.define('PVE.sdn.VnetEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    vnet: undefined,
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.vnet;
+
+        if (me.isCreate) {
+            me.url = '/api2/extjs/cluster/sdn/vnets';
+            me.method = 'POST';
+        } else {
+            me.url = '/api2/extjs/cluster/sdn/vnets/' + me.vnet;
+            me.method = 'PUT';
+        }
+
+	var ipanel = Ext.create('PVE.sdn.VnetInputPanel', {
+	    isCreate: me.isCreate,
+	    vnet: me.vnet
+	});
+
+	Ext.apply(me, {
+            subject: gettext('Vnet'),
+	    items: [ ipanel ]
+	});
+
+	me.callParent();
+
+	if (!me.isCreate) {
+	    me.load({
+		success:  function(response, options) {
+		    var values = response.result.data;
+
+		    ipanel.setValues(values);
+		}
+	    });
+	}
+    }
+});
diff --git a/www/manager6/sdn/VnetView.js b/www/manager6/sdn/VnetView.js
new file mode 100644
index 00000000..8ef25fae
--- /dev/null
+++ b/www/manager6/sdn/VnetView.js
@@ -0,0 +1,159 @@
+Ext.define('PVE.sdn.VnetView', {
+    extend: 'Ext.grid.GridPanel',
+
+    alias: ['widget.pveSDNVnetView'],
+
+    stateful: true,
+    stateId: 'grid-sdn-vnet',
+
+    initComponent : function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-vnet',
+	    proxy: {
+                type: 'proxmox',
+		url: "/api2/json/cluster/sdn/vnets"
+	    },
+	    sorters: {
+		property: 'vnet',
+		order: 'DESC'
+	    }
+	});
+
+	var reload = function() {
+	    store.load();
+	};
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+        var run_editor = function() {
+            var rec = sm.getSelection()[0];
+
+            var win = Ext.create('PVE.sdn.VnetEdit',{
+                vnet: rec.data.vnet
+            });
+            win.on('destroy', reload);
+            win.show();
+        };
+
+	var edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor
+	});
+
+	var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: '/cluster/sdn/vnets/',
+	    callback: reload
+	});
+
+	Ext.apply(me, {
+	    store: store,
+	    reloadStore: reload,
+	    selModel: sm,
+	    viewConfig: {
+		trackOver: false
+	    },
+	    tbar: [
+                {
+                    text: gettext('Create'),
+                    handler: function() {
+                        var win = Ext.create('PVE.sdn.VnetEdit',{
+			    type: 'vnet'
+			});
+                        win.on('destroy', reload);
+                        win.show();
+                    }
+                },
+		remove_btn,
+		edit_btn,
+                {
+                    text: gettext('Revert'),
+                    handler: function() {
+                        Proxmox.Utils.API2Request({
+                            url: '/cluster/sdn/vnets/',
+                            method: 'DELETE',
+                            waitMsgTarget: me,
+                            callback: function() {
+                                reload();
+                            },
+                            failure: function(response, opts) {
+                                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+                            }
+                        });
+                    }
+                },
+
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    sortable: true,
+		    dataIndex: 'vnet'
+		},
+		{
+		    header: gettext('alias'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'alias',
+		},
+		{
+		    header: gettext('zone'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'zone',
+		},
+		{
+		    header: gettext('tag'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'tag',
+		},
+		{
+		    header: gettext('ipv4'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'ipv4',
+		},
+		{
+		    header: gettext('ipv6'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'ipv6',
+		},
+		{
+		    header: gettext('mac'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'mac',
+		},
+		{
+		    header: gettext('mtu'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'mtu',
+		},
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor
+	    }
+	});
+
+	me.callParent();
+    }
+}, function() {
+
+    Ext.define('pve-sdn-vnet', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    'type'
+	],
+	idProperty: 'vnet'
+    });
+
+});
diff --git a/www/manager6/sdn/ZoneContentView.js b/www/manager6/sdn/ZoneContentView.js
new file mode 100644
index 00000000..29f79a17
--- /dev/null
+++ b/www/manager6/sdn/ZoneContentView.js
@@ -0,0 +1,105 @@
+Ext.define('PVE.sdn.ZoneContentView', {
+    extend: 'Ext.grid.GridPanel',
+
+    alias: 'widget.pveSDNZoneContentView',
+
+    stateful: true,
+    stateId: 'grid-sdnzone-content',
+    viewConfig: {
+	trackOver: false,
+	loadMask: false
+    },
+    features: [
+	{
+	    ftype: 'grouping',
+	    groupHeaderTpl: '{name} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
+	}
+    ],
+    initComponent : function() {
+	var me = this;
+
+	var nodename = me.pveSelNode.data.node;
+	if (!nodename) {
+	    throw "no node name specified";
+	}
+
+	var zone = me.pveSelNode.data.sdn;
+	if (!zone) {
+	    throw "no zone ID specified";
+	}
+
+	var baseurl = "/nodes/" + nodename + "/sdn/zones/" + zone + "/content";
+	var store = Ext.create('Ext.data.Store',{
+	    model: 'pve-sdnzone-content',
+	    groupField: 'content',
+	    proxy: {
+                type: 'proxmox',
+		url: '/api2/json' + baseurl
+	    },
+	    sorters: {
+		property: 'vnet',
+		order: 'DESC'
+	    }
+	});
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+	var reload = function() {
+	    store.load();
+	};
+
+	Proxmox.Utils.monStoreErrors(me, store);
+
+	Ext.apply(me, {
+	    store: store,
+	    selModel: sm,
+	    tbar: [
+	    ],
+	    columns: [
+		{
+		    header: gettext('Vnet'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'vnet'
+		},
+		{
+		    header: gettext('Status'),
+		    width: 20,
+		    dataIndex: 'status',
+		},
+		{
+		    header: gettext('Status details'),
+		    width: 20,
+		    dataIndex: 'statusmsg',
+		},
+	    ],
+	    listeners: {
+		activate: reload
+	    }
+	});
+
+	me.callParent();
+
+    }
+}, function() {
+
+    Ext.define('pve-sdnzone-content', {
+	extend: 'Ext.data.Model',
+	fields: [
+	    'vnet', 'status', 'statusmsg',
+	    {
+		name: 'text',
+		convert: function(value, record) {
+		    // check for volid, because if you click on a grouping header,
+		    // it calls convert (but with an empty volid)
+		    if (value || record.data.vnet === null) {
+			return value;
+		    }
+		    return PVE.Utils.format_sdnvnet_type(value, {}, record);
+		}
+	    }
+	],
+	idProperty: 'vnet'
+    });
+
+});
diff --git a/www/manager6/sdn/ZoneView.js b/www/manager6/sdn/ZoneView.js
new file mode 100644
index 00000000..5b31da94
--- /dev/null
+++ b/www/manager6/sdn/ZoneView.js
@@ -0,0 +1,158 @@
+Ext.define('PVE.sdn.ZoneView', {
+    extend: 'Ext.grid.GridPanel',
+
+    alias: ['widget.pveSDNZoneView'],
+
+    stateful: true,
+    stateId: 'grid-sdn-zone',
+
+    createSDNEditWindow: function(type, sid) {
+	var schema = PVE.Utils.sdnzoneSchema[type];
+	if (!schema || !schema.ipanel) {
+	    throw "no editor registered for zone type: " + type;
+	}
+
+	Ext.create('PVE.sdn.zones.BaseEdit', {
+	    paneltype: 'PVE.sdn.zones.' + schema.ipanel,
+	    type: type,
+	    zone: sid,
+	    autoShow: true,
+	    listeners: {
+		destroy: this.reloadStore
+	    }
+	});
+    },
+
+    initComponent : function() {
+	var me = this;
+
+	var store = new Ext.data.Store({
+	    model: 'pve-sdn-zone',
+	    proxy: {
+                type: 'proxmox',
+		url: "/api2/json/cluster/sdn/zones"
+	    },
+	    sorters: {
+		property: 'zone',
+		order: 'DESC'
+	    },
+	});
+
+	var reload = function() {
+	    store.load();
+	};
+
+	var sm = Ext.create('Ext.selection.RowModel', {});
+
+	var run_editor = function() {
+	    var rec = sm.getSelection()[0];
+	    if (!rec) {
+		return;
+	    }
+	    var type = rec.data.type,
+	        zone = rec.data.zone;
+
+	    me.createSDNEditWindow(type, zone);
+	};
+
+	var edit_btn = new Proxmox.button.Button({
+	    text: gettext('Edit'),
+	    disabled: true,
+	    selModel: sm,
+	    handler: run_editor
+	});
+
+	var remove_btn = Ext.create('Proxmox.button.StdRemoveButton', {
+	    selModel: sm,
+	    baseurl: '/cluster/sdn/zones/',
+	    callback: reload
+	});
+
+	// else we cannot dynamically generate the add menu handlers
+	var addHandleGenerator = function(type) {
+	    return function() { me.createSDNEditWindow(type); };
+	};
+	var addMenuItems = [], type;
+	/*jslint forin: true */
+
+	for (type in PVE.Utils.sdnzoneSchema) {
+	    var zone = PVE.Utils.sdnzoneSchema[type];
+	    if (zone.hideAdd) {
+		continue;
+	    }
+	    addMenuItems.push({
+		text:  PVE.Utils.format_sdnzone_type(type),
+		iconCls: 'fa fa-fw fa-' + zone.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,
+                {
+                    text: gettext('Revert'),
+                    handler: function() {
+                        Proxmox.Utils.API2Request({
+                            url: '/cluster/sdn/zones/',
+                            method: 'DELETE',
+                            waitMsgTarget: me,
+                            callback: function() {
+                                reload();
+                            },
+                            failure: function(response, opts) {
+                                Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+                            }
+                        });
+                    }
+                },
+	    ],
+	    columns: [
+		{
+		    header: 'ID',
+		    flex: 2,
+		    sortable: true,
+		    dataIndex: 'zone'
+		},
+		{
+		    header: gettext('Type'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'type',
+		    renderer: PVE.Utils.format_sdnzone_type
+		},
+		{
+		    header: gettext('Uplink'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'uplink-id',
+		},
+		{
+		    header: gettext('Nodes'),
+		    flex: 1,
+		    sortable: true,
+		    dataIndex: 'nodes',
+		},
+	    ],
+	    listeners: {
+		activate: reload,
+		itemdblclick: run_editor
+	    }
+	});
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/controllers/Base.js b/www/manager6/sdn/controllers/Base.js
new file mode 100644
index 00000000..f045acd6
--- /dev/null
+++ b/www/manager6/sdn/controllers/Base.js
@@ -0,0 +1,73 @@
+Ext.define('PVE.panel.SDNControllerBase', {
+    extend: 'Proxmox.panel.InputPanel',
+
+    type: '',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.controller;
+        }
+
+        return values;
+    },
+
+    initComponent : function() {
+        var me = this;
+
+        me.callParent();
+    }
+});
+
+Ext.define('PVE.sdn.controllers.BaseEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.controllerid;
+
+	if (me.isCreate) {
+            me.url = '/api2/extjs/cluster/sdn/controllers';
+            me.method = 'POST';
+        } else {
+            me.url = '/api2/extjs/cluster/sdn/controllers/' + me.controllerid;
+            me.method = 'PUT';
+        }
+
+	var ipanel = Ext.create(me.paneltype, {
+	    type: me.type,
+	    isCreate: me.isCreate,
+	    controllerid: me.controllerid
+	});
+
+	Ext.apply(me, {
+            subject: PVE.Utils.format_sdncontroller_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/controllers/EvpnEdit.js b/www/manager6/sdn/controllers/EvpnEdit.js
new file mode 100644
index 00000000..98f50838
--- /dev/null
+++ b/www/manager6/sdn/controllers/EvpnEdit.js
@@ -0,0 +1,55 @@
+Ext.define('PVE.sdn.controllers.EvpnInputPanel', {
+    extend: 'PVE.panel.SDNControllerBase',
+
+    initComponent : function() {
+	var me = this;
+
+        me.items = [
+           {
+            xtype: me.isCreate ? 'textfield' : 'displayfield',
+            name: 'controller',
+	    maxLength: 10,
+            value: me.controllerid || '',
+            fieldLabel: 'ID',
+            allowBlank: false
+          },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'uplink-id',
+	    minValue: 1,
+	    maxValue: 4096,
+	    fieldLabel: gettext('uplink-id'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'asn',
+	    minValue: 1,
+	    maxValue: 65536,
+	    fieldLabel: gettext('asn'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'textfield',
+	    name: 'peers',
+	    fieldLabel: gettext('peers'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'textfield',
+	    name: 'gateway-external-peers',
+	    fieldLabel: gettext('gateway-external-peers'),
+	    allowBlank: true
+	  },
+          {
+            xtype: 'pveNodeSelector',
+            name: 'gateway-nodes',
+            fieldLabel: gettext('Gateway nodes'),
+            multiSelect: true,
+            autoSelect: false
+          },
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/zones/Base.js b/www/manager6/sdn/zones/Base.js
new file mode 100644
index 00000000..c4bdc72c
--- /dev/null
+++ b/www/manager6/sdn/zones/Base.js
@@ -0,0 +1,73 @@
+Ext.define('PVE.panel.SDNZoneBase', {
+    extend: 'Proxmox.panel.InputPanel',
+
+    type: '',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.zone;
+        }
+
+        return values;
+    },
+
+    initComponent : function() {
+        var me = this;
+
+        me.callParent();
+    }
+});
+
+Ext.define('PVE.sdn.zones.BaseEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    initComponent : function() {
+	var me = this;
+
+	me.isCreate = !me.zone;
+
+	if (me.isCreate) {
+            me.url = '/api2/extjs/cluster/sdn/zones';
+            me.method = 'POST';
+        } else {
+            me.url = '/api2/extjs/cluster/sdn/zones/' + me.zone;
+            me.method = 'PUT';
+        }
+
+	var ipanel = Ext.create(me.paneltype, {
+	    type: me.type,
+	    isCreate: me.isCreate,
+	    zone: me.zone
+	});
+
+	Ext.apply(me, {
+            subject: PVE.Utils.format_sdnzone_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/zones/EvpnEdit.js b/www/manager6/sdn/zones/EvpnEdit.js
new file mode 100644
index 00000000..a8d3597b
--- /dev/null
+++ b/www/manager6/sdn/zones/EvpnEdit.js
@@ -0,0 +1,64 @@
+Ext.define('PVE.sdn.zones.EvpnInputPanel', {
+    extend: 'PVE.panel.SDNZoneBase',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.zone;
+        }
+
+        return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+        me.items = [
+           {
+            xtype: me.isCreate ? 'textfield' : 'displayfield',
+            name: 'zone',
+            maxLength: 10,
+            value: me.zone || '',
+            fieldLabel: 'ID',
+            allowBlank: false
+          },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'uplink-id',
+	    minValue: 1,
+	    maxValue: 4096,
+	    fieldLabel: gettext('uplink-id'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'vrf-vxlan',
+	    minValue: 1,
+	    maxValue: 16000000,
+	    fieldLabel: gettext('vrf vxlan tag'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'pveSDNControllerSelector',
+	    fieldLabel: gettext('Controller'),
+	    name: 'controller',
+	    value: '',
+	    allowBlank: false
+	  },
+          {
+            xtype: 'pveNodeSelector',
+            name: 'nodes',
+            fieldLabel: gettext('Nodes'),
+            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+            multiSelect: true,
+            autoSelect: false
+          },
+
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/zones/QinQEdit.js b/www/manager6/sdn/zones/QinQEdit.js
new file mode 100644
index 00000000..818bd4b2
--- /dev/null
+++ b/www/manager6/sdn/zones/QinQEdit.js
@@ -0,0 +1,63 @@
+Ext.define('PVE.sdn.zones.QinQInputPanel', {
+    extend: 'PVE.panel.SDNZoneBase',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.sdn;
+        }
+
+        return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+        me.items = [
+           {
+            xtype: me.isCreate ? 'textfield' : 'displayfield',
+            name: 'sdn',
+            maxLength: 10,
+            value: me.zone || '',
+            fieldLabel: 'ID',
+            allowBlank: false
+          },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'uplink-id',
+	    minValue: 1,
+	    maxValue: 4096,
+	    fieldLabel: gettext('uplink-id'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'tag',
+	    fieldLabel: gettext('Service vlan'),
+	    allowBlank: false
+	  },
+	  {
+	    xtype: 'proxmoxKVComboBox',
+	    name: 'vlan-protocol',
+	    fieldLabel: gettext('Service vlan protocol'),
+	    allowBlank: true,
+	    value: '802.1q',
+	    comboItems: [['802.1q', '802.1q'], ['802.1ad', '802.1ad']]
+	  },
+          {
+            xtype: 'pveNodeSelector',
+            name: 'nodes',
+            fieldLabel: gettext('Nodes'),
+            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+            multiSelect: true,
+            autoSelect: false
+          },
+
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/zones/VlanEdit.js b/www/manager6/sdn/zones/VlanEdit.js
new file mode 100644
index 00000000..61fdf2ec
--- /dev/null
+++ b/www/manager6/sdn/zones/VlanEdit.js
@@ -0,0 +1,49 @@
+Ext.define('PVE.sdn.zones.VlanInputPanel', {
+    extend: 'PVE.panel.SDNZoneBase',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.zone;
+        }
+
+        return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+        me.items = [
+           {
+            xtype: me.isCreate ? 'textfield' : 'displayfield',
+            name: 'zone',
+            maxLength: 10,
+            value: me.zone || '',
+            fieldLabel: 'ID',
+            allowBlank: false
+          },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'uplink-id',
+	    minValue: 1,
+	    maxValue: 4096,
+	    fieldLabel: gettext('uplink-id'),
+	    allowBlank: false
+	  },
+          {
+            xtype: 'pveNodeSelector',
+            name: 'nodes',
+            fieldLabel: gettext('Nodes'),
+            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+            multiSelect: true,
+            autoSelect: false
+          },
+
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/sdn/zones/VxlanEdit.js b/www/manager6/sdn/zones/VxlanEdit.js
new file mode 100644
index 00000000..7831f7da
--- /dev/null
+++ b/www/manager6/sdn/zones/VxlanEdit.js
@@ -0,0 +1,106 @@
+Ext.define('PVE.sdn.zones.VxlanInputPanel', {
+    extend: 'PVE.panel.SDNZoneBase',
+
+    onGetValues: function(values) {
+        var me = this;
+
+        if (me.isCreate) {
+            values.type = me.type;
+        } else {
+            delete values.zone;
+        }
+
+	delete values.mode;
+
+        return values;
+    },
+
+    initComponent : function() {
+	var me = this;
+
+        me.items = [
+           {
+            xtype: me.isCreate ? 'textfield' : 'displayfield',
+	    maxLength: 10,
+            name: 'zone',
+            value: me.zone || '',
+            fieldLabel: 'ID',
+            allowBlank: false
+          },
+	  {
+	    xtype: 'proxmoxintegerfield',
+	    name: 'uplink-id',
+	    minValue: 1,
+	    maxValue: 4096,
+	    fieldLabel: gettext('uplink-id'),
+	    allowBlank: false
+	  },
+            {
+                layout: {
+                    type: 'hbox',
+                    align: 'middle'
+                },
+                border: false,
+                margin: '0 0 5 0',
+                items: [
+                    {
+                        xtype: 'label',
+                        text: 'Mode:' // do not localize
+                    },
+                    {
+                        xtype: 'radiofield',
+                        boxLabel: 'Unicast', // do not localize
+                        name: 'mode',
+//                        inputValue: 'dhcp',
+                        margin: '0 0 0 10',
+                        listeners: {
+                            change: function(cb, value) {
+                                me.down('field[name=unicast-address]').setDisabled(false);
+                                me.down('field[name=multicast-address]').setDisabled(true);
+                            }
+                        }
+                    },
+                    {
+                        xtype: 'radiofield',
+                        boxLabel: 'Multicast', // do not localize
+                        name: 'mode',
+//                        inputValue: 'auto',
+                        margin: '0 0 0 10',
+                        listeners: {
+                            change: function(cb, value) {
+                                me.down('field[name=multicast-address]').setDisabled(false);
+                                me.down('field[name=unicast-address]').setDisabled(true);
+                            }
+                        }
+                    }
+                ]
+            },
+
+	  {
+	    xtype: 'textfield',
+	    name: 'multicast-address',
+	    fieldLabel: gettext('multicast-address'),
+	    allowBlank: false,
+	    disabled: true
+	  },
+	  {
+	    xtype: 'textfield',
+	    name: 'unicast-address',
+	    fieldLabel: gettext('unicast address list'),
+	    allowBlank: false,
+	    disabled: true
+	  },
+          {
+            xtype: 'pveNodeSelector',
+            name: 'nodes',
+            fieldLabel: gettext('Nodes'),
+            emptyText: gettext('All') + ' (' + gettext('No restrictions') +')',
+            multiSelect: true,
+            autoSelect: false
+          },
+
+	];
+
+	me.callParent();
+    }
+});
diff --git a/www/manager6/tree/ResourceTree.js b/www/manager6/tree/ResourceTree.js
index 251eb542..5adb864b 100644
--- a/www/manager6/tree/ResourceTree.js
+++ b/www/manager6/tree/ResourceTree.js
@@ -19,6 +19,10 @@ Ext.define('PVE.tree.ResourceTree', {
 		iconCls: 'fa fa-database',
 		text: gettext('Storage')
 	    },
+	    sdn: {
+		iconCls: 'fa fa-database',
+		text: gettext('Sdn')
+	    },
 	    qemu: {
 		iconCls: 'fa fa-desktop',
 		text: gettext('Virtual Machine')
-- 
2.20.1




More information about the pve-devel mailing list