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 80CEB9B401 for ; Mon, 20 Nov 2023 14:45:16 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 62A691A28E for ; Mon, 20 Nov 2023 14:44:46 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (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 firstgate.proxmox.com (Proxmox) with ESMTPS for ; Mon, 20 Nov 2023 14:44:45 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 236FC4346E; Mon, 20 Nov 2023 14:44:45 +0100 (CET) Message-ID: <862bb465-d873-4cb5-a57a-afe160ede751@proxmox.com> Date: Mon, 20 Nov 2023 14:44:43 +0100 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Beta From: Dominik Csapak To: Proxmox VE development discussion , Stefan Hanreich References: <20231117114011.834002-1-s.hanreich@proxmox.com> <20231117114011.834002-21-s.hanreich@proxmox.com> Content-Language: en-US In-Reply-To: <20231117114011.834002-21-s.hanreich@proxmox.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL -1.483 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 SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - URIBL_BLACK 3 Contains an URL listed in the URIBL blacklist [data.zone] Subject: Re: [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: Mon, 20 Nov 2023 13:45:16 -0000 generally looks ok, but i couldn't edit a manual dhcp mapping with the error: vmid: type check ('integer') failed - got '' did not look too deep so i'm not sure where the empty vmid value is coming from also i like alexandres idea to put it into the main tree in the zone panel as new panel that way we could even reduce the tree by one level since we don't have to add the zone here one minor thing inline (but that's not a blocker for me) On 11/17/23 12:39, Stefan Hanreich wrote: > 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(); > + }, > + }, > + }); > + }, i'd put that function in the controller like the other handlers here > + 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'; > + }, > + }, > + ], > + }, > + ], > +});