[pmg-devel] [PATCH pmg-gui v2 1/2] quarantine: refactor spamquarantine controller

Stoiko Ivanov s.ivanov at proxmox.com
Thu Oct 27 21:12:57 CEST 2022


over the time the spam quarantine has gained quite a few nice
usability improvments and features, which would be useful in
the virus and attachment quarantines as well - e.g.:

* multi-mail selection
* keyboard actions
* context menu
* download mail as .eml

this patch splits the controller part into a file of its own, while changing
'var' to 'let' and removing the parts only relevant for the spamquarantine
and adapts the current SpamQuarantine.js to use it.

Signed-off-by: Stoiko Ivanov <s.ivanov at proxmox.com>
---
 js/Makefile                           |   1 +
 js/SpamQuarantine.js                  | 290 +++++++++-----------------
 js/controller/QuarantineController.js | 170 +++++++++++++++
 3 files changed, 265 insertions(+), 196 deletions(-)
 create mode 100644 js/controller/QuarantineController.js

diff --git a/js/Makefile b/js/Makefile
index b904598..9a2bcf2 100644
--- a/js/Makefile
+++ b/js/Makefile
@@ -19,6 +19,7 @@ JSSRC=							\
 	RuleInfo.js					\
 	RuleEditor.js					\
 	MainView.js					\
+	controller/QuarantineController.js		\
 	QuarantineContextMenu.js			\
 	QuarantineList.js				\
 	SpamInfoGrid.js					\
diff --git a/js/SpamQuarantine.js b/js/SpamQuarantine.js
index 6fe20d7..cf7f181 100644
--- a/js/SpamQuarantine.js
+++ b/js/SpamQuarantine.js
@@ -33,225 +33,123 @@ Ext.define('pmg-spam-list', {
     idProperty: 'id',
 });
 
