--- 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 | 133 +++++++++++++++++ www/manager6/sdn/VnetView.js | 159 +++++++++++++++++++++ www/manager6/sdn/ZoneContentView.js | 105 ++++++++++++++ www/manager6/sdn/ZoneView.js | 152 ++++++++++++++++++++ www/manager6/sdn/controllers/Base.js | 73 ++++++++++ www/manager6/sdn/controllers/EvpnEdit.js | 48 +++++++ www/manager6/sdn/zones/Base.js | 73 ++++++++++ www/manager6/sdn/zones/EvpnEdit.js | 66 +++++++++ www/manager6/sdn/zones/QinQEdit.js | 63 ++++++++ www/manager6/sdn/zones/VlanEdit.js | 47 ++++++ www/manager6/sdn/zones/VxlanEdit.js | 59 ++++++++ www/manager6/tree/ResourceTree.js | 4 + 25 files changed, 1560 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 eb7ac004..ef9b0dec 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -69,6 +69,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 \ @@ -189,6 +191,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 86951ed7..3d4ac50c 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..afe89264 --- /dev/null +++ b/www/manager6/sdn/VnetEdit.js @@ -0,0 +1,133 @@ +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: '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..951a02cb --- /dev/null +++ b/www/manager6/sdn/ZoneView.js @@ -0,0 +1,152 @@ +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('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..8264bc75 --- /dev/null +++ b/www/manager6/sdn/controllers/EvpnEdit.js @@ -0,0 +1,48 @@ +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: 'asn', + minValue: 1, + maxValue: 4294967295, + value: 65000, + 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..6e08138d --- /dev/null +++ b/www/manager6/sdn/zones/EvpnEdit.js @@ -0,0 +1,66 @@ +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: 'vrf-vxlan', + minValue: 1, + maxValue: 16000000, + fieldLabel: gettext('vrf vxlan tag'), + allowBlank: false + }, + { + xtype: 'pveSDNControllerSelector', + fieldLabel: gettext('Controller'), + name: 'controller', + value: '', + allowBlank: false + }, + { + xtype: 'proxmoxintegerfield', + name: 'mtu', + minValue: 100, + maxValue: 65000, + fieldLabel: gettext('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(); + } +}); diff --git a/www/manager6/sdn/zones/QinQEdit.js b/www/manager6/sdn/zones/QinQEdit.js new file mode 100644 index 00000000..0d05b17e --- /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: 'zone', + maxLength: 10, + value: me.zone || '', + fieldLabel: 'ID', + allowBlank: false + }, + { + xtype: 'textfield', + name: 'bridge', + fieldLabel: gettext('bridge'), + allowBlank: false, + }, + { + xtype: 'proxmoxintegerfield', + name: 'tag', + fieldLabel: gettext('Service vlan'), + allowBlank: false + }, + { + xtype: 'proxmoxintegerfield', + name: 'mtu', + minValue: 100, + maxValue: 65000, + fieldLabel: gettext('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(); + } +}); diff --git a/www/manager6/sdn/zones/VlanEdit.js b/www/manager6/sdn/zones/VlanEdit.js new file mode 100644 index 00000000..ca1b9d30 --- /dev/null +++ b/www/manager6/sdn/zones/VlanEdit.js @@ -0,0 +1,47 @@ +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: 'textfield', + name: 'bridge', + fieldLabel: gettext('bridge'), + 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..72721226 --- /dev/null +++ b/www/manager6/sdn/zones/VxlanEdit.js @@ -0,0 +1,59 @@ +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: 'textfield', + name: 'peers', + fieldLabel: gettext('peers address list'), + allowBlank: false, + }, + { + xtype: 'proxmoxintegerfield', + name: 'mtu', + minValue: 100, + maxValue: 65000, + fieldLabel: gettext('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(); + } +}); 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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel