postmortem 0.2.0 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -46
- data/.ruby-version +1 -1
- data/Gemfile +0 -2
- data/Makefile +4 -0
- data/README.md +20 -16
- data/layout/default.html.erb +46 -26
- data/layout/dependencies.css +11 -0
- data/layout/dependencies.js +14 -0
- data/layout/favicon.b64 +1 -0
- data/layout/layout.css +124 -17
- data/layout/layout.js +212 -52
- data/layout/postmortem_identity.html.erb +20 -0
- data/layout/{index.html.erb → postmortem_index.html.erb} +3 -6
- data/lib/postmortem.rb +5 -2
- data/lib/postmortem/adapters.rb +1 -1
- data/lib/postmortem/adapters/action_mailer.rb +6 -41
- data/lib/postmortem/adapters/base.rb +43 -2
- data/lib/postmortem/adapters/mail.rb +3 -3
- data/lib/postmortem/adapters/pony.rb +3 -6
- data/lib/postmortem/configuration.rb +1 -9
- data/lib/postmortem/delivery.rb +9 -15
- data/lib/postmortem/identity.rb +20 -0
- data/lib/postmortem/index.rb +18 -6
- data/lib/postmortem/layout.rb +20 -1
- data/lib/postmortem/plugins/action_mailer.rb +7 -0
- data/lib/postmortem/version.rb +1 -1
- data/postmortem.gemspec +6 -3
- metadata +54 -8
- data/doc/screenshot.png +0 -0
data/layout/layout.js
CHANGED
@@ -1,13 +1,30 @@
|
|
1
1
|
(function () {
|
2
|
+
if (POSTMORTEM && POSTMORTEM.downloadedPreview) document.title = 'PostMortem';
|
3
|
+
|
2
4
|
let previousInbox;
|
3
5
|
let currentView;
|
6
|
+
let reloadIdentityIframeTimeout;
|
7
|
+
let identityUuid;
|
4
8
|
let indexUuid;
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
let twoColumnView;
|
10
|
+
let headersView;
|
11
|
+
let indexIframeTimeout;
|
12
|
+
|
13
|
+
const inboxContent = [];
|
14
|
+
const headers = document.querySelector('.headers');
|
15
|
+
const inbox = document.querySelector('#inbox-container');
|
16
|
+
const inboxInfo = document.querySelector('#inbox-info');
|
17
|
+
const columnSwitch = document.querySelector('.column-switch');
|
18
|
+
const headersViewSwitch = document.querySelector('.headers-view-switch');
|
19
|
+
const readAllButton = document.querySelector('.read-all-button');
|
20
|
+
const showHideReadButton = document.querySelector('.show-hide-read-button');
|
21
|
+
const showHideReadIcon = document.querySelector('.show-hide-read-icon');
|
22
|
+
const htmlIframeDocument = document.querySelector("#html-iframe").contentDocument;
|
23
|
+
const indexIframe = document.querySelector("#index-iframe");
|
24
|
+
const identityIframe = document.querySelector("#identity-iframe");
|
25
|
+
const textIframeDocument = document.querySelector("#text-iframe").contentDocument;
|
26
|
+
const sourceIframeDocument = document.querySelector("#source-iframe").contentDocument;
|
27
|
+
const sourceHighlightBundle = [
|
11
28
|
'<link rel="stylesheet"',
|
12
29
|
' href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/agate.min.css"',
|
13
30
|
' integrity="sha512-mMMMPADD4HAIogAWZbv+WjZTC0itafUZNI0jQm48PMBTApXt11omF5jhS7U1kp3R2Pr6oGJ+JwQKiUkUwCQaUQ=="',
|
@@ -18,46 +35,55 @@
|
|
18
35
|
'<script>hljs.initHighlightingOnLoad();</' + 'script>',
|
19
36
|
].join('\n');
|
20
37
|
|
38
|
+
const storage = window.localStorage;
|
39
|
+
|
21
40
|
const initialize = () => {
|
22
|
-
|
23
|
-
|
24
|
-
let reloadIframeTimeout = setTimeout(() => indexIframe.src += '', 3000);
|
25
|
-
window.addEventListener('message', function (ev) {
|
26
|
-
clearTimeout(reloadIframeTimeout);
|
27
|
-
reloadIframeTimeout = setTimeout(() => indexIframe.src += '', 3000);
|
28
|
-
if (indexUuid !== ev.data.uuid) renderInbox(ev.data.mails);
|
29
|
-
indexUuid = ev.data.uuid;
|
30
|
-
});
|
41
|
+
reloadIdentityIframeTimeout = setTimeout(() => identityIframe.src += '', 3000);
|
31
42
|
|
32
|
-
setInterval(function () {
|
43
|
+
setInterval(function () { identityIframe.contentWindow.postMessage('HELO', '*'); }, 1000);
|
33
44
|
|
34
|
-
toolbar.html.onclick
|
35
|
-
toolbar.text.onclick
|
36
|
-
toolbar.source.onclick
|
37
|
-
toolbar.headers.onclick
|
38
|
-
columnSwitch.onclick
|
45
|
+
toolbar.html.onclick = (ev) => setView('html', ev);
|
46
|
+
toolbar.text.onclick = (ev) => setView('text', ev);
|
47
|
+
toolbar.source.onclick = (ev) => setView('source', ev);
|
48
|
+
toolbar.headers.onclick = () => setHeadersView(!headersView);
|
49
|
+
columnSwitch.onclick = () => setColumnView(!twoColumnView);
|
50
|
+
readAllButton.onclick = () => markAllAsRead();
|
51
|
+
showHideReadButton.onclick = () => toggleHideReadMessages();
|
39
52
|
|
40
|
-
if (hasHtml) {
|
53
|
+
if (POSTMORTEM.hasHtml) {
|
41
54
|
setView('html');
|
42
55
|
} else {
|
43
56
|
setView('text');
|
44
57
|
}
|
45
58
|
|
46
|
-
setColumnView(
|
59
|
+
setColumnView(!POSTMORTEM.downloadedPreview);
|
47
60
|
setHeadersView(true);
|
61
|
+
setEnabled(columnSwitch);
|
62
|
+
setOn(columnSwitch);
|
63
|
+
setHidden(inbox, POSTMORTEM.downloadedPreview);
|
48
64
|
|
49
|
-
|
50
|
-
|
65
|
+
if (POSTMORTEM.downloadedPreview) {
|
66
|
+
setHidden(toolbar.download, true);
|
67
|
+
setHidden(columnSwitch, true);
|
68
|
+
} else {
|
69
|
+
window.addEventListener('message', function (ev) {
|
70
|
+
switch (ev.data.type) {
|
71
|
+
case 'index':
|
72
|
+
loadInbox(ev.data.uuid, ev.data.mails);
|
73
|
+
break;
|
74
|
+
case 'identity':
|
75
|
+
compareIdentity(ev.data.uuid);
|
76
|
+
break;
|
77
|
+
};
|
78
|
+
});
|
79
|
+
}
|
51
80
|
|
81
|
+
loadMail(POSTMORTEM.initialData);
|
52
82
|
|
53
|
-
|
54
|
-
|
55
|
-
var headers = document.querySelector('.headers');
|
56
|
-
var inbox = document.querySelector('#inbox');
|
57
|
-
var columnSwitch = document.querySelector('.column-switch');
|
58
|
-
var headersViewSwitch = document.querySelector('.headers-view-switch');
|
83
|
+
$('[data-toggle="tooltip"]').tooltip();
|
84
|
+
}
|
59
85
|
|
60
|
-
|
86
|
+
const setHeadersView = (enableHeadersView) => {
|
61
87
|
headersView = enableHeadersView;
|
62
88
|
if (enableHeadersView) {
|
63
89
|
setOn(headersViewSwitch);
|
@@ -68,9 +94,11 @@
|
|
68
94
|
}
|
69
95
|
};
|
70
96
|
|
71
|
-
|
72
|
-
|
73
|
-
|
97
|
+
const setColumnView = (enableTwoColumnView) => {
|
98
|
+
if (!inbox) return;
|
99
|
+
|
100
|
+
const container = document.querySelector('.container');
|
101
|
+
twoColumnView = POSTMORTEM.downloadedPreview ? false : enableTwoColumnView;
|
74
102
|
if (twoColumnView) {
|
75
103
|
setVisible(inbox, true);
|
76
104
|
setOn(columnSwitch);
|
@@ -82,42 +110,51 @@
|
|
82
110
|
}
|
83
111
|
};
|
84
112
|
|
85
|
-
|
113
|
+
const contexts = ['source', 'text', 'html'];
|
86
114
|
|
87
|
-
|
115
|
+
const views = {
|
88
116
|
source: document.querySelector('.source-view'),
|
89
117
|
html: document.querySelector('.html-view'),
|
90
118
|
text: document.querySelector('.text-view'),
|
91
119
|
};
|
92
120
|
|
93
|
-
|
121
|
+
const toolbar = {
|
94
122
|
source: document.querySelector('.source-view-switch'),
|
95
123
|
html: document.querySelector('.html-view-switch'),
|
96
124
|
text: document.querySelector('.text-view-switch'),
|
97
125
|
headers: document.querySelector('.headers-view-switch'),
|
126
|
+
download: document.querySelector('#download-link'),
|
98
127
|
};
|
99
128
|
|
100
|
-
|
129
|
+
const setOn = function(element) {
|
101
130
|
element.classList.add('text-primary');
|
102
131
|
element.classList.remove('text-secondary');
|
103
132
|
};
|
104
133
|
|
105
|
-
|
134
|
+
const setOff = function(element) {
|
106
135
|
element.classList.add('text-secondary');
|
107
136
|
element.classList.remove('text-primary');
|
108
137
|
};
|
109
138
|
|
110
|
-
|
139
|
+
const setDisabled = function(element) {
|
111
140
|
element.classList.add('disabled');
|
112
141
|
element.classList.remove('text-secondary');
|
113
142
|
};
|
114
143
|
|
115
|
-
|
144
|
+
const setEnabled = function(element) {
|
116
145
|
element.classList.remove('disabled');
|
117
146
|
element.classList.add('text-secondary');
|
118
147
|
};
|
119
148
|
|
120
|
-
|
149
|
+
const setHidden = function(element, hidden) {
|
150
|
+
if (hidden) {
|
151
|
+
element.classList.add('hidden');
|
152
|
+
} else {
|
153
|
+
element.classList.remove('hidden');
|
154
|
+
}
|
155
|
+
};
|
156
|
+
|
157
|
+
const setVisible = function(element, visible) {
|
121
158
|
if (visible) {
|
122
159
|
element.classList.add('visible');
|
123
160
|
} else {
|
@@ -125,9 +162,9 @@
|
|
125
162
|
}
|
126
163
|
};
|
127
164
|
|
128
|
-
|
165
|
+
const setView = function(context, ev) {
|
129
166
|
if (ev && $(ev.target).hasClass('disabled')) return;
|
130
|
-
|
167
|
+
let key;
|
131
168
|
for (i = 0; i < contexts.length; i++) {
|
132
169
|
key = contexts[i];
|
133
170
|
if (key === context) {
|
@@ -170,7 +207,7 @@
|
|
170
207
|
setDisabled(toolbar.text);
|
171
208
|
setView('html');
|
172
209
|
} else {
|
173
|
-
|
210
|
+
setEnabled(toolbar.text);
|
174
211
|
}
|
175
212
|
|
176
213
|
if (!mail.htmlBody) {
|
@@ -181,10 +218,23 @@
|
|
181
218
|
setEnabled(toolbar.html);
|
182
219
|
setEnabled(toolbar.source);
|
183
220
|
}
|
221
|
+
|
184
222
|
setView(currentView);
|
185
223
|
};
|
186
224
|
|
187
225
|
const loadMail = (mail) => {
|
226
|
+
const initializeScript = document.querySelector("#initialize-script");
|
227
|
+
const initObject = {
|
228
|
+
initialData: mail,
|
229
|
+
hasHtml: !!mail.htmlBody,
|
230
|
+
hasText: !!mail.textBody,
|
231
|
+
downloadedPreview: true,
|
232
|
+
};
|
233
|
+
|
234
|
+
initializeScript.text = [
|
235
|
+
`const POSTMORTEM = ${JSON.stringify(initObject)};`
|
236
|
+
].join('\n\n');
|
237
|
+
|
188
238
|
htmlIframeDocument.open();
|
189
239
|
htmlIframeDocument.write(mail.htmlBody);
|
190
240
|
htmlIframeDocument.close();
|
@@ -200,25 +250,135 @@
|
|
200
250
|
|
201
251
|
loadHeaders(mail);
|
202
252
|
loadToolbar(mail);
|
253
|
+
loadDownloadLink(mail);
|
254
|
+
|
255
|
+
highlightMail(mail);
|
256
|
+
markAsRead(mail);
|
257
|
+
updateInboxInfo();
|
258
|
+
};
|
259
|
+
|
260
|
+
const markAsRead = (mail) => {
|
261
|
+
storage.setItem(mail.id, 'read');
|
262
|
+
$(`li[data-email-id="${mail.id}"]`).removeClass('unread');
|
263
|
+
$(readAllButton).blur();
|
264
|
+
};
|
265
|
+
|
266
|
+
const showReadMessages = (show) => {
|
267
|
+
if (show) {
|
268
|
+
inbox.classList.remove('hide-read');
|
269
|
+
} else {
|
270
|
+
inbox.classList.add('hide-read');
|
271
|
+
}
|
203
272
|
};
|
204
273
|
|
205
|
-
const
|
274
|
+
const toggleHideReadMessages = () => {
|
275
|
+
const $target = $(showHideReadButton);
|
276
|
+
const $icon = $(showHideReadIcon);
|
277
|
+
|
278
|
+
if ($target.data('state') === 'hide') {
|
279
|
+
$target.data('state', 'show');
|
280
|
+
$target.attr('data-original-title', 'Hide read messages');
|
281
|
+
$target.attr('title', 'Hide read messages');
|
282
|
+
$icon.removeClass('fa-eye-slash');
|
283
|
+
$icon.addClass('fa fa-eye text-primary');
|
284
|
+
showReadMessages(true);
|
285
|
+
} else {
|
286
|
+
$target.data('state', 'hide');
|
287
|
+
$target.attr('data-original-title', 'Show read messages');
|
288
|
+
$target.attr('title', 'Show read messages');
|
289
|
+
$icon.removeClass('fa-eye text-primary');
|
290
|
+
$icon.addClass('fa fa-eye-slash');
|
291
|
+
showReadMessages(false);
|
292
|
+
}
|
293
|
+
|
294
|
+
$target.blur();
|
295
|
+
};
|
296
|
+
|
297
|
+
const markAllAsRead = () => {
|
298
|
+
inboxContent.forEach(mail => markAsRead(mail));
|
299
|
+
updateInboxInfo();
|
300
|
+
};
|
301
|
+
|
302
|
+
const isNewMail = (mail) => {
|
303
|
+
if (!storage.getItem(mail.id)) return true;
|
304
|
+
|
305
|
+
return false;
|
306
|
+
};
|
307
|
+
|
308
|
+
const highlightMail = (mail) => {
|
309
|
+
window.location.hash = mail.id;
|
310
|
+
const $target = $(`li[data-email-id="${mail.id}"]`);
|
311
|
+
$('.inbox-item').removeClass('active');
|
312
|
+
$target.addClass('active');
|
313
|
+
};
|
314
|
+
|
315
|
+
const loadDownloadLink = (mail) => {
|
316
|
+
const html = document.documentElement.innerHTML;
|
317
|
+
const start = html.indexOf('<!--INBOX-START-->');
|
318
|
+
const end = html.indexOf('<!--INBOX-END-->') + '<!--INBOX-END-->'.length;
|
319
|
+
const modifiedHtml = [html.substring(0, start), html.substring(end + 1, html.length)].join('');
|
320
|
+
const blob = new Blob([modifiedHtml], { type: 'application/octet-stream' });
|
321
|
+
const uri = window.URL.createObjectURL(blob);
|
322
|
+
$("#download-link").attr('href', uri);
|
323
|
+
console.log(mail.subject.replace(/[0-9a-zA-Z_ -]/gi, ''));
|
324
|
+
$("#download-link").attr('download', mail.subject.replace(/[^0-9a-zA-Z_ -]/gi, '') + '.html');
|
325
|
+
};
|
326
|
+
|
327
|
+
const compareIdentity = (uuid) => {
|
328
|
+
clearTimeout(reloadIdentityIframeTimeout);
|
329
|
+
reloadIdentityIframeTimeout = setTimeout(() => identityIframe.src += '', 3000);
|
330
|
+
if (identityUuid !== uuid) {
|
331
|
+
indexIframe.src += '';
|
332
|
+
clearTimeout(indexIframeTimeout);
|
333
|
+
indexIframeTimeout = setInterval(function () { indexIframe.contentWindow.postMessage('HELO', '*'); }, 200);
|
334
|
+
}
|
335
|
+
identityUuid = uuid;
|
336
|
+
};
|
337
|
+
|
338
|
+
const updateInboxInfo = () => {
|
339
|
+
if (!inboxContent.length) return;
|
340
|
+
|
341
|
+
const unreadCount = inboxContent.filter((mail) => isNewMail(mail)).length;
|
342
|
+
document.title = `PostMortem ${unreadCount}/${inboxContent.length} (unread/total)`;
|
343
|
+
inboxInfo.textContent = `${inboxContent.length} emails (${unreadCount} unread)`;
|
344
|
+
inboxInfo.innerHTML = `— ${inboxInfo.innerHTML}`;
|
345
|
+
};
|
346
|
+
|
347
|
+
const loadInbox = (uuid, mails) => {
|
348
|
+
clearTimeout(indexIframeTimeout);
|
349
|
+
inboxContent.splice(0, Infinity, ...mails)
|
350
|
+
if (uuid === indexUuid) {
|
351
|
+
return;
|
352
|
+
}
|
353
|
+
const mailsById = {};
|
206
354
|
const html = mails.map((mail, index) => {
|
207
355
|
const parsedTimestamp = new Date(mail.timestamp);
|
208
356
|
const timestampSpan = `<span class="timestamp">${parsedTimestamp.toLocaleString()}</span>`;
|
209
357
|
const classes = ['list-group-item', 'inbox-item'];
|
210
|
-
|
211
|
-
|
358
|
+
|
359
|
+
if (window.location.hash === '#' + mail.id) classes.push('active');
|
360
|
+
if (isNewMail(mail)) classes.push('unread');
|
361
|
+
|
362
|
+
mailsById[mail.id] = mail;
|
363
|
+
|
364
|
+
return [`<li data-email-id="${mail.id}" class="${classes.join(' ')}">`,
|
365
|
+
`<a title="${mail.subject}" href="javascript:void(0)">`,
|
366
|
+
`<i class="fa fa-envelope-open read-icon"></i>`,
|
367
|
+
`<i class="fa fa-envelope unread-icon"></i>${mail.subject}`,
|
368
|
+
`</a>`,
|
369
|
+
`${timestampSpan}</li>`].join('');
|
212
370
|
});
|
371
|
+
updateInboxInfo();
|
213
372
|
if (arrayIdentical(html, previousInbox)) return;
|
214
373
|
previousInbox = html;
|
215
374
|
$('#inbox').html('<ul class="list-group">' + html.join('\n') + '</ul>');
|
216
375
|
$('.inbox-item').click((ev) => {
|
217
376
|
const $target = $(ev.currentTarget);
|
218
|
-
|
219
|
-
|
220
|
-
$target.addClass('active');
|
377
|
+
const id = $target.data('email-id');
|
378
|
+
setTimeout(() => loadMail(mailsById[id].content), 0);
|
221
379
|
});
|
380
|
+
|
381
|
+
indexUuid = uuid;
|
222
382
|
};
|
223
383
|
|
224
384
|
initialize();
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<body>
|
4
|
+
<div data-uuid="<%= uuid %>" id="identity">
|
5
|
+
</div>
|
6
|
+
<script>
|
7
|
+
window.addEventListener('message', function (ev) {
|
8
|
+
const identity = document.querySelector('#identity');
|
9
|
+
const uuid = identity.dataset.uuid;
|
10
|
+
const type = 'identity';
|
11
|
+
ev.source.postMessage({ uuid, type }, '*');
|
12
|
+
});
|
13
|
+
|
14
|
+
setInterval(function () {
|
15
|
+
window.location.reload();
|
16
|
+
}, 3000);
|
17
|
+
|
18
|
+
</script>
|
19
|
+
</body>
|
20
|
+
</html>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
3
|
<body>
|
4
|
-
<div data-uuid="<%=
|
4
|
+
<div data-uuid="<%= uuid %>" id="index">
|
5
5
|
### INDEX START
|
6
6
|
<% encoded_index.each do |encoded| -%>
|
7
7
|
<%= encoded %>
|
@@ -13,16 +13,13 @@
|
|
13
13
|
const filter = (line) => line !== '' && !line.startsWith('### INDEX');
|
14
14
|
const index = document.querySelector('#index');
|
15
15
|
const uuid = index.dataset.uuid;
|
16
|
+
const type = 'index';
|
16
17
|
const mails = index.innerText
|
17
18
|
.split('\n')
|
18
19
|
.filter(filter)
|
19
20
|
.map(line => JSON.parse(atob(line)));
|
20
|
-
ev.source.postMessage({ uuid, mails }, '*');
|
21
|
+
ev.source.postMessage({ uuid, type, mails }, '*');
|
21
22
|
});
|
22
|
-
|
23
|
-
setInterval(function () {
|
24
|
-
window.location.reload();
|
25
|
-
}, 1000);
|
26
23
|
</script>
|
27
24
|
</body>
|
28
25
|
</html>
|
data/lib/postmortem.rb
CHANGED
@@ -8,12 +8,15 @@ require 'mail'
|
|
8
8
|
require 'erb'
|
9
9
|
require 'json'
|
10
10
|
require 'cgi'
|
11
|
+
require 'digest'
|
12
|
+
require 'securerandom'
|
11
13
|
|
12
14
|
require 'postmortem/version'
|
13
15
|
require 'postmortem/adapters'
|
14
16
|
require 'postmortem/delivery'
|
15
17
|
require 'postmortem/layout'
|
16
18
|
require 'postmortem/configuration'
|
19
|
+
require 'postmortem/identity'
|
17
20
|
require 'postmortem/index'
|
18
21
|
|
19
22
|
# HTML email inspection tool.
|
@@ -54,7 +57,7 @@ module Postmortem
|
|
54
57
|
private
|
55
58
|
|
56
59
|
def log_delivery(delivery)
|
57
|
-
output_file.write(colorized(delivery.path.to_s)
|
60
|
+
output_file.write("#{colorized(delivery.path.to_s)}\n")
|
58
61
|
output_file.flush
|
59
62
|
end
|
60
63
|
|
@@ -65,7 +68,7 @@ module Postmortem
|
|
65
68
|
end
|
66
69
|
|
67
70
|
def output_file
|
68
|
-
return
|
71
|
+
return $stdout if config.log_path.nil?
|
69
72
|
|
70
73
|
@output_file ||= File.open(config.log_path, mode: 'a')
|
71
74
|
end
|