-Ext.define('PMG.SpamQuarantine', {
-    extend: 'Ext.container.Container',
-    xtype: 'pmgSpamQuarantine',
-
-    border: false,
-    layout: { type: 'border' },
+Ext.define('PMG.SpamQuarantineController', {
+    extend: 'PMG.controller.QuarantineController',
+    xtype: 'pmgSpamQuarantineController',
+    alias: 'controller.spamquarantine',
 
-    defaults: { border: false },
+    updatePreview: function(raw, rec) {
+	let me = this;
+	me.lookupReference('spam').setDisabled(false);
 
-    // from mail link
-    cselect: undefined,
+	me.callParent(arguments);
+    },
 
-    viewModel: {
-	parent: null,
-	data: {
-	    mailid: '',
-	},
-	formulas: {
-	    downloadMailURL: get => '/api2/json/quarantine/download?mailid=' + encodeURIComponent(get('mailid')),
-	},
+    multiSelect: function(selection) {
+	let me = this;
+	let spam = me.lookupReference('spam');
+	spam.setDisabled(true);
+	spam.setPressed(false);
+	me.lookupReference('spaminfo').setVisible(false);
+	me.callParent(selection);
     },
-    controller: {
 
-	xclass: 'Ext.app.ViewController',
+    onSelectMail: function() {
+	let me = this;
+	let list = me.lookupReference('list');
+	let selection = list.selModel.getSelection();
+	if (selection.length <= 1) {
+	    let rec = selection[0] || {};
+	    me.lookupReference('spaminfo').setID(rec);
+	}
+	me.callParent();
+    },
 
-	updatePreview: function(raw, rec) {
-	    var preview = this.lookupReference('preview');
 
-	    if (!rec || !rec.data || !rec.data.id) {
-		preview.update('');
-		preview.setDisabled(true);
-		return;
-	    }
+    toggleSpamInfo: function(btn) {
+	var grid = this.lookupReference('spaminfo');
+	grid.setVisible(!grid.isVisible());
+    },
 
-	    let url = `/api2/htmlmail/quarantine/content?id=${rec.data.id}`;
-	    if (raw) {
-		url += '&raw=1';
-	    }
-	    preview.setDisabled(false);
-	    this.lookupReference('raw').setDisabled(false);
-	    this.lookupReference('spam').setDisabled(false);
-	    this.lookupReference('download').setDisabled(false);
-	    preview.update("<iframe frameborder=0 width=100% height=100% sandbox='allow-same-origin' src='" + url +"'></iframe>");
-	},
+    openContextMenu: function(table, record, tr, index, event) {
+	event.stopEvent();
+	let me = this;
+	let list = me.lookup('list');
+	Ext.create('PMG.menu.SpamContextMenu', {
+	    callback: action => me.doAction(action, list.getSelection()),
+	}).showAt(event.getXY());
+    },
 
-	multiSelect: function(selection) {
-	    var preview = this.lookupReference('preview');
-	    var raw = this.lookupReference('raw');
-	    var spam = this.lookupReference('spam');
-	    var spaminfo = this.lookupReference('spaminfo');
-	    var mailinfo = this.lookupReference('mailinfo');
-	    var download = this.lookupReference('download');
+    keyPress: function(table, record, item, index, event) {
+	var me = this;
+	var list = me.lookup('list');
+	var key = event.getKey();
+	var action = '';
+	switch (key) {
+	    case event.DELETE:
+	    case 127:
+		action = 'delete';
+		break;
+	    case Ext.event.Event.D:
+	    case Ext.event.Event.D + 32:
+		action = 'deliver';
+		break;
+	    case Ext.event.Event.W:
+	    case Ext.event.Event.W + 32:
+		action = 'whitelist';
+		break;
+	    case Ext.event.Event.B:
+	    case Ext.event.Event.B + 32:
+		action = 'blacklist';
+		break;
+	}
 
-	    preview.setDisabled(false);
-	    preview.update(`<h3 style="padding-left:5px;">${gettext('Multiple E-Mails selected')} (${selection.length})</h3>`);
-	    raw.setDisabled(true);
-	    spam.setDisabled(true);
-	    spam.setPressed(false);
-	    spaminfo.setVisible(false);
-	    mailinfo.setVisible(false);
-	    download.setDisabled(true);
-	},
+	if (action !== '') {
+	    me.doAction(action, list.getSelection());
+	}
+    },
 
-	toggleRaw: function(button) {
-	    var me = this;
-	    var list = me.lookupReference('list');
-	    var rec = list.selModel.getSelection()[0];
-	    me.lookupReference('mailinfo').setVisible(me.raw);
-	    me.raw = !me.raw;
-	    me.updatePreview(me.raw, rec);
-	},
+    init: function(view) {
+	this.lookup('list').cselect = view.cselect;
+    },
 
-	btnHandler: function(button, e) {
-	    var me = this;
-	    var action = button.reference;
-	    var list = this.lookupReference('list');
-	    var selected = list.getSelection();
-	    me.doAction(action, selected);
+    control: {
+	'button[reference=raw]': {
+	    click: 'toggleRaw',
 	},
-
-	doAction: function(action, selected) {
-	    if (!selected.length) {
-		return;
-	    }
-
-	    var list = this.lookupReference('list');
-
-	    if (selected.length > 1) {
-		let idlist = selected.map(item => item.data.id);
-		Ext.Msg.confirm(
-		    gettext('Confirm'),
-		    Ext.String.format(
-			gettext("Action '{0}' for '{1}' items"),
-			action, selected.length,
-		    ),
-		    async function(button) {
-			if (button !== 'yes') {
-			    return;
-			}
-
-			list.mask(gettext('Processing...'), 'x-mask-loading');
-
-			const sliceSize = 2500, maxInFlight = 2;
-			let batches = [], batchCount = Math.ceil(selected.length / sliceSize);
-			for (let i = 0; i * sliceSize < selected.length; i++) {
-			    let sliceStart = i * sliceSize;
-			    let sliceEnd = Math.min(sliceStart + sliceSize, selected.length);
-			    batches.push(
-				PMG.Async.doQAction(
-				    action,
-				    idlist.slice(sliceStart, sliceEnd),
-				    i + 1,
-				    batchCount,
-				),
-			    );
-			    if (batches.length >= maxInFlight) {
-				await Promise.allSettled(batches); // eslint-disable-line no-await-in-loop
-				batches = [];
-			    }
-			}
-			await Promise.allSettled(batches); // await possible remaining ones
-			list.unmask();
-			// below can be slow, we could remove directly from the in-memory store, but
-			// with lots of elements and some failures we could be quite out of sync?
-			list.getController().load();
-		    },
-		);
-		return;
-	    }
-
-	    PMG.Utils.doQuarantineAction(action, selected[0].data.id, function() {
-		let listController = list.getController();
-		listController.allowPositionSave = false;
-		// success -> remove directly to avoid slow store reload for a single-element action
-		list.getStore().remove(selected[0]);
-		listController.restoreSavedSelection();
-		listController.allowPositionSave = true;
-	    });
+	'button[reference=spam]': {
+	    click: 'toggleSpamInfo',
 	},
-
-	onSelectMail: function() {
-	    var me = this;
-	    var list = this.lookupReference('list');
-	    var selection = list.selModel.getSelection();
-	    if (selection.length > 1) {
-		me.multiSelect(selection);
-		return;
-	    }
-
-	    var rec = selection[0] || {};
-
-	    me.getViewModel().set('mailid', rec.data ? rec.data.id : '');
-	    me.updatePreview(me.raw || false, rec);
-	    me.lookupReference('spaminfo').setID(rec);
-	    me.lookupReference('mailinfo').setVisible(!!rec.data && !me.raw);
-	    me.lookupReference('mailinfo').update(rec.data);
+	'pmgQuarantineList': {
+	    selectionChange: 'onSelectMail',
+	    itemkeypress: 'keyPress',
+	    rowcontextmenu: 'openContextMenu',
 	},
+    },
+});
 
-	toggleSpamInfo: function(btn) {
-	    var grid = this.lookupReference('spaminfo');
-	    grid.setVisible(!grid.isVisible());
-	},
+Ext.define('PMG.SpamQuarantine', {
+    extend: 'Ext.container.Container',
+    xtype: 'pmgSpamQuarantine',
 
-	openContextMenu: function(table, record, tr, index, event) {
-	    event.stopEvent();
-	    let me = this;
-	    let list = me.lookup('list');
-	    Ext.create('PMG.menu.SpamContextMenu', {
-		callback: action => me.doAction(action, list.getSelection()),
-	    }).showAt(event.getXY());
-	},
+    border: false,
+    layout: { type: 'border' },
 
-	keyPress: function(table, record, item, index, event) {
-	    var me = this;
-	    var list = me.lookup('list');
-	    var key = event.getKey();
-	    var action = '';
-	    switch (key) {
-		case event.DELETE:
-		case 127:
-		    action = 'delete';
-		    break;
-		case Ext.event.Event.D:
-		case Ext.event.Event.D + 32:
-		    action = 'deliver';
-		    break;
-		case Ext.event.Event.W:
-		case Ext.event.Event.W + 32:
-		    action = 'whitelist';
-		    break;
-		case Ext.event.Event.B:
-		case Ext.event.Event.B + 32:
-		    action = 'blacklist';
-		    break;
-	    }
+    defaults: { border: false },
 
-	    if (action !== '') {
-		me.doAction(action, list.getSelection());
-	    }
-	},
+    // from mail link
+    cselect: undefined,
 
-	init: function(view) {
-	    this.lookup('list').cselect = view.cselect;
+    viewModel: {
+	parent: null,
+	data: {
+	    mailid: '',
 	},
-
-	control: {
-	    'button[reference=raw]': {
-		click: 'toggleRaw',
-	    },
-	    'button[reference=spam]': {
-		click: 'toggleSpamInfo',
-	    },
-	    'pmgQuarantineList': {
-		selectionChange: 'onSelectMail',
-		itemkeypress: 'keyPress',
-		rowcontextmenu: 'openContextMenu',
-	    },
+	formulas: {
+	    downloadMailURL: get => '/api2/json/quarantine/download?mailid=' + encodeURIComponent(get('mailid')),
 	},
     },
+    controller: 'spamquarantine',
 
     items: [
 	{
diff --git a/js/controller/QuarantineController.js b/js/controller/QuarantineController.js
new file mode 100644
index 0000000..dfe2915
--- /dev/null
+++ b/js/controller/QuarantineController.js
@@ -0,0 +1,170 @@
+Ext.define('PMG.controller.QuarantineController', {
+    extend: 'Ext.app.ViewController',
+    xtype: 'controller.Quarantine',
+    alias: 'controller.quarantine',
+
+    updatePreview: function(raw, rec) {
+	let preview = this.lookupReference('preview');
+
+	if (!rec || !rec.data || !rec.data.id) {
+	    preview.update('');
+	    preview.setDisabled(true);
+	    return;
+	}
+
+	let url = `/api2/htmlmail/quarantine/content?id=${rec.data.id}`;
+	if (raw) {
+	    url += '&raw=1';
+	}
+	preview.setDisabled(false);
+	this.lookupReference('raw').setDisabled(false);
+	this.lookupReference('download').setDisabled(false);
+	preview.update("<iframe frameborder=0 width=100% height=100% sandbox='allow-same-origin' src='" + url +"'></iframe>");
+    },
+
+    multiSelect: function(selection) {
+	let me = this;
+	me.lookupReference('raw').setDisabled(true);
+	me.lookupReference('download').setDisabled(true);
+	me.lookupReference('mailinfo').setVisible(false);
+
+	let preview = me.lookupReference('preview');
+	preview.setDisabled(false);
+	preview.update(`<h3 style="padding-left:5px;">${gettext('Multiple E-Mails selected')} (${selection.length})</h3>`);
+    },
+
+    toggleRaw: function(button) {
+	let me = this;
+	let list = me.lookupReference('list');
+	let rec = list.selModel.getSelection()[0];
+	me.lookupReference('mailinfo').setVisible(me.raw);
+	me.raw = !me.raw;
+	me.updatePreview(me.raw, rec);
+    },
+
+    btnHandler: function(button, e) {
+	let me = this;
+	let action = button.reference;
+	let list = me.lookupReference('list');
+	let selected = list.getSelection();
+	me.doAction(action, selected);
+    },
+
+    doAction: function(action, selected) {
+	if (!selected.length) {
+	    return;
+	}
+
+	let list = this.lookupReference('list');
+
+	if (selected.length > 1) {
+	    let idlist = selected.map(item => item.data.id);
+	    Ext.Msg.confirm(
+		gettext('Confirm'),
+		Ext.String.format(
+		    gettext("Action '{0}' for '{1}' items"),
+		    action, selected.length,
+		),
+		async function(button) {
+		    if (button !== 'yes') {
+			return;
+		    }
+
+		    list.mask(gettext('Processing...'), 'x-mask-loading');
+
+		    const sliceSize = 2500, maxInFlight = 2;
+		    let batches = [], batchCount = Math.ceil(selected.length / sliceSize);
+		    for (let i = 0; i * sliceSize < selected.length; i++) {
+			let sliceStart = i * sliceSize;
+			let sliceEnd = Math.min(sliceStart + sliceSize, selected.length);
+			batches.push(
+			    PMG.Async.doQAction(
+				action,
+				idlist.slice(sliceStart, sliceEnd),
+				i + 1,
+				batchCount,
+			    ),
+			);
+			if (batches.length >= maxInFlight) {
+			    await Promise.allSettled(batches); // eslint-disable-line no-await-in-loop
+			    batches = [];
+			}
+		    }
+		    await Promise.allSettled(batches); // await possible remaining ones
+		    list.unmask();
+		    // below can be slow, we could remove directly from the in-memory store, but
+		    // with lots of elements and some failures we could be quite out of sync?
+		    list.getController().load();
+		},
+	    );
+	    return;
+	}
+
+	PMG.Utils.doQuarantineAction(action, selected[0].data.id, function() {
+	    let listController = list.getController();
+	    listController.allowPositionSave = false;
+	    // success -> remove directly to avoid slow store reload for a single-element action
+	    list.getStore().remove(selected[0]);
+	    listController.restoreSavedSelection();
+	    listController.allowPositionSave = true;
+	});
+    },
+
+    onSelectMail: function() {
+	let me = this;
+	let list = this.lookupReference('list');
+	let selection = list.selModel.getSelection();
+	if (selection.length > 1) {
+	    me.multiSelect(selection);
+	    return;
+	}
+
+	let rec = selection[0] || {};
+
+	me.getViewModel().set('mailid', rec.data ? rec.data.id : '');
+	me.updatePreview(me.raw || false, rec);
+	me.lookupReference('mailinfo').setVisible(!!rec.data && !me.raw);
+	me.lookupReference('mailinfo').update(rec.data);
+    },
+
+    openContextMenu: function(table, record, tr, index, event) {
+	event.stopEvent();
+	let me = this;
+	let list = me.lookup('list');
+	Ext.create('PMG.menu.QuarantineContextMenu', {
+	    callback: action => me.doAction(action, list.getSelection()),
+	}).showAt(event.getXY());
+    },
+
+    keyPress: function(table, record, item, index, event) {
+	let me = this;
+	let list = me.lookup('list');
+	let key = event.getKey();
+	let action = '';
+	switch (key) {
+	    case event.DELETE:
+	    case 127:
+		action = 'delete';
+		break;
+	    case Ext.event.Event.D:
+	    case Ext.event.Event.D + 32:
+		action = 'deliver';
+		break;
+	}
+
+	if (action !== '') {
+	    me.doAction(action, list.getSelection());
+	}
+    },
+
+    control: {
+	'button[reference=raw]': {
+	    click: 'toggleRaw',
+	},
+	'pmgQuarantineList': {
+	    selectionChange: 'onSelectMail',
+	    itemkeypress: 'keyPress',
+	    rowcontextmenu: 'openContextMenu',
+	},
+    },
+});
-- 
2.30.2





More information about the pmg-devel mailing list