]> git.proxmox.com Git - pmg-gui.git/blob - js/SpamQuarantine.js
a68568602e5ef136668daf47b1b38cbe65b5153d
[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 });
167 },
168
169 onSelectMail: function() {
170 var me = this;
171 var list = this.lookupReference('list');
172 var selection = list.selModel.getSelection();
173 if (selection.length > 1) {
174 me.multiSelect(selection);
175 return;
176 }
177
178 var rec = selection[0] || {};
179
180 me.getViewModel().set('mailid', rec.data ? rec.data.id : '');
181 me.updatePreview(me.raw || false, rec);
182 me.lookupReference('spaminfo').setID(rec);
183 me.lookupReference('mailinfo').setVisible(!!rec.data && !me.raw);
184 me.lookupReference('mailinfo').update(rec.data);
185 },
186
187 toggleSpamInfo: function(btn) {
188 var grid = this.lookupReference('spaminfo');
189 grid.setVisible(!grid.isVisible());
190 },
191
192 openContextMenu: function(table, record, tr, index, event) {
193 event.stopEvent();
194 let me = this;
195 let list = me.lookup('list');
196 Ext.create('PMG.menu.SpamContextMenu', {
197 callback: action => me.doAction(action, list.getSelection()),
198 }).showAt(event.getXY());
199 },
200
201 keyPress: function(table, record, item, index, event) {
202 var me = this;
203 var list = me.lookup('list');
204 var key = event.getKey();
205 var action = '';
206 switch (key) {
207 case event.DELETE:
208 case 127:
209 action = 'delete';
210 break;
211 case Ext.event.Event.D:
212 case Ext.event.Event.D + 32:
213 action = 'deliver';
214 break;
215 case Ext.event.Event.W:
216 case Ext.event.Event.W + 32:
217 action = 'whitelist';
218 break;
219 case Ext.event.Event.B:
220 case Ext.event.Event.B + 32:
221 action = 'blacklist';
222 break;
223 }
224
225 if (action !== '') {
226 me.doAction(action, list.getSelection());
227 }
228 },
229
230 init: function(view) {
231 this.lookup('list').cselect = view.cselect;
232 },
233
234 control: {
235 'button[reference=raw]': {
236 click: 'toggleRaw',
237 },
238 'button[reference=spam]': {
239 click: 'toggleSpamInfo',
240 },
241 'pmgQuarantineList': {
242 selectionChange: 'onSelectMail',
243 itemkeypress: 'keyPress',
244 rowcontextmenu: 'openContextMenu',
245 },
246 },
247 },
248
249 items: [
250 {
251 title: gettext('Spam Quarantine'),
252 xtype: 'pmgQuarantineList',
253 selModel: 'checkboxmodel',
254 emailSelection: true,
255 reference: 'list',
256 region: 'west',
257 width: 500,
258 split: true,
259 collapsible: false,
260 store: {
261 model: 'pmg-spam-list',
262 groupField: 'day',
263 groupDir: 'DESC',
264 sorters: [{
265 property: 'time',
266 direction: 'DESC',
267 }],
268 },
269
270 columns: [
271 {
272 header: gettext('Sender/Subject'),
273 dataIndex: 'subject',
274 renderer: PMG.Utils.sender_renderer,
275 flex: 1,
276 },
277 {
278 header: gettext('Score'),
279 dataIndex: 'spamlevel',
280 align: 'right',
281 width: 70,
282 },
283 {
284 header: gettext('Size') + ' (KB)',
285 renderer: function(v) { return Ext.Number.toFixed(v/1024, 0); },
286 dataIndex: 'bytes',
287 align: 'right',
288 width: 90,
289 },
290 {
291 header: gettext('Date'),
292 dataIndex: 'day',
293 hidden: true,
294 },
295 {
296 xtype: 'datecolumn',
297 header: gettext('Time'),
298 dataIndex: 'time',
299 format: 'H:i:s',
300 },
301 ],
302 },
303 {
304 title: gettext('Selected Mail'),
305 border: false,
306 region: 'center',
307 split: true,
308 reference: 'preview',
309 disabled: true,
310 dockedItems: [
311 {
312 xtype: 'toolbar',
313 dock: 'top',
314 items: [
315 {
316 xtype: 'button',
317 reference: 'raw',
318 text: gettext('Toggle Raw'),
319 enableToggle: true,
320 iconCls: 'fa fa-file-code-o',
321 },
322 {
323 xtype: 'button',
324 reference: 'spam',
325 text: gettext('Toggle Spam Info'),
326 enableToggle: true,
327 iconCls: 'fa fa-bullhorn',
328 },
329 '->',
330 {
331 xtype: 'button',
332 reference: 'download',
333 text: gettext('Download'),
334 setDownload: function(id) {
335 this.el.dom.download = id + ".eml";
336 },
337 bind: {
338 href: '{downloadMailURL}',
339 download: '{mailid}',
340 },
341 iconCls: 'fa fa-download',
342 },
343 '-',
344 {
345 reference: 'whitelist',
346 text: gettext('Whitelist'),
347 iconCls: 'fa fa-check',
348 handler: 'btnHandler',
349 },
350 {
351 reference: 'blacklist',
352 text: gettext('Blacklist'),
353 iconCls: 'fa fa-times',
354 handler: 'btnHandler',
355 },
356 {
357 reference: 'deliver',
358 text: gettext('Deliver'),
359 iconCls: 'fa fa-paper-plane-o',
360 handler: 'btnHandler',
361 },
362 {
363 reference: 'delete',
364 text: gettext('Delete'),
365 iconCls: 'fa fa-trash-o',
366 handler: 'btnHandler',
367 },
368 ],
369 },
370 {
371 xtype: 'pmgSpamInfoGrid',
372 border: false,
373 reference: 'spaminfo',
374 },
375 {
376 xtype: 'pmgMailInfo',
377 hidden: true,
378 reference: 'mailinfo',
379 },
380 ],
381 },
382 ],
383 });