all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-widget-toolkit 1/1] notifications: matchers: add nested matcher support
Date: Wed, 21 May 2025 16:23:08 +0200	[thread overview]
Message-ID: <20250521142309.264719-4-l.wagner@proxmox.com> (raw)
In-Reply-To: <20250521142309.264719-1-l.wagner@proxmox.com>

Adds a new checkbox to declare a matcher is 'nested', as well as a
new node type in the rule tree 'Evaluate nested matcher'. The latter
allows one to select other matcher which is declared as 'nested'.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 src/data/model/NotificationConfig.js  |   8 +-
 src/panel/NotificationConfigView.js   |   6 +
 src/window/NotificationMatcherEdit.js | 220 +++++++++++++++++++++++++-
 3 files changed, 232 insertions(+), 2 deletions(-)

diff --git a/src/data/model/NotificationConfig.js b/src/data/model/NotificationConfig.js
index 03cf317..e1f7058 100644
--- a/src/data/model/NotificationConfig.js
+++ b/src/data/model/NotificationConfig.js
@@ -9,7 +9,13 @@ Ext.define('proxmox-notification-endpoints', {
 
 Ext.define('proxmox-notification-matchers', {
     extend: 'Ext.data.Model',
-    fields: ['name', 'comment', 'disable', 'origin'],
+    fields: [
+        'name',
+        'comment',
+        'disable',
+        'origin',
+        'nested',
+    ],
     proxy: {
         type: 'proxmox',
     },
diff --git a/src/panel/NotificationConfigView.js b/src/panel/NotificationConfigView.js
index 9505eb7..994ff9d 100644
--- a/src/panel/NotificationConfigView.js
+++ b/src/panel/NotificationConfigView.js
@@ -326,6 +326,12 @@ Ext.define('Proxmox.panel.NotificationMatcherView', {
 	    renderer: (disable) => Proxmox.Utils.renderEnabledIcon(!disable),
 	    align: 'center',
 	},
+	{
+	    dataIndex: 'nested',
+	    text: gettext('Nested'),
+	    renderer: (nested) => Proxmox.Utils.renderEnabledIcon(nested),
+	    align: 'center',
+	},
 	{
 	    dataIndex: 'name',
 	    text: gettext('Matcher Name'),
diff --git a/src/window/NotificationMatcherEdit.js b/src/window/NotificationMatcherEdit.js
index 83c09ea..06597d5 100644
--- a/src/window/NotificationMatcherEdit.js
+++ b/src/window/NotificationMatcherEdit.js
@@ -21,6 +21,21 @@ Ext.define('Proxmox.panel.NotificationMatcherGeneralPanel', {
 	    allowBlank: false,
 	    checked: true,
 	},
+	{
+	    xtype: 'proxmoxcheckbox',
+	    name: 'nested',
+	    fieldLabel: gettext('Nested matcher'),
+	    allowBlank: false,
+	    checked: false,
+	    bind: '{isNestedMatcher}',
+	    autoEl: {
+		tag: 'div',
+		'data-qtip': gettext('Nested matchers can be used by other matchers to create sophisticated matching rules.'),
+	    },
+	    cbind: {
+		deleteEmpty: '{!isCreate}',
+	    },
+	},
 	{
 	    xtype: 'proxmoxtextfield',
 	    name: 'comment',
@@ -64,9 +79,24 @@ Ext.define('Proxmox.panel.NotificationMatcherTargetPanel', {
 	{
 	    xtype: 'pmxNotificationTargetSelector',
 	    name: 'target',
-	    allowBlank: false,
+	    allowBlank: true,
 	},
     ],
+
+    onGetValues: function(values) {
+	let me = this;
+
+	if (Ext.isArray(values.target)) {
+	    if (values.target.length === 0) {
+		delete values.target;
+		if (!me.isCreate) {
+		    Proxmox.Utils.assemble_field_data(values, { 'delete': 'target' });
+		}
+	    }
+	}
+
+	return values;
+    },
 });
 
 Ext.define('Proxmox.window.NotificationMatcherEdit', {
@@ -81,6 +111,12 @@ Ext.define('Proxmox.window.NotificationMatcherEdit', {
 
     width: 800,
 
+    viewModel: {
+	data: {
+	    isNestedMatcher: false,
+	},
+    },
+
     initComponent: function() {
 	let me = this;
 
@@ -130,6 +166,9 @@ Ext.define('Proxmox.window.NotificationMatcherEdit', {
 			    xtype: 'pmxNotificationMatcherTargetPanel',
 			    isCreate: me.isCreate,
 			    baseUrl: me.baseUrl,
+			    bind: {
+				disabled: '{isNestedMatcher}',
+			    },
 			},
 		    ],
 		},
@@ -357,6 +396,11 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', {
 				value: '',
 			    };
 			    break;
+			case 'eval-matcher':
+			    data = {
+				matcher: '',
+			    };
+			    break;
 		    }
 
 		    let node = {
@@ -424,6 +468,7 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', {
 	    xtype: 'pmxNotificationMatchRuleSettings',
 	    cbind: {
 		baseUrl: '{baseUrl}',
+		name: '{name}',
 	    },
 	},
 
@@ -445,6 +490,7 @@ Ext.define('Proxmox.panel.NotificationRulesEditPanel', {
 	deleteArrayIfEmtpy('match-field');
 	deleteArrayIfEmtpy('match-severity');
 	deleteArrayIfEmtpy('match-calendar');
+	deleteArrayIfEmtpy('eval-matcher');
 
 	return values;
     },
@@ -506,6 +552,14 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 		iconCls = 'fa fa-filter';
 
 		break;
+	    case 'eval-matcher': {
+		let v = data.matcher;
+		text = Ext.String.format(gettext("Evaluate nested matcher: {0}"), v);
+		iconCls = 'fa fa-filter';
+		if (!v) {
+		    iconCls += ' internal-error';
+		}
+	    } break;
 	}
 
 	return [text, iconCls];
@@ -632,12 +686,29 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 	    deleteEmpty: !me.isCreate,
 	});
 
+	let realEvalMatcher = Ext.create({
+	    xtype: 'hiddenfield',
+	    name: 'eval-matcher',
+	    setValue: function(value) {
+		this.value = value;
+		this.checkChange();
+	    },
+	    getValue: function() {
+		return this.value;
+	    },
+	    getSubmitValue: function() {
+		let value = this.value;
+		return value;
+	    },
+	});
+
 	let storeChanged = function(store) {
 	    store.suspendEvent('datachanged');
 
 	    let matchFieldStmts = [];
 	    let matchSeverityStmts = [];
 	    let matchCalendarStmts = [];
+	    let evalMatcherStmts = [];
 	    let modeStmt = 'all';
 	    let invertMatchStmt = false;
 
@@ -663,6 +734,9 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 			modeStmt = data.value;
 			invertMatchStmt = data.invert;
 			break;
+		    case 'eval-matcher':
+			evalMatcherStmts.push(data.matcher);
+			break;
 		}
 
 		let [text, iconCls] = me.getNodeTextAndIcon(type, data);
@@ -692,6 +766,10 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 	    realMatchSeverity.setValue(matchSeverityStmts);
 	    realMatchSeverity.resumeEvent('change');
 
+	    realEvalMatcher.suspendEvent('change');
+	    realEvalMatcher.setValue(evalMatcherStmts);
+	    realEvalMatcher.resumeEvent('change');
+
 	    store.resumeEvent('datachanged');
 	};
 
@@ -800,6 +878,30 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 	    });
 	});
 
+	realEvalMatcher.addListener('change', function(field, value) {
+	    let parseEvalMatcher = function(matcher) {
+		return {
+		    type: 'eval-matcher',
+		    data: {
+			matcher: matcher,
+		    },
+		    leaf: true,
+		};
+	    };
+
+	    for (let node of treeStore.queryBy(
+		record => record.get('type') === 'eval-matcher').getRange()) {
+		node.remove(true);
+	    }
+
+	    let records = value.map(parseEvalMatcher);
+	    let rootNode = treeStore.getRootNode();
+
+	    for (let record of records) {
+		rootNode.appendChild(record);
+	    }
+	});
+
 	treeStore.addListener('datachanged', storeChanged);
 
 	let treePanel = Ext.create({
@@ -844,6 +946,7 @@ Ext.define('Proxmox.panel.NotificationMatchRuleTree', {
 		realMatchSeverity,
 		realInvertMatch,
 		realMatchCalendar,
+		realEvalMatcher,
 		treePanel,
 		{
 		    xtype: 'button',
@@ -913,6 +1016,7 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', {
 		['match-field', gettext('Match Field')],
 		['match-severity', gettext('Match Severity')],
 		['match-calendar', gettext('Match Calendar')],
+		['eval-matcher', gettext('Evaluate nested matcher')],
 	    ],
 	},
 	{
@@ -927,6 +1031,13 @@ Ext.define('Proxmox.panel.NotificationMatchRuleSettings', {
 	{
 	    xtype: 'pmxNotificationMatchCalendarSettings',
 	},
+	{
+	    xtype: 'pmxNotificationEvalMatcherSettings',
+	    cbind: {
+		baseUrl: '{baseUrl}',
+		name: '{name}',
+	    },
+	},
     ],
 });
 
@@ -1372,3 +1483,110 @@ Ext.define('Proxmox.panel.MatchFieldSettings', {
 	me.callParent();
     },
 });
+
+Ext.define('Proxmox.panel.EvalMatcherSettings', {
+    extend: 'Ext.panel.Panel',
+    xtype: 'pmxNotificationEvalMatcherSettings',
+    border: false,
+    layout: 'anchor',
+    // Hide initially to avoid glitches when opening the window
+    hidden: true,
+    bind: {
+	hidden: '{!typeIsEvalMatcher}',
+    },
+    viewModel: {
+	// parent is set in `initComponents`
+	formulas: {
+	    typeIsEvalMatcher: {
+		bind: {
+		    bindTo: '{selectedRecord}',
+		    deep: true,
+		},
+		get: function(record) {
+		    return record?.get('type') === 'eval-matcher';
+		},
+	    },
+	    evalMatcherValue: {
+		bind: {
+		    bindTo: '{selectedRecord}',
+		    deep: true,
+		},
+		set: function(value) {
+		    let record = this.get('selectedRecord');
+		    let currentData = record.get('data');
+		    record.set({
+			data: {
+			    ...currentData,
+			    matcher: value,
+			},
+		    });
+		},
+		get: function(record) {
+		    return record?.get('data')?.matcher;
+		},
+	    },
+	},
+    },
+
+    initComponent: function() {
+	let me = this;
+
+	let valueStore = Ext.create('Ext.data.Store', {
+	    model: 'proxmox-notification-matchers',
+	    autoLoad: true,
+	    proxy: {
+		type: 'proxmox',
+		url: `/api2/json/${me.baseUrl}/matchers`,
+	    },
+	    filters: [
+		{
+		    property: 'nested',
+		    value: true,
+		},
+		(item) => item.get('name') !== me.name,
+	    ],
+	});
+
+	Ext.apply(me.viewModel, {
+	    parent: me.up('pmxNotificationMatchRulesEditPanel').getViewModel(),
+	});
+	Ext.apply(me, {
+	    items: [
+		{
+		    fieldLabel: gettext('Matcher'),
+		    xtype: 'proxmoxComboGrid',
+		    autoSelect: false,
+		    editable: false,
+		    isFormField: false,
+		    submitValue: false,
+		    allowBlank: false,
+		    showClearTrigger: true,
+		    field: 'name',
+		    store: valueStore,
+		    valueField: 'name',
+		    displayField: 'name',
+		    notFoundIsValid: false,
+		    multiSelect: false,
+		    bind: {
+			value: '{evalMatcherValue}',
+		    },
+		    listConfig: {
+			columns: [
+			    {
+				header: gettext('Name'),
+				dataIndex: 'name',
+				flex: 1,
+			    },
+			    {
+				header: gettext('Comment'),
+				dataIndex: 'comment',
+				flex: 2,
+			    },
+			],
+		    },
+		},
+	    ],
+	});
+	me.callParent();
+    },
+});
-- 
2.39.5



_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel


  parent reply	other threads:[~2025-05-21 14:23 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-21 14:23 [pbs-devel] [PATCH proxmox{, -widget-toolkit, -backup} 0/4] notifications: add support for nested matchers Lukas Wagner
2025-05-21 14:23 ` [pbs-devel] [PATCH proxmox 1/2] notify: matcher: allow to evaluate other matchers via `eval-matcher` Lukas Wagner
2025-05-21 14:23 ` [pbs-devel] [PATCH proxmox 2/2] notify: matcher: API: update methods for nested matcher support Lukas Wagner
2025-05-21 14:23 ` Lukas Wagner [this message]
2025-05-21 14:23 ` [pbs-devel] [PATCH proxmox-backup 1/1] docs: notifications: add section about nested matchers Lukas Wagner
2025-06-02 14:35 ` [pbs-devel] [PATCH proxmox{, -widget-toolkit, -backup} 0/4] notifications: add support for " Lukas Wagner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250521142309.264719-4-l.wagner@proxmox.com \
    --to=l.wagner@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal