From e2c7198f53a3eddd6f68c05628b11e45adfdf34a Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Thu, 14 Nov 2019 12:18:55 +0100 Subject: [PATCH] add Custom Scores panel to the Spam Detector with this panel, users can manually override a rule score for SpamAssassin rules. This can be useful sometimes, e.g. if a certain rule always triggers in a certain environment which is considered spam by the SpamAssassin rules after adding/editing a rule, we show diff, similar to the network panel, and offer a button to apply those changes (and restart pmg-smtp-filter). the changes can also be reverted Signed-off-by: Dominik Csapak --- js/Makefile | 1 + js/SpamDetectorConfiguration.js | 7 +- js/SpamDetectorCustom.js | 308 ++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 js/SpamDetectorCustom.js diff --git a/js/Makefile b/js/Makefile index 762b43d..e0ac026 100644 --- a/js/Makefile +++ b/js/Makefile @@ -54,6 +54,7 @@ JSSRC= \ SpamQuarantineOptions.js \ SpamDetectorStatus.js \ SpamDetectorConfiguration.js \ + SpamDetectorCustom.js \ VirusDetectorOptions.js \ VirusQuarantineOptions.js \ VirusQuarantine.js \ diff --git a/js/SpamDetectorConfiguration.js b/js/SpamDetectorConfiguration.js index f10c8dc..2c0d755 100644 --- a/js/SpamDetectorConfiguration.js +++ b/js/SpamDetectorConfiguration.js @@ -23,7 +23,12 @@ Ext.define('PMG.SpamDetectorConfiguration', { title: gettext('Status'), itemId: 'status', xtype: 'pmgSpamDetectorStatus' - } + }, + { + title: gettext('Custom Scores'), + itemId: 'scores', + xtype: 'pmgSpamDetectorCustomScores' + }, ] }); diff --git a/js/SpamDetectorCustom.js b/js/SpamDetectorCustom.js new file mode 100644 index 0000000..34cd95d --- /dev/null +++ b/js/SpamDetectorCustom.js @@ -0,0 +1,308 @@ +Ext.define('pmg-sa-custom', { + extend: 'Ext.data.Model', + fields: [ 'name', 'score', 'comment', 'digest' ], + idProperty: 'name' +}); + +Ext.define('PMG.SpamDetectorCustomScores', { + extend: 'Ext.panel.Panel', + xtype: 'pmgSpamDetectorCustomScores', + + layout: 'border', + + viewModel: { + data: { + applied: true, + changetext: '', + digest: null, + }, + }, + + controller: { + xclass: 'Ext.app.ViewController', + + reload: function() { + var me = this; + var vm = this.getViewModel(); + var grid = me.lookup('grid'); + + Proxmox.Utils.API2Request({ + url: '/config/customscores', + failure: function(response, opts) { + grid.getStore().loadData({}); + Proxmox.Utils.setErrorMask(grid, response.htmlStatus); + vm.set('digest', null); + vm.set('applied', true); + vm.set('changetext', ''); + }, + success: function(response, opts) { + let data = response.result.data; + let digestel = data.pop(); // last element is digest + let changes = response.result.changes; + grid.getStore().loadData(data); + + vm.set('digest', digestel.digest); + vm.set('applied', !changes); + vm.set('changetext', `
${changes || ''}
`); + } + }); + }, + + revert: function() { + var me = this; + var vm = this.getViewModel(); + + Proxmox.Utils.API2Request({ + url: '/config/customscores', + method: 'DELETE', + param: { + digest: vm.get('digest'), + }, + failure: function(response, opts) { + grid.getStore().loadData({}); + Proxmox.Utils.setErrorMask(grid, response.htmlStatus); + vm.set('digest', null); + vm.set('applied', true); + vm.set('changetext', ''); + }, + success: () => { me.reload(); }, + }); + }, + + restart: function() { + var me = this; + var vm = this.getViewModel(); + + var win = Ext.createWidget('proxmoxWindowEdit', { + method: 'PUT', + url: "/api2/extjs/config/customscores", + isCreate: true, + submitText: gettext('Apply'), + showProgress: true, + taskDone: () => { me.reload(); }, + + title: gettext("Apply Custom Scores"), + + items: [ + { + xtype: 'proxmoxcheckbox', + name: 'restart-daemon', + fieldLabel: gettext('Restart pmg-smtp-filter'), + labelWidth: 150, + checked: true, + }, + { + xtype: 'hiddenfield', + name: 'digest', + value: vm.get('digest'), + } + ] + }).show(); + }, + + create_custom: function() { + var me = this; + var vm = this.getViewModel(); + + var win = Ext.createWidget('proxmoxWindowEdit', { + method: 'POST', + url: "/api2/extjs/config/customscores", + isCreate: true, + subject: gettext("Custom Rule Score"), + items: [ + { + xtype: 'proxmoxtextfield', + name: 'name', + allowBlank: false, + fieldLabel: gettext('Name') + }, + { + xtype: 'numberfield', + name: 'score', + allowBlank: false, + fieldLabel: gettext('Score') + }, + + { + xtype: 'proxmoxtextfield', + name: 'comment', + fieldLabel: gettext("Comment") + }, + { + xtype: 'hiddenfield', + name: 'digest', + value: vm.get('digest'), + } + ] + }); + + win.on('destroy', me.reload, me); + win.show(); + }, + + run_editor: function() { + var me = this; + var vm = this.getViewModel(); + var grid = me.lookup('grid'); + var rec = grid.getSelection()[0]; + if (!rec) { + return; + } + + var win = Ext.createWidget('proxmoxWindowEdit', { + url: "/api2/extjs/config/customscores/" + rec.data.name, + method: 'PUT', + subject: gettext("Custom Rule Score"), + items: [ + { + xtype: 'displayfield', + name: 'name', + fieldLabel: gettext('Name') + }, + { + xtype: 'numberfield', + name: 'score', + allowBlank: false, + fieldLabel: gettext('Score') + }, + + { + xtype: 'proxmoxtextfield', + name: 'comment', + fieldLabel: gettext("Comment") + }, + { + xtype: 'hiddenfield', + name: 'digest', + value: vm.get('digest'), + } + ] + }); + + win.load(); + win.on('destroy', me.reload, me); + win.show(); + }, + }, + + listeners: { + activate: 'reload', + }, + + defaults: { + border: 0, + }, + + items: [ + { + xtype: 'gridpanel', + region: 'center', + reference: 'grid', + + store: { + model: 'pmg-sa-custom', + proxy: { + type: 'proxmox', + url: "/api2/json/config/customscores" + }, + sorters: { + property: 'name', + } + }, + + tbar: [ + { + xtype: 'proxmoxButton', + text: gettext('Edit'), + disabled: true, + handler: 'run_editor' + }, + { + text: gettext('Create'), + handler: 'create_custom', + }, + { + xtype: 'proxmoxStdRemoveButton', + getUrl: function(rec) { + let digest = this.up('grid').digest; + let url = `/config/customscores/${rec.getId()}`; + if (digest) { + url += `?digest=${digest}` + } + return url; + }, + callback: 'reload', + }, + ' ', + { + text: gettext('Revert'), + reference: 'revert_btn', + handler: 'revert', + disabled: true, + bind: { + disabled: '{applied}', + }, + }, + '-', + { + text: gettext('Apply Custom Scores'), + reference: 'restart_btn', + disabled: true, + bind: { + disabled: '{applied}', + }, + handler: 'restart', + } + ], + + viewConfig: { + trackOver: false + }, + + columns: [ + { + header: gettext('Name'), + width: 200, + sortable: true, + dataIndex: 'name' + }, + { + header: gettext('Score'), + width: 200, + sortable: true, + dataIndex: 'score' + }, + { + header: gettext('Comment'), + sortable: false, + renderer: Ext.String.htmlEncode, + dataIndex: 'comment', + flex: 1 + } + ], + + listeners: { + itemdblclick: 'run_editor', + } + }, + { + xtype: 'panel', + bodyPadding: 5, + region: 'south', + autoScroll: true, + flex: 0.5, + hidden: true, + bind: { + hidden: '{applied}', + html: '{changetext}' + }, + reference: 'changes', + tbar: [ + gettext('Pending changes') + ' (' + + gettext('Please restart pmg-smtp-filter to activate changes') + ')' + ], + split: true, + } + ], + +}); -- 2.39.5