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 40EAFBA2CA for ; Wed, 13 Dec 2023 17:38:58 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 7D7F9DAC8 for ; Wed, 13 Dec 2023 17:38:27 +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 ; Wed, 13 Dec 2023 17:38:22 +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 80CBC472F8 for ; Wed, 13 Dec 2023 17:38:21 +0100 (CET) From: Lukas Wagner To: pve-devel@lists.proxmox.com Date: Wed, 13 Dec 2023 17:37:55 +0100 Message-Id: <20231213163801.392492-17-l.wagner@proxmox.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231213163801.392492-1-l.wagner@proxmox.com> References: <20231213163801.392492-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.004 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 - Subject: [pve-devel] [PATCH v2 proxmox-widget-toolkit 16/22] notification: matcher: match-field: show known fields/values 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: Wed, 13 Dec 2023 16:38:58 -0000 These changes introduce combogrid pickers for the 'field' and 'value' form elements for 'match-field' match rules. The 'field' picker shows a list of all known metadata fields, while the 'value' picker shows a list of all known values, filtered depending on the current value of 'field'. The list of known fields/values is retrieved from new API endpoints. Some values are marked 'internal' by the backend. This means that the 'value' field was not user-created (counter example: backup job IDs) and can therefore be used as a base for translations. Signed-off-by: Lukas Wagner Tested-by: Maximiliano Sandoval --- src/Utils.js | 31 +++ src/data/model/NotificationConfig.js | 12 ++ src/window/NotificationMatcherEdit.js | 300 +++++++++++++++++++++----- 3 files changed, 287 insertions(+), 56 deletions(-) diff --git a/src/Utils.js b/src/Utils.js index 0a6d263..23d0651 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -644,6 +644,37 @@ utilities: { Proxmox.Utils.unknownText; }, + // Only add product-agnostic fields here! + notificationFieldName: { + 'type': gettext('Notification type'), + 'hostname': gettext('Hostname'), + }, + + formatNotificationFieldName: (value) => + Proxmox.Utils.notificationFieldName[value] || value, + + // to add or change existing for product specific ones + overrideNotificationFieldName: function(extra) { + for (const [key, value] of Object.entries(extra)) { + Proxmox.Utils.notificationFieldName[key] = value; + } + }, + + // Only add product-agnostic fields here! + notificationFieldValue: { + 'system-mail': gettext('Forwarded mails to the local root user'), + }, + + formatNotificationFieldValue: (value) => + Proxmox.Utils.notificationFieldValue[value] || value, + + // to add or change existing for product specific ones + overrideNotificationFieldValue: function(extra) { + for (const [key, value] of Object.entries(extra)) { + Proxmox.Utils.notificationFieldValue[key] = value; + } + }, + // NOTE: only add general, product agnostic, ones here! Else use override helper in product repos task_desc_table: { aptupdate: ['', gettext('Update package database')], diff --git a/src/data/model/NotificationConfig.js b/src/data/model/NotificationConfig.js index e8ebf28..a2c365b 100644 --- a/src/data/model/NotificationConfig.js +++ b/src/data/model/NotificationConfig.js @@ -15,3 +15,15 @@ Ext.define('proxmox-notification-matchers', { }, idProperty: 'name', }); + +Ext.define('proxmox-notification-fields', { + extend: 'Ext.data.Model', + fields: ['name', 'description'], + idProperty: 'name', +}); + +Ext.define('proxmox-notification-field-values', { + extend: 'Ext.data.Model', + fields: ['value', 'comment', 'internal', 'field'], + idProperty: 'value', +}); diff --git a/src/window/NotificationMatcherEdit.js b/src/window/NotificationMatcherEdit.js index f88576a..fa42e1e 100644 --- a/src/window/NotificationMatcherEdit.js +++ b/src/window/NotificationMatcherEdit.js @@ -79,7 +79,7 @@ Ext.define('Proxmox.window.NotificationMatcherEdit', { labelWidth: 120, }, - width: 700, + width: 800, initComponent: function() { let me = this; @@ -416,10 +416,22 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', { let me = this; let record = me.get('selectedRecord'); let currentData = record.get('data'); + + let newValue = []; + + // Build equivalent regular expression if switching + // to 'regex' mode + if (value === 'regex') { + let regexVal = "^("; + regexVal += currentData.value.join('|') + ")$"; + newValue.push(regexVal); + } + record.set({ data: { ...currentData, type: value, + value: newValue, }, }); }, @@ -441,6 +453,8 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', { data: { ...currentData, field: value, + // Reset value if field changes + value: [], }, }); }, @@ -549,6 +563,9 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', { column2: [ { xtype: 'pmxNotificationMatchRuleSettings', + cbind: { + baseUrl: '{baseUrl}', + }, }, ], @@ -601,7 +618,7 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', { let value = data.value; text = Ext.String.format(gettext("Match field: {0}={1}"), field, value); iconCls = 'fa fa-square-o'; - if (!field || !value) { + if (!field || !value || (Ext.isArray(value) && !value.length)) { iconCls += ' internal-error'; } } break; @@ -821,6 +838,11 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', { if (type === undefined) { type = "exact"; } + + if (type === 'exact') { + matchedValue = matchedValue.split(','); + } + return { type: 'match-field', data: { @@ -982,7 +1004,9 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', { Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { extend: 'Ext.panel.Panel', xtype: 'pmxNotificationMatchRuleSettings', + mixins: ['Proxmox.Mixin.CBind'], border: false, + layout: 'anchor', items: [ { @@ -1000,6 +1024,8 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { ['notall', gettext('At least one rule does not match')], ['notany', gettext('No rule matches')], ], + // Hide initially to avoid glitches when opening the window + hidden: true, bind: { hidden: '{!showMatchingMode}', disabled: '{!showMatchingMode}', @@ -1011,7 +1037,8 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { fieldLabel: gettext('Node type'), isFormField: false, allowBlank: false, - + // Hide initially to avoid glitches when opening the window + hidden: true, bind: { value: '{nodeType}', hidden: '{!showMatcherType}', @@ -1025,57 +1052,9 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { ], }, { - fieldLabel: 'Match Type', - xtype: 'proxmoxKVComboBox', - reference: 'type', - isFormField: false, - allowBlank: false, - submitValue: false, - field: 'type', - - bind: { - hidden: '{!typeIsMatchField}', - disabled: '{!typeIsMatchField}', - value: '{matchFieldType}', - }, - - comboItems: [ - ['exact', gettext('Exact')], - ['regex', gettext('Regex')], - ], - }, - { - fieldLabel: gettext('Field'), - xtype: 'proxmoxKVComboBox', - isFormField: false, - submitValue: false, - allowBlank: false, - editable: true, - displayField: 'key', - field: 'field', - bind: { - hidden: '{!typeIsMatchField}', - disabled: '{!typeIsMatchField}', - value: '{matchFieldField}', - }, - // TODO: Once we have a 'notification registry', we should - // retrive those via an API call. - comboItems: [ - ['type', ''], - ['hostname', ''], - ], - }, - { - fieldLabel: gettext('Value'), - xtype: 'textfield', - isFormField: false, - submitValue: false, - allowBlank: false, - field: 'value', - bind: { - hidden: '{!typeIsMatchField}', - disabled: '{!typeIsMatchField}', - value: '{matchFieldValue}', + xtype: 'pmxNotificationMatchFieldSettings', + cbind: { + baseUrl: '{baseUrl}', }, }, { @@ -1085,7 +1064,8 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { allowBlank: true, multiSelect: true, field: 'value', - + // Hide initially to avoid glitches when opening the window + hidden: true, bind: { value: '{matchSeverityValue}', hidden: '{!typeIsMatchSeverity}', @@ -1108,7 +1088,8 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { editable: true, displayField: 'key', field: 'value', - + // Hide initially to avoid glitches when opening the window + hidden: true, bind: { value: '{matchCalendarValue}', hidden: '{!typeIsMatchCalendar}', @@ -1122,3 +1103,210 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', { }, ], }); + +Ext.define('Proxmox.panel.MatchFieldSettings', { + extend: 'Ext.panel.Panel', + xtype: 'pmxNotificationMatchFieldSettings', + border: false, + layout: 'anchor', + // Hide initially to avoid glitches when opening the window + hidden: true, + bind: { + hidden: '{!typeIsMatchField}', + }, + controller: { + xclass: 'Ext.app.ViewController', + + control: { + 'field[reference=fieldSelector]': { + change: function(field) { + let view = this.getView(); + let valueField = view.down('field[reference=valueSelector]'); + let store = valueField.getStore(); + let val = field.getValue(); + + if (val) { + store.setFilters([ + { + property: 'field', + value: val, + }, + ]); + } + }, + }, + }, + }, + + + initComponent: function() { + let me = this; + + let store = Ext.create('Ext.data.Store', { + model: 'proxmox-notification-fields', + autoLoad: true, + proxy: { + type: 'proxmox', + url: `/api2/json/${me.baseUrl}/fields`, + }, + listeners: { + 'load': function() { + this.each(function(record) { + record.set({ + description: + Proxmox.Utils.formatNotificationFieldName( + record.get('name'), + ), + }); + }); + + // Commit changes so that the description field is not marked + // as dirty + this.commitChanges(); + }, + }, + }); + + let valueStore = Ext.create('Ext.data.Store', { + model: 'proxmox-notification-field-values', + autoLoad: true, + proxy: { + type: 'proxmox', + + url: `/api2/json/${me.baseUrl}/values`, + }, + listeners: { + 'load': function() { + this.each(function(record) { + // if 'internal' is set, we know that 'value' was provided + // by the system and not by the user. We can then use + // it safely for deriving translations. + if (record.get('internal')) { + record.set({ + comment: + Proxmox.Utils.formatNotificationFieldValue( + record.get('value'), + ), + }); + } + }); + + // Commit changes so that the description field is not marked + // as dirty + this.commitChanges(); + }, + }, + }); + + Ext.apply(me, { + viewModel: Ext.create('Ext.app.ViewModel', { + parent: me.up('pmxNotificationMatchRulesEditPanel').getViewModel(), + formulas: { + isRegex: function(get) { + return get('matchFieldType') === 'regex'; + }, + }, + }), + items: [ + { + fieldLabel: 'Match Type', + xtype: 'proxmoxKVComboBox', + reference: 'type', + isFormField: false, + allowBlank: false, + submitValue: false, + field: 'type', + + bind: { + value: '{matchFieldType}', + }, + + comboItems: [ + ['exact', gettext('Exact')], + ['regex', gettext('Regex')], + ], + }, + { + fieldLabel: gettext('Field'), + reference: 'fieldSelector', + xtype: 'proxmoxComboGrid', + isFormField: false, + submitValue: false, + allowBlank: false, + editable: false, + store: store, + queryMode: 'local', + valueField: 'name', + displayField: 'description', + field: 'field', + bind: { + value: '{matchFieldField}', + }, + listConfig: { + columns: [ + { + header: gettext('Description'), + dataIndex: 'description', + flex: 2, + }, + { + header: gettext('Field Name'), + dataIndex: 'name', + flex: 1, + }, + ], + }, + }, + { + fieldLabel: gettext('Value'), + reference: 'valueSelector', + xtype: 'proxmoxComboGrid', + autoSelect: false, + editable: false, + isFormField: false, + submitValue: false, + allowBlank: false, + showClearTrigger: true, + field: 'value', + store: valueStore, + valueField: 'value', + displayField: 'value', + notFoundIsValid: false, + multiSelect: true, + bind: { + value: '{matchFieldValue}', + hidden: '{isRegex}', + }, + listConfig: { + columns: [ + { + header: gettext('Value'), + dataIndex: 'value', + flex: 1, + }, + { + header: gettext('Comment'), + dataIndex: 'comment', + flex: 2, + }, + ], + }, + }, + { + fieldLabel: gettext('Regex'), + xtype: 'proxmoxtextfield', + editable: true, + isFormField: false, + submitValue: false, + allowBlank: false, + field: 'value', + bind: { + value: '{matchFieldValue}', + hidden: '{!isRegex}', + }, + }, + ], + }); + me.callParent(); + }, +}); -- 2.39.2