[pbs-devel] [PATCH proxmox-widget-toolkit 1/1] notifications: matchers: add nested matcher support

Lukas Wagner l.wagner at proxmox.com
Wed May 21 16:23:08 CEST 2025


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 at 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





More information about the pbs-devel mailing list