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 A4C2F9A58F for ; Fri, 17 Nov 2023 12:50:04 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7B5D631B19 for ; Fri, 17 Nov 2023 12:50:04 +0100 (CET) Received: from lana.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP for ; Fri, 17 Nov 2023 12:50:02 +0100 (CET) Received: by lana.proxmox.com (Postfix, from userid 10043) id 9AD092C34C9; Fri, 17 Nov 2023 12:40:18 +0100 (CET) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Fri, 17 Nov 2023 12:39:58 +0100 Message-Id: <20231117114011.834002-21-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231117114011.834002-1-s.hanreich@proxmox.com> References: <20231117114011.834002-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.440 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 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] [PATCH v4 pve-manager 20/33] sdn: ipam: add ipam panel 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: Fri, 17 Nov 2023 11:50:04 -0000 Signed-off-by: Stefan Hanreich --- www/css/ext6-pve.css | 22 ++- www/manager6/Makefile | 1 + www/manager6/dc/Config.js | 12 +- www/manager6/sdn/IpamEdit.js | 78 ++++++++++ www/manager6/tree/DhcpTree.js | 267 ++++++++++++++++++++++++++++++++++ 5 files changed, 372 insertions(+), 8 deletions(-) create mode 100644 www/manager6/sdn/IpamEdit.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..091855356 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -510,28 +510,38 @@ 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: ' '; } +.x-fa-treepanel:before { + width: 16px; + height: 24px; + display: block; + background-repeat: no-repeat; + background-position: center; +} + +.x-tree-icon-none { + display: none; +} + .x-treelist-row-over > * > .x-treelist-item-icon, .x-treelist-row-over > * > .x-treelist-item-text{ color: #000; diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 093452cd7..93b4ff155 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 \ diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js index 7d01da5fb..7c2b7b168 100644 --- a/www/manager6/dc/Config.js +++ b/www/manager6/dc/Config.js @@ -185,7 +185,7 @@ Ext.define('PVE.dc.Config', { me.items.push({ xtype: 'pveSDNStatus', title: gettext('SDN'), - iconCls: 'fa fa-sdn', + iconCls: 'fa fa-sdn x-fa-sdn-treelist', hidden: true, itemId: 'sdn', expandedOnInit: true, @@ -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: gettext('IPAM'), + hidden: true, + iconCls: 'fa fa-map-signs', + itemId: 'sdnmappings', }); } diff --git a/www/manager6/sdn/IpamEdit.js b/www/manager6/sdn/IpamEdit.js new file mode 100644 index 000000000..18e22c592 --- /dev/null +++ b/www/manager6/sdn/IpamEdit.js @@ -0,0 +1,78 @@ +Ext.define('PVE.sdn.IpamEditInputPanel', { + extend: 'Proxmox.panel.InputPanel', + mixins: ['Proxmox.Mixin.CBind'], + + isCreate: false, + + onGetValues: function(values) { + let me = this; + + if (!values.vmid) { + delete values.vmid; + } + + return values; + }, + + items: [ + { + xtype: 'pmxDisplayEditField', + name: 'vmid', + fieldLabel: gettext('VMID'), + allowBlank: false, + editable: false, + cbind: { + hidden: '{isCreate}', + }, + }, + { + 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.IpamEdit', { + extend: 'Proxmox.window.Edit', + + subject: gettext('DHCP Mapping'), + width: 350, + + isCreate: false, + mapping: {}, + + submitUrl: function(url, values) { + return `${url}/${values.zone}/${values.vnet}/${values.mac}`; + }, + + initComponent: function() { + var me = this; + + me.method = me.isCreate ? 'POST' : 'PUT'; + + let ipanel = Ext.create('PVE.sdn.IpamEditInputPanel', { + 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..ca279c29a --- /dev/null +++ b/www/manager6/tree/DhcpTree.js @@ -0,0 +1,267 @@ +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, + zone: element.zone, + type: 'vnet', + iconCls: 'fa fa-network-wired x-fa-treepanel', + expanded: true, + children: [], + }; + + vnets[element.vnet] = vnet; + zones[element.zone].children.push(vnet); + } + + if (!(element.subnet in subnets)) { + let subnet = { + name: element.subnet, + zone: element.zone, + vnet: element.vnet, + type: 'subnet', + iconCls: 'x-tree-icon-none', + expanded: true, + children: [], + }; + + subnets[element.subnet] = subnet; + vnets[element.vnet].children.push(subnet); + } + + element.type = 'mapping'; + element.iconCls = 'x-tree-icon-none'; + 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: Ext.String.format(gettext('Are you sure you want to remove DHCP mapping {0}'), `${data.mac} / ${data.ip}`), + buttons: Ext.Msg.YESNO, + defaultFocus: 'no', + callback: function(btn) { + if (btn !== 'yes') { + return; + } + + Proxmox.Utils.API2Request({ + url: `/cluster/sdn/ipam/${data.zone}/${data.vnet}/${data.mac}`, + method: 'DELETE', + waitMsgTarget: view, + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + callback: me.reload.bind(me), + }); + }, + }); + }, + + editAction: function(_grid, _rI, _cI, _item, _e, rec) { + this.edit(rec); + }, + + editDblClick: function() { + let me = this; + + let view = me.getView(); + let selection = view.getSelection(); + + if (!selection || selection.length < 1) { + return; + } + + me.edit(selection[0]); + }, + + edit: function(rec) { + let me = this; + + if (rec.data.type === 'mapping' && !rec.data.gateway) { + me.openEditWindow(rec.data); + } + }, + + openEditWindow: function(data) { + let me = this; + + Ext.create('PVE.sdn.IpamEdit', { + autoShow: true, + mapping: data, + url: `/cluster/sdn/ipam`, + extraRequestParams: { + vmid: data.vmid, + mac: data.mac, + zone: data.zone, + vnet: data.vnet, + }, + listeners: { + destroy: () => me.reload(), + }, + }); + }, + }, + + listeners: { + itemdblclick: 'editDblClick', + }, + + tbar: [ + { + xtype: 'proxmoxButton', + text: gettext('Reload'), + handler: 'reload', + }, + ], + + columns: [ + { + xtype: 'treecolumn', + text: gettext('Name / VMID'), + dataIndex: 'name', + width: 200, + renderer: function(value, meta, record) { + if (record.get('gateway')) { + return gettext('Gateway'); + } + + return record.get('name') ?? record.get('vmid') ?? ' '; + }, + }, + { + text: gettext('IP'), + dataIndex: 'ip', + width: 200, + }, + { + text: gettext('MAC'), + dataIndex: 'mac', + width: 200, + }, + { + text: gettext('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; + + Ext.create('PVE.sdn.IpamEdit', { + autoShow: true, + mapping: {}, + url: `/cluster/sdn/ipam`, + isCreate: true, + extraRequestParams: { + vnet: data.name, + zone: data.zone, + }, + listeners: { + destroy: () => { + me.up('pveDhcpTree').controller.reload(); + }, + }, + }); + }, + getTip: (v, m, rec) => gettext('Add'), + getClass: (v, m, { data }) => { + if (data.type === 'vnet') { + return 'fa fa-plus-square'; + } + + return 'pmx-hidden'; + }, + }, + { + handler: 'editAction', + getTip: (v, m, rec) => gettext('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) => gettext('Delete'), + getClass: (v, m, { data }) => { + if (data.type === 'mapping' && !data.gateway) { + return 'fa critical fa-trash-o'; + } + + return 'pmx-hidden'; + }, + }, + ], + }, + ], +}); -- 2.39.2