]> git.proxmox.com Git - pmg-gui.git/blob - js/SpamQuarantine.js
quarantine: restore behavior of selecting next in list after action
[pmg-gui.git] / js / SpamQuarantine.js
1 Ext.define('pmg-spam-archive', {
2 extend: 'Ext.data.Model',
3 fields: [
4 { type: 'number', name: 'spamavg' },
5 { type: 'integer', name: 'count' },
6 { type: 'date', dateFormat: 'timestamp', name: 'day' },
7 ],
8 proxy: {
9 type: 'proxmox',
10 url: "/api2/json/quarantine/spam",
11 },
12 idProperty: 'day',
13 });
14
15 Ext.define('pmg-spam-list', {
16 extend: 'Ext.data.Model',
17 fields: ['id', 'envelope_sender', 'from', 'sender', 'receiver', 'subject',
18 { type: 'number', name: 'spamlevel' },
19 { type: 'integer', name: 'bytes' },
20 { type: 'date', dateFormat: 'timestamp', name: 'time' },
21 {
22 type: 'string',
23 name: 'day',
24 convert: function(v, rec) {
25 return Ext.Date.format(rec.get('time'), 'Y-m-d');
26 }, depends: ['time'],
27 },
28 ],
29 proxy: {
30 type: 'proxmox',
31 url: "/api2/json/quarantine/spam",
32 },
33 idProperty: 'id',
34 });
35
36 Ext.define('PMG.SpamQuarantine', {
37 extend: 'Ext.container.Container',
38 xtype: 'pmgSpamQuarantine',
39
40 border: false,
41 layout: { type: 'border' },
42
43 defaults: { border: false },
44
45 // from mail link
46 cselect: undefined,
47
48 viewModel: {
49 parent: null,
50 data: {
51 mailid: '',
52 },
53 formulas: {
54 downloadMailURL: get => '/api2/json/quarantine/download?mailid=' + encodeURIComponent(get('mailid')),
55 },
56 },
57 controller: {
58
59 xclass: 'Ext.app.ViewController',
60
61 updatePreview: function(raw, rec) {
62 var preview = this.lookupReference('preview');
63
64 if (!rec || !rec.data || !rec.data.id) {
65 preview.update('');
66 preview.setDisabled(true);
67 return;
68 }
69
70 var url = '/api2/htmlmail/quarantine/content?id=' + rec.data.id + (raw?'&raw=1':'');
71 preview.setDisabled(false);
72 this.lookupReference('raw').setDisabled(false);
73 this.lookupReference('spam').setDisabled(false);
74 this.lookupReference('download').setDisabled(false);
75 preview.update("<iframe frameborder=0 width=100% height=100% sandbox='allow-same-origin' src='" + url +"'></iframe>");
76 },
77
78 multiSelect: function(selection) {
79 var preview = this.lookupReference('preview');
80 var raw = this.lookupReference('raw');
81 var spam = this.lookupReference('spam');
82 var spaminfo = this.lookupReference('spaminfo');
83 var mailinfo = this.lookupReference('mailinfo');
84 var download = this.lookupReference('download');
85
86 preview.setDisabled(false);
87 preview.update(`<h3 style="padding-left:5px;">${gettext('Multiple E-Mails selected')} (${selection.length})</h3>`);
88 raw.setDisabled(true);
89 spam.setDisabled(true);
90 spam.setPressed(false);
91 spaminfo.setVisible(false);
92 mailinfo.setVisible(false);
93 download.setDisabled(true);
94 },
95
96 toggleRaw: function(button) {
97 var me = this;
98 var list = me.lookupReference('list');
99 var rec = list.selModel.getSelection()[0];
100 me.lookupReference('mailinfo').setVisible(me.raw);
101 me.raw = !me.raw;
102 me.updatePreview(me.raw, rec);
103 },
104
105 btnHandler: function(button, e) {
106 var me = this;
107 var action = button.reference;
108 var list = this.lookupReference('list');
109 var selected = list.getSelection();
110 me.doAction(action, selected);
111 },
112
113 doAction: function(action, selected) {
114 if (!selected.length) {
115 return;
116 }
117
118 var list = this.lookupReference('list');
119
120 if (selected.length > 1) {
121 let idlist = selected.map(item => item.data.id);
122 Ext.Msg.confirm(
123 gettext('Confirm'),
124 Ext.String.format(
125 gettext("Action '{0}' for '{1}' items"),
126 action, selected.length,
127 ),
128 async function(button) {
129 if (button !== 'yes') {
130 return;
131 }
132
133 list.mask(gettext('Processing...'), 'x-mask-loading');
134
135 const sliceSize = 2500, maxInFlight = 2;
136 let batches = [], batchCount = Math.ceil(selected.length / sliceSize);
137 for (let i = 0; i * sliceSize < selected.length; i++) {
138 let sliceStart = i * sliceSize;
139 let sliceEnd = Math.min(sliceStart + sliceSize, selected.length);
140 batches.push(
141 PMG.Async.doQAction(
142 action,
143 idlist.slice(sliceStart, sliceEnd),
144 i + 1,
145 batchCount,
146 ),
147 );
148 if (batches.length >= maxInFlight) {
149 await Promise.allSettled(batches); // eslint-disable-line no-await-in-loop
150 batches = [];
151 }
152 }
153 await Promise.allSettled(batches); // await possible remaining ones
154 list.unmask();
155 // below can be slow, we could remove directly from the in-memory store, but
156 // with lots of elements and some failures we could be quite out of sync?
157 list.getController().load();
158 },
159 );
160 return;
161 }
162
163 PMG.Utils.doQuarantineAction(action, selected[0].data.id, function() {
164 // success -> remove directly to avoid slow store reload for a single-element action
165 list.getStore().remove(selected[0]);
166 list.getController().restoreSavedSelection();
167 });
168 },
169
170 onSelectMail: function() {
171 var me = this;
172 var list = this.lookupReference('list');
173 var selection = list.selModel.getSelection();
174 if (selection.length > 1) {
175 me.multiSelect(selection);
176 return;
177 }
178
179 var rec = selection[0] || {};
180
181 me.getViewModel().set('mailid', rec.data ? rec.data.id : '');
182 me.updatePreview(me.raw || false, rec);
183 me.lookupReference('spaminfo').setID(rec);
184 me.lookupReference('mailinfo').setVisible(!!rec.data && !me.raw);
185 me.lookupReference('mailinfo').update(rec.data);
186 },
187
188 toggleSpamInfo: function(btn) {
189 var grid = this.lookupReference('spaminfo');
190 grid.setVisible(!grid.isVisible());
191 },
192
193 openContextMenu: function(table, record, tr, index, event) {
194 event.stopEvent();
195 let me = this;
196 let list = me.lookup('list');
197 Ext.create('PMG.menu.SpamContextMenu', {
198 callback: action => me.doAction(action, list.getSelection()),
199 }).showAt(event.getXY());
200 },
201
202 keyPress: function(table, record, item, index, event) {
203 var me = this;
204 var list = me.lookup('list');
205 var key = event.getKey();
206 var action = '';
207 switch (key) {
208 case event.DELETE:
209 case 127:
210 action = 'delete';
211 break;
212 case Ext.event.Event.D:
213 case Ext.event.Event.D + 32:
214 action = 'deliver';
215 break;
216 case Ext.event.Event.W:
217 case Ext.event.Event.W + 32:
218 action = 'whitelist';
219 break;
220 case Ext.event.Event.B:
221 case Ext.event.Event.B + 32:
222 action = 'blacklist';
223 break;
224 }
225
226 if (action !== '') {
227 me.doAction(action, list.getSelection());
228 }
229 },
230
231 init: function(view) {
232 this.lookup('list').cselect = view.cselect;
233 },
234
235 control: {
236 'button[reference=raw]': {
237 click: 'toggleRaw',
238 },
239 'button[reference=spam]': {
240 click: 'toggleSpamInfo',
241 },
242 'pmgQuarantineList': {
243 selectionChange: 'onSelectMail',
244 itemkeypress: 'keyPress',
245 rowcontextmenu: 'openContextMenu',
246 },
247 },
248 },
249
250 items: [
251 {
252 title: gettext('Spam Quarantine'),
253 xtype: 'pmgQuarantineList',
254 selModel: 'checkboxmodel',
255 emailSelection: true,
256 reference: 'list',
257 region: 'west',
258 width: 500,
259 split: true,
260 collapsible: false,
261 store: {
262 model: 'pmg-spam-list',
263 groupField: 'day',
264 groupDir: 'DESC',
265 sorters: [{
266 property: 'time',
267 direction: 'DESC',
268 }],
269 },
270
271 columns: [
272 {
273 header: gettext('Sender/Subject'),
274 dataIndex: 'subject',
275 renderer: PMG.Utils.sender_renderer,
276 flex: 1,
277 },
278 {
279 header: gettext('Score'),
280 dataIndex: 'spamlevel',
281 align: 'right',
282 width: 70,
283 },
284 {
285 header: gettext('Size') + ' (KB)',
286 renderer: function(v) { return Ext.Number.toFixed(v/1024, 0); },
287 dataIndex: 'bytes',
288 align: 'right',
289 width: 90,
290 },
291 {
292 header: gettext('Date'),
293 dataIndex: 'day',
294 hidden: true,
295 },
296 {
297 xtype: 'datecolumn',
298 header: gettext('Time'),
299 dataIndex: 'time',
300 format: 'H:i:s',
301 },
302 ],
303 },
304 {
305 title: gettext('Selected Mail'),
306 border: false,
307 region: 'center',
308 split: true,
309 reference: 'preview',
310 disabled: true,
311 dockedItems: [
312 {
313 xtype: 'toolbar',
314 dock: 'top',
315 items: [
316 {
317 xtype: 'button',
318 reference: 'raw',
319 text: gettext('Toggle Raw'),
320 enableToggle: true,
321 iconCls: 'fa fa-file-code-o',
322 },
323 {
324 xtype: 'button',
325 reference: 'spam',
326 text: gettext('Toggle Spam Info'),
327 enableToggle: true,
328 iconCls: 'fa fa-bullhorn',
329 },
330 '->',
331 {
332 xtype: 'button',
333 reference: 'download',
334 text: gettext('Download'),
335 setDownload: function(id) {
336 this.el.dom.download = id + ".eml";
337 },
338 bind: {
339 href: '{downloadMailURL}',
340 download: '{mailid}',
341 },
342 iconCls: 'fa fa-download',
343 },
344 '-',
345 {
346 reference: 'whitelist',
347 text: gettext('Whitelist'),
348 iconCls: 'fa fa-check',
349 handler: 'btnHandler',
350 },
351 {
352 reference: 'blacklist',
353 text: gettext('Blacklist'),
354 iconCls: 'fa fa-times',
355 handler: 'btnHandler',
356 },
357 {
358 reference: 'deliver',
359 text: gettext('Deliver'),
360 iconCls: 'fa fa-paper-plane-o',
361 handler: 'btnHandler',
362 },
363 {
364 reference: 'delete',
365 text: gettext('Delete'),
366 iconCls: 'fa fa-trash-o',
367 handler: 'btnHandler',
368 },
369 ],
370 },
371 {
372 xtype: 'pmgSpamInfoGrid',
373 border: false,
374 reference: 'spaminfo',
375 },
376 {
377 xtype: 'pmgMailInfo',
378 hidden: true,
379 reference: 'mailinfo',
380 },
381 ],
382 },
383 ],
384 });