quarantine: refactor spamquarantine controller

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@proxmox.com>
This commit is contained in:
Stoiko Ivanov 2022-10-27 21:12:57 +02:00 committed by Thomas Lamprecht
parent ce7acf1733
commit e66c888b86
3 changed files with 267 additions and 198 deletions

View File

@ -19,6 +19,7 @@ JSSRC= \
RuleInfo.js \ RuleInfo.js \
RuleEditor.js \ RuleEditor.js \
MainView.js \ MainView.js \
controller/QuarantineController.js \
QuarantineContextMenu.js \ QuarantineContextMenu.js \
QuarantineList.js \ QuarantineList.js \
SpamInfoGrid.js \ SpamInfoGrid.js \

View File

@ -33,6 +33,101 @@ Ext.define('pmg-spam-list', {
idProperty: 'id', idProperty: 'id',
}); });
Ext.define('PMG.SpamQuarantineController', {
extend: 'PMG.controller.QuarantineController',
xtype: 'pmgSpamQuarantineController',
alias: 'controller.spamquarantine',
updatePreview: function(raw, rec) {
let me = this;
me.lookupReference('spam').setDisabled(false);
me.callParent(arguments);
},
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);
},
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();
},
toggleSpamInfo: function(btn) {
var grid = this.lookupReference('spaminfo');
grid.setVisible(!grid.isVisible());
},
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());
},
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;
}
if (action !== '') {
me.doAction(action, list.getSelection());
}
},
init: function(view) {
this.lookup('list').cselect = view.cselect;
},
control: {
'button[reference=raw]': {
click: 'toggleRaw',
},
'button[reference=spam]': {
click: 'toggleSpamInfo',
},
'pmgQuarantineList': {
selectionChange: 'onSelectMail',
itemkeypress: 'keyPress',
rowcontextmenu: 'openContextMenu',
},
},
});
Ext.define('PMG.SpamQuarantine', { Ext.define('PMG.SpamQuarantine', {
extend: 'Ext.container.Container', extend: 'Ext.container.Container',
xtype: 'pmgSpamQuarantine', xtype: 'pmgSpamQuarantine',
@ -54,204 +149,7 @@ Ext.define('PMG.SpamQuarantine', {
downloadMailURL: get => '/api2/json/quarantine/download?mailid=' + encodeURIComponent(get('mailid')), downloadMailURL: get => '/api2/json/quarantine/download?mailid=' + encodeURIComponent(get('mailid')),
}, },
}, },
controller: { controller: 'spamquarantine',
xclass: 'Ext.app.ViewController',
updatePreview: function(raw, rec) {
var 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('spam').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) {
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');
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);
},
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);
},
btnHandler: function(button, e) {
var me = this;
var action = button.reference;
var list = this.lookupReference('list');
var selected = list.getSelection();
me.doAction(action, selected);
},
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;
});
},
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);
},
toggleSpamInfo: function(btn) {
var grid = this.lookupReference('spaminfo');
grid.setVisible(!grid.isVisible());
},
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());
},
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;
}
if (action !== '') {
me.doAction(action, list.getSelection());
}
},
init: function(view) {
this.lookup('list').cselect = view.cselect;
},
control: {
'button[reference=raw]': {
click: 'toggleRaw',
},
'button[reference=spam]': {
click: 'toggleSpamInfo',
},
'pmgQuarantineList': {
selectionChange: 'onSelectMail',
itemkeypress: 'keyPress',
rowcontextmenu: 'openContextMenu',
},
},
},
items: [ items: [
{ {

View File

@ -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',
},
},
});