mailcatcher-ng 1.2.0 → 1.3.1

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.
@@ -0,0 +1,240 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <style>
6
+ body {
7
+ margin: 0;
8
+ padding: 20px 28px;
9
+ font-family: 'Monaco', 'Courier New', 'Consolas', monospace;
10
+ font-size: 12px;
11
+ line-height: 1.6;
12
+ background: #ffffff;
13
+ }
14
+ .transcript-header {
15
+ background: #f9f9f9;
16
+ border: 1px solid #e8eaed;
17
+ border-radius: 8px;
18
+ padding: 16px 20px;
19
+ margin-bottom: 20px;
20
+ }
21
+ .transcript-header h3 {
22
+ font-size: 12px;
23
+ font-weight: 600;
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.5px;
26
+ color: #5f5f5f;
27
+ margin: 0 0 12px 0;
28
+ }
29
+ .transcript-info-grid {
30
+ display: grid;
31
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
32
+ gap: 12px;
33
+ }
34
+ .transcript-info-item {
35
+ display: flex;
36
+ flex-direction: column;
37
+ gap: 4px;
38
+ }
39
+ .transcript-info-label {
40
+ font-size: 10px;
41
+ font-weight: 600;
42
+ color: #999;
43
+ text-transform: uppercase;
44
+ letter-spacing: 0.5px;
45
+ }
46
+ .transcript-info-value {
47
+ font-size: 12px;
48
+ color: #1a1a1a;
49
+ font-family: 'Monaco', 'Courier New', monospace;
50
+ word-break: break-all;
51
+ }
52
+ .transcript-search-box {
53
+ margin-bottom: 16px;
54
+ position: relative;
55
+ }
56
+ .transcript-search-box input {
57
+ width: 100%;
58
+ padding: 10px 36px 10px 12px;
59
+ border: 1px solid #e0e0e0;
60
+ border-radius: 6px;
61
+ font-size: 13px;
62
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto';
63
+ background: #ffffff;
64
+ }
65
+ .transcript-search-box input:focus {
66
+ outline: none;
67
+ border-color: #2196F3;
68
+ box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
69
+ }
70
+ .transcript-log {
71
+ background: #f9f9f9;
72
+ border: 1px solid #e8eaed;
73
+ border-radius: 6px;
74
+ padding: 16px;
75
+ max-height: 600px;
76
+ overflow-y: auto;
77
+ font-family: 'Monaco', 'Courier New', monospace;
78
+ }
79
+ .transcript-entry {
80
+ display: flex;
81
+ gap: 12px;
82
+ margin: 6px 0;
83
+ padding: 4px 0;
84
+ border-bottom: 1px solid #f0f0f0;
85
+ }
86
+ .transcript-entry:last-child {
87
+ border-bottom: none;
88
+ }
89
+ .transcript-entry.hidden {
90
+ display: none;
91
+ }
92
+ .transcript-time {
93
+ color: #999;
94
+ min-width: 100px;
95
+ flex-shrink: 0;
96
+ font-size: 11px;
97
+ }
98
+ .transcript-type {
99
+ min-width: 80px;
100
+ flex-shrink: 0;
101
+ font-size: 11px;
102
+ font-weight: 600;
103
+ text-transform: uppercase;
104
+ }
105
+ .transcript-type.connection { color: #9c27b0; }
106
+ .transcript-type.command { color: #2196F3; }
107
+ .transcript-type.response { color: #34a853; }
108
+ .transcript-type.tls { color: #ff9800; }
109
+ .transcript-type.data { color: #607d8b; }
110
+ .transcript-type.error { color: #f44336; }
111
+ .transcript-direction {
112
+ min-width: 60px;
113
+ flex-shrink: 0;
114
+ font-size: 11px;
115
+ color: #666;
116
+ }
117
+ .transcript-direction.client::before {
118
+ content: '→ ';
119
+ color: #2196F3;
120
+ }
121
+ .transcript-direction.server::before {
122
+ content: '← ';
123
+ color: #34a853;
124
+ }
125
+ .transcript-message {
126
+ flex: 1;
127
+ color: #1a1a1a;
128
+ font-size: 12px;
129
+ word-break: break-word;
130
+ white-space: pre-wrap;
131
+ }
132
+ .transcript-message.error {
133
+ color: #f44336;
134
+ font-weight: 500;
135
+ }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <div class="transcript-header">
140
+ <h3>SMTP Session Information</h3>
141
+ <div class="transcript-info-grid">
142
+ <div class="transcript-info-item">
143
+ <div class="transcript-info-label">Client</div>
144
+ <div class="transcript-info-value"><%= transcript['client_ip'] %>:<%= transcript['client_port'] %></div>
145
+ </div>
146
+ <div class="transcript-info-item">
147
+ <div class="transcript-info-label">Server</div>
148
+ <div class="transcript-info-value"><%= transcript['server_ip'] %>:<%= transcript['server_port'] %></div>
149
+ </div>
150
+ <div class="transcript-info-item">
151
+ <div class="transcript-info-label">Session ID</div>
152
+ <div class="transcript-info-value"><%= transcript['session_id'] %></div>
153
+ </div>
154
+ <div class="transcript-info-item">
155
+ <div class="transcript-info-label">TLS</div>
156
+ <div class="transcript-info-value">
157
+ <% if transcript['tls_enabled'] %>
158
+ ✓ <%= transcript['tls_protocol'] || 'Enabled' %>
159
+ <% else %>
160
+ ✗ Not used
161
+ <% end %>
162
+ </div>
163
+ </div>
164
+ <% if transcript['tls_enabled'] && transcript['tls_cipher'] %>
165
+ <div class="transcript-info-item">
166
+ <div class="transcript-info-label">Cipher</div>
167
+ <div class="transcript-info-value"><%= transcript['tls_cipher'] %></div>
168
+ </div>
169
+ <% end %>
170
+ </div>
171
+ </div>
172
+
173
+ <div class="transcript-search-box" style="width: 50%;">
174
+ <input type="text" id="transcriptSearch" placeholder="Filter transcript entries..." style="width: 100%;" />
175
+ <button class="transcript-search-clear" id="transcriptSearchClear" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #999; cursor: pointer; display: none; font-size: 18px; padding: 0; width: 20px; height: 20px;">×</button>
176
+ </div>
177
+
178
+ <div class="transcript-log">
179
+ <% if transcript['entries'] && transcript['entries'].length > 0 %>
180
+ <% transcript['entries'].each do |entry| %>
181
+ <div class="transcript-entry" data-searchable="<%= CGI.escapeHTML(entry.to_json.downcase) %>">
182
+ <div class="transcript-time">
183
+ <%
184
+ time = Time.parse(entry['timestamp'])
185
+ hours = time.strftime('%H')
186
+ minutes = time.strftime('%M')
187
+ seconds = time.strftime('%S')
188
+ ms = (time.usec / 1000).to_s.rjust(3, '0')
189
+ %>
190
+ <%= "#{hours}:#{minutes}:#{seconds}.#{ms}" %>
191
+ </div>
192
+ <div class="transcript-type <%= entry['type'] %>"><%= entry['type'] %></div>
193
+ <div class="transcript-direction <%= entry['direction'] %>"><%= entry['direction'] %></div>
194
+ <div class="transcript-message <%= 'error' if entry['type'] == 'error' %>"><%= CGI.escapeHTML(entry['message']) %></div>
195
+ </div>
196
+ <% end %>
197
+ <% else %>
198
+ <div style="text-align: center; padding: 40px 20px; color: #999; font-size: 14px;">
199
+ No transcript entries found.
200
+ </div>
201
+ <% end %>
202
+ </div>
203
+
204
+ <script>
205
+ var searchInput = document.getElementById('transcriptSearch');
206
+ var searchClear = document.getElementById('transcriptSearchClear');
207
+ var entries = document.querySelectorAll('.transcript-entry');
208
+
209
+ function filterEntries() {
210
+ var query = searchInput.value.toLowerCase().trim();
211
+ entries.forEach(function(entry) {
212
+ if (!query) {
213
+ entry.classList.remove('hidden');
214
+ } else {
215
+ var searchable = entry.getAttribute('data-searchable');
216
+ if (searchable.indexOf(query) >= 0) {
217
+ entry.classList.remove('hidden');
218
+ } else {
219
+ entry.classList.add('hidden');
220
+ }
221
+ }
222
+ });
223
+ searchClear.style.display = query ? 'block' : 'none';
224
+ }
225
+
226
+ searchInput.addEventListener('keyup', filterEntries);
227
+ searchClear.addEventListener('click', function() {
228
+ searchInput.value = '';
229
+ filterEntries();
230
+ searchInput.focus();
231
+ });
232
+
233
+ // Auto-scroll to bottom
234
+ var log = document.querySelector('.transcript-log');
235
+ if (log) {
236
+ log.scrollTop = log.scrollHeight;
237
+ }
238
+ </script>
239
+ </body>
240
+ </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailcatcher-ng
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephane Paquet
@@ -353,11 +353,14 @@ files:
353
353
  - lib/mailcatcher.rb
354
354
  - public/assets/atom-one-light.min.css
355
355
  - public/assets/highlight.min.js
356
+ - public/assets/mailcatcher-ui.js
357
+ - public/assets/mailcatcher.css
356
358
  - public/assets/mailcatcher.js
357
359
  - public/favicon.ico
358
360
  - views/404.erb
359
361
  - views/index.erb
360
362
  - views/server_info.erb
363
+ - views/transcript.erb
361
364
  - views/websocket_test.erb
362
365
  homepage: https://spaquet.github.io/mailcatcher/
363
366
  licenses: