From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 935E99A385 for ; Tue, 14 Nov 2023 19:06:32 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id D32281F7F7 for ; Tue, 14 Nov 2023 19:06:30 +0100 (CET) Received: from lana.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP for ; Tue, 14 Nov 2023 19:06:28 +0100 (CET) Received: by lana.proxmox.com (Postfix, from userid 10043) id 29D8E2C4382; Tue, 14 Nov 2023 19:06:24 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Tue, 14 Nov 2023 19:06:16 +0100 Message-Id: <20231114180620.2635449-19-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231114180620.2635449-1-s.hanreich@proxmox.com> References: <20231114180620.2635449-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.526 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods PROLO_LEO1 0.1 Meta Catches all Leo drug variations so far RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 14 Nov 2023 18:06:32 -0000 Signed-off-by: Stefan Hanreich --- www/css/ext6-pve.css | 10 +- www/manager6/Makefile | 2 + www/manager6/dc/Config.js | 12 +- www/manager6/sdn/MappingEdit.js | 65 ++++++++++ www/manager6/tree/DhcpTree.js | 215 ++++++++++++++++++++++++++++++++ 5 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 www/manager6/sdn/MappingEdit.js create mode 100644 www/manager6/tree/DhcpTree.js diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index e18b173f5..e5e616832 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -510,23 +510,21 @@ div.right-aligned { content: ' '; } -.fa-sdn:before { +.x-fa-sdn-treelist:before { width: 14px; height: 14px; position: absolute; left: 1px; top: 4px; +} + +.fa-sdn:before { background-image:url(../images/icon-sdn.svg); background-size: 14px 14px; content: ' '; } .fa-network-wired:before { - width: 14px; - height: 14px; - position: absolute; - left: 1px; - top: 4px; background-image:url(../images/icon-fa-network-wired.svg); background-size: 14px 14px; content: ' '; diff --git a/www/manager6/Makefile b/www/manager6/Makefile index dccd2ba1c..d226c8faa 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -108,6 +108,7 @@ JSSRC= \ tree/ResourceTree.js \ tree/SnapshotTree.js \ tree/ResourceMapTree.js \ + tree/DhcpTree.js \ window/Backup.js \ window/BackupConfig.js \ window/BulkAction.js \ @@ -274,6 +275,7 @@ JSSRC= \ sdn/ZoneContentView.js \ sdn/ZoneContentPanel.js \ sdn/ZoneView.js \ + sdn/MappingEdit.js \ sdn/OptionsPanel.js \ sdn/controllers/Base.js \ sdn/controllers/EvpnEdit.js \ diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js index 7d01da5fb..0e3948ef4 100644 --- a/www/manager6/dc/Config.js +++ b/www/manager6/dc/Config.js @@ -195,7 +195,7 @@ Ext.define('PVE.dc.Config', { groups: ['sdn'], title: gettext('Zones'), hidden: true, - iconCls: 'fa fa-th', + iconCls: 'fa fa-th x-fa-sdn-treelist', itemId: 'sdnzone', }, { @@ -203,7 +203,7 @@ Ext.define('PVE.dc.Config', { groups: ['sdn'], title: 'VNets', hidden: true, - iconCls: 'fa fa-network-wired', + iconCls: 'fa fa-network-wired x-fa-sdn-treelist', itemId: 'sdnvnet', }, { @@ -213,6 +213,14 @@ Ext.define('PVE.dc.Config', { hidden: true, iconCls: 'fa fa-gear', itemId: 'sdnoptions', + }, + { + xtype: 'pveDhcpTree', + groups: ['sdn'], + title: 'Dhcp Mappings', + hidden: true, + iconCls: 'fa fa-gear', + itemId: 'sdnmappings', }); } diff --git a/www/manager6/sdn/MappingEdit.js b/www/manager6/sdn/MappingEdit.js new file mode 100644 index 000000000..533fc6249 --- /dev/null +++ b/www/manager6/sdn/MappingEdit.js @@ -0,0 +1,65 @@ +Ext.define('PVE.sdn.DhcpMappingInputPanel', { + extend: 'Proxmox.panel.InputPanel', + mixins: ['Proxmox.Mixin.CBind'], + + isCreate: false, + + items: [ + { + xtype: 'pmxDisplayEditField', + name: 'vmid', + fieldLabel: gettext('VMID'), + allowBlank: false, + editable: false, + }, + { + xtype: 'pmxDisplayEditField', + name: 'mac', + fieldLabel: gettext('MAC'), + allowBlank: false, + cbind: { + editable: '{isCreate}', + }, + }, + { + xtype: 'proxmoxtextfield', + name: 'ip', + fieldLabel: gettext('IP'), + allowBlank: false, + }, + ], +}); + +Ext.define('PVE.sdn.MappingEdit', { + extend: 'Proxmox.window.Edit', + + subject: gettext('DHCP Mapping'), + width: 350, + + isCreate: false, + mapping: {}, + + submitUrl: function(url, values) { + return `${url}/${values.vnet}/${values.mac}`; + }, + + initComponent: function() { + var me = this; + + me.method = me.isCreate ? 'POST' : 'PUT'; + + let ipanel = Ext.create('PVE.sdn.DhcpMappingInputPanel', { + isCreate: me.isCreate, + }); + + Ext.apply(me, { + items: [ + ipanel, + ], + }); + + me.callParent(); + + ipanel.setValues(me.mapping); + }, +}); diff --git a/www/manager6/tree/DhcpTree.js b/www/manager6/tree/DhcpTree.js new file mode 100644 index 000000000..c714f0ff0 --- /dev/null +++ b/www/manager6/tree/DhcpTree.js @@ -0,0 +1,215 @@ +Ext.define('PVE.sdn.DhcpTree', { + extend: 'Ext.tree.Panel', + xtype: 'pveDhcpTree', + + layout: 'fit', + rootVisible: false, + animate: false, + + store: { + sorters: ['ip', 'name'], + }, + + controller: { + xclass: 'Ext.app.ViewController', + reload: function() { + let me = this; + + Proxmox.Utils.API2Request({ + url: `/cluster/sdn/ipam`, + method: 'GET', + success: function(response, opts) { + let root = { + name: '__root', + expanded: true, + children: [], + }; + + let zones = {}; + let vnets = {}; + let subnets = {}; + + response.result.data.forEach((element) => { + element.leaf = true; + + if (!(element.zone in zones)) { + let zone = { + name: element.zone, + type: 'zone', + iconCls: 'fa fa-th', + expanded: true, + children: [], + }; + + zones[element.zone] = zone; + root.children.push(zone); + } + + if (!(element.vnet in vnets)) { + let vnet = { + name: element.vnet, + type: 'vnet', + iconCls: 'fa fa-network-wired', + expanded: true, + children: [], + }; + + vnets[element.vnet] = vnet; + zones[element.zone].children.push(vnet); + } + + if (!(element.subnet in subnets)) { + let subnet = { + name: element.subnet, + type: 'subnet', + expanded: true, + children: [], + }; + + subnets[element.subnet] = subnet; + vnets[element.vnet].children.push(subnet); + } + + element.type = 'mapping'; + subnets[element.subnet].children.push(element); + }); + + me.getView().setRootNode(root); + }, + }); + }, + init: function(view) { + let me = this; + me.reload(); + }, + onDelete: function(table, rI, cI, item, e, { data }) { + let me = this; + let view = me.getView(); + + Ext.Msg.show({ + title: gettext('Confirm'), + icon: Ext.Msg.WARNING, + message: gettext('Are you sure you want to remove DHCP mapping {0}'), + buttons: Ext.Msg.YESNO, + defaultFocus: 'no', + callback: function(btn) { + if (btn !== 'yes') { + return; + } + + Proxmox.Utils.API2Request({ + url: `/cluster/sdn/ipam/${data.vnet}/${data.mac}`, + method: 'DELETE', + waitMsgTarget: view, + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + callback: me.reload.bind(me), + }); + }, + }); + }, + }, + + columns: [ + { + xtype: 'treecolumn', + text: 'Name / VMID', + dataIndex: 'name', + width: 200, + renderer: function(value, meta, record) { + if (record.get('gateway')) { + return 'Gateway'; + } + + return record.get('name') ?? record.get('vmid') ?? ''; + }, + }, + { + text: 'IP', + dataIndex: 'ip', + width: 200, + }, + { + text: 'MAC', + dataIndex: 'mac', + width: 200, + }, + { + text: 'Gateway', + dataIndex: 'gateway', + width: 200, + }, + { + header: gettext('Actions'), + xtype: 'actioncolumn', + dataIndex: 'text', + width: 150, + items: [ + { + handler: function(table, rI, cI, item, e, { data }) { + let me = this; + + let win = Ext.create('PVE.sdn.MappingEdit', { + autoShow: true, + mapping: {}, + url: `/cluster/sdn/ipam`, + method: 'POST', + isCreate: true, + extraRequestParams: { + vnet: data.name, + }, + }); + + win.on('destroy', me.reload); + }, + getTip: (v, m, rec) => gettext('Add'), + getClass: (v, m, { data }) => { + if (data.type === 'vnet') { + return 'fa fa-plus-square'; + } + + return 'pmx-hidden'; + }, + }, + { + handler: function(table, rI, cI, item, e, { data }) { + let me = this; + + let win = Ext.create('PVE.sdn.MappingEdit', { + autoShow: true, + mapping: data, + url: `/cluster/sdn/ipam`, + method: 'PUT', + extraRequestParams: { + vmid: data.vmid, + vnet: data.vnet, + }, + }); + + win.on('destroy', me.reload); + }, + getTip: (v, m, rec) => 'Edit', + getClass: (v, m, { data }) => { + if (data.type === 'mapping' && !data.gateway) { + return 'fa fa-pencil fa-fw'; + } + + return 'pmx-hidden'; + }, + }, + { + handler: 'onDelete', + getTip: (v, m, rec) => 'Delete', + getClass: (v, m, { data }) => { + if (data.type === 'mapping' && !data.gateway) { + return 'fa critical fa-trash-o'; + } + + return 'pmx-hidden'; + }, + }, + ], + }, + ], +}); -- 2.39.2