Skip to content

Commit d17197c

Browse files
authored
v1.7 update
1 parent b805d68 commit d17197c

File tree

15 files changed

+1263
-491
lines changed

15 files changed

+1263
-491
lines changed

CHANGES.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,57 @@
11
Unreleased VER.
2-
- add multiple simultanious text filters
3-
- minify
42
- replace momentjs wih day.js or luxone(?)
53
- make mailcow PR to integrate into
64
- remove python privex helpers code (?)
75
- save settings into cookies (for firefox private window mainly) + accept cookies modal (?)
86
- fix dropdown update if use fomantic ui div based dropdown
9-
- fix sort icon to the same shevrons as main menu gui show/hide buttons
107
- upgrade procedure from GUI (?) if decided to do so use [this manual](https://stackoverflow.com/questions/32163955/how-to-run-shell-script-on-host-from-docker-container/63719458#63719458)
11-
- optimize and fix mail_to and log_lines queries with not(!)
12-
- BUG: fix process of '...and more' in stats
8+
- minify
9+
- add multiple simultanious text filters
10+
- MS Exchange multiple mail_to processing
1311
- [fix of non-dockerizeed deploy issue](https://github.com/drlight17/mta-log-parser/issues/10)
1412

13+
VER. 1.7
14+
15+
- ~~click arrows for modal view to get first and last message correspondly~~
16+
- ~~BUG: loading on top sometimes~~
17+
- ~~home and end key bindings for modal view to get first and last message respectively~~
18+
- ~~sort icon to the same shevrons as main menu gui show/hide buttons~~
19+
- ~~BUG: vertical scroll of modals by arrow keys~~
20+
- ~~TLS + status in the email details~~
21+
- ~~bigger bars and fonts for top charts~~
22+
- ~~main menu buttons border~~
23+
- ~~turn off blur by default for mobile devices (use is_mobile var)~~
24+
- ~~move settings and tips to modal menus~~
25+
- ~~change "Hide" to cross (times icon), cross (times icon) to eye icon, add Hide title, change clear exclude slash eye icon to trash alternate icon (or group two icons like [this](https://fomantic-ui.com/elements/icon.html#icons))~~
26+
- ~~multiple mail_to save as array~~
27+
- ~~remake multiple filtered stats~~
28+
- ~~remake multiple filtered emails and details view (TEST in different MTA!!!)~~
29+
- ~~remake addFilterLink for multiple (TEST in different MTA!!!)~~
30+
- ~~BUG: unknown with all recipients in exchange top 10 recipients stats~~
31+
- ~~BUG: trim comma in exchange recipients~~
32+
- ~~BUG: swipes pass 6 email instead of 1~~
33+
- ~~BUG: context menu is on the left (Macbook)~~
34+
- ~~BUG: multiple with no aliases link on the parent TD~~
35+
- ~~optimize and fix mail_to querie with and without not(!)~~
36+
- ~~show stats context menu with no data~~
37+
- ~~BUG: use stats context menu in mobile view~~
38+
- ~~make stats bars more thick in mobile view~~
39+
- ~~add tips about stats context menu (long press and right click)~~
40+
- ~~BUG: vertical scroll mobile view jump to top (modal details)~~
41+
- ~~BUG: change multiple li to span to fix overflow and hover effects~~
42+
- ~~add postfix orig_to (alias) to multiple mail_to li list (in brackets after to)~~
43+
- ~~BUG: email list and detail alias appearance (details look awful)~~
44+
- ~~BUG: fix stats top mail_to_alias appearance~~
45+
- ~~stats context menu max height with scroll for many hiddens + place context menu to the left from cursor~~
46+
- ~~mobile multiple view aliases in one row (hover effect must be fixed)~~
47+
- ~~BUG: emails list multiple overflow view~~
48+
- ~~BUG: prevent vertical scrolling when swiping left and right (modal details)~~
49+
- ~~BUG: prevent swipe when swiping log_lines in (modal details)~~
50+
- ~~BUG: fix postfix mail_to multiple regex (with orig_to)~~
51+
- ~~hidden addresses stats indicator~~
52+
- ~~show slow log_lines queries attention after choosing this filter (notie)~~
53+
- ~~BUG: emails list width is wider then other gui blocks with editable column width turned on~~
54+
- ~~BUG: fix stat cached over hover details~~
1555

1656
VER. 1.6
1757
- ~~use more then one CPU core when parsing (moment timestamp convert of related logs is variable)~~

mlp/core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ async def get_rethink() -> Tuple[DB, DefaultConnection, RethinkDB]:
8585
idxs = await db.table(t).index_list().run(conn, array_limit=settings.rethink_arr_limit)
8686
for index in indexes:
8787
if index not in idxs:
88-
8988
log.debug('Index %s on table %s did not exist. Creating.', index, t)
9089
if index == 'status_code':
9190
# TODO need to test index creation
9291
await db.table(t).index_create(index,r.row['status']['code'], multi=True).run(conn, array_limit=settings.rethink_arr_limit)
92+
#if index == 'lines':
93+
# await db.table(t).index_create(index, lambda row: row['lines'].map(lambda line: [line['message']]).reduce(lambda left, right: left.set_union(right)), multi=True).run(conn, array_limit=settings.rethink_arr_limit)
9394
else:
9495
await db.table(t).index_create(index).run(conn, array_limit=settings.rethink_arr_limit)
9596

mlp/main.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,21 @@
2828
import email
2929
import base64
3030
import quopri
31+
import json
32+
3133
from datetime import datetime, timedelta, timezone
3234
from quart import jsonify
3335

36+
from collections import defaultdict
37+
3438
import moment
3539
import datefinder
3640

3741
log = logging.getLogger(__name__)
3842

3943
# !!! change version upon update !!!
4044
global VERSION
41-
VERSION ="1.6"
45+
VERSION ="1.7"
4246

4347
# postf_match += r'([A-F0-9]{11})\:[ \t]+?(.*)'
4448
#postf_match = r'([A-Za-z]+[ \t]+[0-9]+[ \t]+[0-9]+\:[0-9]+:[0-9]+).*'
@@ -156,7 +160,9 @@ async def housekeeping(housekeeping_days):
156160
async def import_log(logfile: str) -> Dict[str, PostfixMessage]:
157161
log.info('Opening log file %s', logfile)
158162
messages = {}
159-
multiple_recipients = []
163+
multiple_recipients_qids = []
164+
multiple_recipients = defaultdict(list)
165+
160166
counter = 0
161167
same_qid = ''
162168
# avoid utf-8 codec error
@@ -217,8 +223,36 @@ async def import_log(logfile: str) -> Dict[str, PostfixMessage]:
217223
messages[qid].merge(await parse_line(msg))
218224
#print(messages[qid])
219225
#print(msg)
226+
227+
checking_mailto_alias = {}
228+
229+
if settings.mta == 'postfix':
230+
if messages[qid].get('mail_to_alias') is not None:
231+
if messages[qid].get('mail_to_alias') != {}:
232+
#print(messages[qid].get('mail_to'))
233+
#print(messages[qid].get('mail_to_alias'))
234+
#checking_mailto_alias_dict = messages[qid]['mail_to_alias']
235+
try:
236+
subdict = {}
237+
subdict['mail_to'] = messages[qid]['mail_to']
238+
subdict['mail_to_alias'] = messages[qid]['mail_to_alias'][messages[qid].get('mail_to')]
239+
240+
checking_mailto_alias[qid] = subdict
241+
#print(checking_mailto_alias)
242+
except KeyError:
243+
continue
244+
245+
'''if checking_mailto_alias is not None and checking_mailto_alias != {}:
246+
checking_mailto = checking_mailto_alias[qid]['mail_to']
247+
else:
248+
checking_mailto = messages[qid]['mail_to']'''
249+
220250
checking_mailto = messages[qid]['mail_to']
221-
if qid not in set(multiple_recipients):
251+
# remove commas for ms exchange log
252+
if settings.mta == 'exchange':
253+
messages[qid]['mail_to'] = messages[qid]['mail_to'].replace(",", "")
254+
255+
if qid not in set(multiple_recipients_qids):
222256
if qid == same_qid or same_qid == '':
223257
if messages[qid]['status'].get('code') is not None:
224258
# check if there are already recipients in message and there are recipients parsed
@@ -227,22 +261,37 @@ async def import_log(logfile: str) -> Dict[str, PostfixMessage]:
227261
if checking_mailto in msg:
228262
same_qid = qid
229263
counter += 1
264+
# don't add email duplicates
265+
if checking_mailto not in multiple_recipients[same_qid]:
266+
# add alias dict if any
267+
if checking_mailto_alias is not None and checking_mailto_alias != {}:
268+
checking_mailto = checking_mailto_alias[qid]
269+
multiple_recipients[same_qid].append(checking_mailto)
230270
else:
231271
#print("New message ID: ", qid)
232272
#print("There are", counter,"recipients in message id",same_qid)
233273
if counter > 1:
234-
multiple_recipients.append(same_qid)
274+
multiple_recipients_qids.append(same_qid)
235275
counter = 0
236276
same_qid = ''
237277

238278
#print(await parse_line(msg[1]))
239279
messages[qid].lines.append(PostfixLog(timestamp=dtime, queue_id=qid, message=msg))
280+
# fix for the last log line if multiple
281+
if counter > 1:
282+
multiple_recipients_qids.append(same_qid)
240283

284+
# clear multiple_recipients from the qids with < 2 recipients
285+
for k in list(multiple_recipients):
286+
if k not in multiple_recipients_qids:
287+
del multiple_recipients[k]
288+
241289
#print(multiple_recipients)
242290
#print(messages)
243291
log.info('Finished parsing log file %s', logfile)
244292
output = dict();
245293
output['messages'] = messages
294+
output['multiple_recipients_qids'] = multiple_recipients_qids
246295
output['multiple_recipients'] = multiple_recipients
247296
return output
248297

@@ -306,6 +355,7 @@ async def main():
306355
log.info('Importing %s log file', settings.mta)
307356
import_output = await import_log(settings.mail_log)
308357
log.info('Converting %s log data into list',settings.mta)
358+
multiple_recipients_qids = import_output['multiple_recipients_qids']
309359
multiple_recipients = import_output['multiple_recipients']
310360
msgs = import_output['messages']
311361
msg_list = [{"id": qid, **msg.clean_dict(convert_time=r_q.expr)} for qid, msg in msgs.items()]
@@ -355,8 +405,11 @@ async def main():
355405
continue
356406
# check if there are many recipients
357407
if m.get('id') in set(multiple_recipients):
358-
#print("There multiple recipients in ",m.get('id'))
359-
m['mail_to'] += " and more (check log lines)"
408+
#print("There are",len(multiple_recipients[m.get('id')]),"recipients in ",m.get('id'))
409+
#print(multiple_recipients[m.get('id')])
410+
#m['mail_to'] += " and more (check log lines)"
411+
m['mail_to'] = json.dumps(multiple_recipients[m.get('id')])
412+
#m['mail_to'] = multiple_recipients[m.get('id')]
360413
m['status']['code'] = 'multiple'
361414
m['status']['message'] = 'multiple, see log lines below'
362415

mlp/objects.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@ def clean_dict(self, convert_time=str) -> dict:
5050

5151
return data
5252

53-
5453
@dataclass
5554
class PostfixMessage(Dictable):
5655
timestamp: datetime
5756
queue_id: str
5857
lines: List[PostfixLog] = field(default_factory=list)
5958
mail_to: str = ""
59+
mail_to_alias: dict = field(default_factory=dict)
60+
#mail_to_alias: str = ""
6061
#mail_to: dict = field(default_factory=dict)
6162
mail_from: str = ""
6263
subject: str = ""

mlp/parser.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
log = logging.getLogger(__name__)
2222

2323
# postfix regexp
24-
postf_to = re.compile(r'.*to=<([a-zA-Z0-9-+_.=]+@[a-zA-Z0-9-+_.]+)>')
24+
#OLD!!! postf_to = re.compile(r'.*to=<([a-zA-Z0-9-+_.=]+@[a-zA-Z0-9-+_.]+)>')
25+
#postf_to = re.compile(r'^.*to=<([^>]*)>,.*orig_to=<[^>]*>') <---- this is only for orig_to
26+
#postf_to = re.compile(r'^.*to=<([^>]*)>,.*orig_to=<[^>]*>|^.*to=<([^>]*)>')
27+
postf_to = re.compile(r'^.*to=<([^>]*)>,.*orig_to=<([^>]*)>|^.*to=<([^>]*)>')
2528
postf_from = re.compile(r'.*from=<([a-zA-Z0-9-+_.=]+@[a-zA-Z0-9-+_.]+)>')
2629
postf_subject = re.compile(r'.*header\sSubject:\s(.*)\sfrom\s')
2730
postf_size = re.compile(r'.*size=([0-9]{1,}),.*')
@@ -111,6 +114,8 @@
111114

112115
async def parse_line(mline) -> dict:
113116
lm = {}
117+
#aliases = []
118+
aliases = {}
114119

115120
_to = find_to.match(mline)
116121
#print(mline)
@@ -121,9 +126,22 @@ async def parse_line(mline) -> dict:
121126
_relay = find_relay.match(mline)
122127
_status = find_status.match(mline)
123128

129+
124130
if _to is not None:
125131
if settings.mta == 'exchange':
126132
lm['mail_to'] = _to.group(1)[:-1]
133+
if settings.mta == 'postfix':
134+
if _to.group(2) is not None:
135+
aliases = {_to.group(1):_to.group(2)}
136+
#aliases.append(_to.group(2))
137+
lm['mail_to_alias'] = aliases
138+
139+
if _to.group(1) is not None:
140+
lm['mail_to'] = _to.group(1)
141+
#if _to.group(2) is not None:
142+
# lm['mail_to'] +=' ('+_to.group(2)+')'
143+
else:
144+
lm['mail_to'] = _to.group(3)
127145
else:
128146
lm['mail_to'] = _to.group(1)
129147
if _subject is not None:
@@ -189,17 +207,17 @@ async def parse_line(mline) -> dict:
189207
if _client is not None: lm['client'] = dict(host=_client.group(1)[:-1], ip=_client.group(2)[:-1])
190208
if _status is not None:
191209
if _status.group(1) is not None:
192-
if _status.group(1)[:-1].lower() == 'deliver' or _status.group(1)[:-1].lower() == 'duplicatedeliver' or _status.group(1)[:-1].lower() == 'process' or _status.group(1)[:-1].lower() == 'receive' or _status.group(1)[:-1].lower() == 'redirect' or _status.group(1)[:-1].lower() == 'send' or _status.group(1)[:-1].lower() == 'submit':
193-
lm['status'] = lm['status'] = dict(code='sent', message="")
194-
elif _status.group(1)[:-1].lower() == 'fail' or _status.group(1)[:-1].lower() == 'hadiscard' or _status.group(1)[:-1].lower() == 'moderatorreject' or _status.group(1)[:-1].lower() == 'resubmitfail' or _status.group(1)[:-1].lower() == 'submitfail' or _status.group(1)[:-1].lower() == 'suppressed' or _status.group(1)[:-1].lower() == 'drop' or _status.group(1)[:-1].lower() == 'deliverfail':
195-
lm['status'] = lm['status'] = dict(code='reject', message="")
196-
elif _status.group(1)[:-1].lower() == 'dsn' or _status.group(1)[:-1].lower() == 'haredirect':
197-
lm['status'] = lm['status'] = dict(code='bounced', message="")
198-
elif _status.group(1)[:-1].lower() == 'defer' or _status.group(1)[:-1].lower() == 'resubmitdefer' or _status.group(1)[:-1].lower() == 'submitdefer':
199-
lm['status'] = lm['status'] = dict(code='deferred', message="")
200-
201-
if _status.group(2) is not None:
202-
lm['status']['message'] = _status.group(2)[:-1]
210+
if 'deliver' in _status.group(1)[:-1].lower() or _status.group(1)[:-1].lower() == 'process' or _status.group(1)[:-1].lower() == 'receive' or _status.group(1)[:-1].lower() == 'redirect' or _status.group(1)[:-1].lower() == 'send' or _status.group(1)[:-1].lower() == 'submit':
211+
lm['status'] = lm['status'] = dict(code='sent', message="")
212+
elif 'fail' in _status.group(1)[:-1].lower() or 'discard' in _status.group(1)[:-1].lower() or 'reject' in _status.group(1)[:-1].lower() or _status.group(1)[:-1].lower() == 'suppressed' or _status.group(1)[:-1].lower() == 'drop':
213+
lm['status'] = lm['status'] = dict(code='reject', message="")
214+
elif 'redirect' in _status.group(1)[:-1].lower() or _status.group(1)[:-1].lower() == 'dsn':
215+
lm['status'] = lm['status'] = dict(code='bounced', message="")
216+
elif 'defer' in _status.group(1)[:-1].lower():
217+
lm['status'] = lm['status'] = dict(code='deferred', message="")
218+
if _status.group(2) is not None:
219+
if _status.group(2)[:-1] != "":
220+
lm['status']['message'] = _status.group(2)[:-1]
203221

204222
elif settings.mta == 'postfix':
205223
if _relay is not None: lm['relay'] = dict(host=_relay.group(1), ip=_relay.group(2), port=_relay.group(3))

mlp/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
rethink_arr_limit = int(env('RETHINK_ARR_LIMIT', 100000))
4242

4343
rethink_tables = [
44-
('sent_mail', 'auth', ['status_code', 'mail_from', 'mail_to', 'timestamp', 'first_attempt', 'last_attempt']),
44+
('sent_mail', 'auth', ['status_code', 'mail_from', 'mail_to', 'mail_to_alias', 'timestamp', 'first_attempt', 'last_attempt']),
4545
]
4646

4747
# add ldap vars from env

0 commit comments

Comments
 (0)