mailcatcher-ng 1.3.1 → 1.4.0
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/lib/mail_catcher/mail.rb +42 -7
- data/lib/mail_catcher/smtp.rb +8 -6
- data/lib/mail_catcher/version.rb +1 -1
- data/lib/mail_catcher/web/application.rb +30 -0
- data/lib/mail_catcher.rb +5 -0
- data/public/assets/mailcatcher.css +31 -0
- data/public/assets/mailcatcher.js +4 -4
- data/views/index.erb +36 -4
- data/views/server_info.erb +582 -94
- metadata +1 -1
data/views/server_info.erb
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
<title>Server Information - MailCatcher</title>
|
|
5
5
|
<base href="<%= settings.prefix.chomp("/") %>/">
|
|
6
6
|
<link href="favicon.ico" rel="icon">
|
|
7
|
+
<!-- Tippy.js v6 for session ID tooltips -->
|
|
8
|
+
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
|
9
|
+
<script src="https://unpkg.com/tippy.js@6"></script>
|
|
10
|
+
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/themes/light.css">
|
|
7
11
|
<style>
|
|
8
12
|
* {
|
|
9
13
|
margin: 0;
|
|
@@ -16,37 +20,57 @@
|
|
|
16
20
|
background: #f5f5f5;
|
|
17
21
|
color: #1a1a1a;
|
|
18
22
|
min-height: 100vh;
|
|
23
|
+
display: block;
|
|
24
|
+
padding: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.page-container {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-template-columns: 450px 1fr;
|
|
30
|
+
gap: 32px;
|
|
31
|
+
max-width: 1600px;
|
|
32
|
+
margin: 0 auto;
|
|
33
|
+
padding: 40px;
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.left-column {
|
|
19
38
|
display: flex;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
padding: 20px;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: 24px;
|
|
23
41
|
}
|
|
24
42
|
|
|
25
|
-
.
|
|
43
|
+
.right-column {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 24px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.info-block {
|
|
26
50
|
background: white;
|
|
27
51
|
border-radius: 8px;
|
|
28
|
-
padding:
|
|
52
|
+
padding: 32px;
|
|
29
53
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
30
|
-
max-width: 700px;
|
|
31
|
-
width: 100%;
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
h1 {
|
|
35
57
|
font-size: 24px;
|
|
36
58
|
font-weight: 700;
|
|
37
|
-
margin: 0 0
|
|
59
|
+
margin: 0 0 4px 0;
|
|
60
|
+
color: #1a1a1a;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
h2 {
|
|
64
|
+
font-size: 18px;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
margin: 0;
|
|
38
67
|
color: #1a1a1a;
|
|
39
68
|
}
|
|
40
69
|
|
|
41
70
|
.page-subtitle {
|
|
42
71
|
font-size: 13px;
|
|
43
72
|
color: #666;
|
|
44
|
-
margin-bottom:
|
|
45
|
-
margin-top: 4px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.info-section {
|
|
49
|
-
margin-bottom: 32px;
|
|
73
|
+
margin-bottom: 24px;
|
|
50
74
|
}
|
|
51
75
|
|
|
52
76
|
.section-title {
|
|
@@ -63,12 +87,12 @@
|
|
|
63
87
|
.info-grid {
|
|
64
88
|
display: grid;
|
|
65
89
|
grid-template-columns: 1fr 1fr;
|
|
66
|
-
gap:
|
|
67
|
-
margin-bottom:
|
|
90
|
+
gap: 12px;
|
|
91
|
+
margin-bottom: 0;
|
|
68
92
|
}
|
|
69
93
|
|
|
70
94
|
.info-item {
|
|
71
|
-
padding: 12px
|
|
95
|
+
padding: 10px 12px;
|
|
72
96
|
background: #f9f9f9;
|
|
73
97
|
border-left: 3px solid #2196F3;
|
|
74
98
|
border-radius: 4px;
|
|
@@ -79,26 +103,44 @@
|
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
.info-label {
|
|
82
|
-
font-size:
|
|
106
|
+
font-size: 10px;
|
|
83
107
|
font-weight: 600;
|
|
84
108
|
color: #666;
|
|
85
109
|
text-transform: uppercase;
|
|
86
110
|
letter-spacing: 0.5px;
|
|
87
111
|
display: block;
|
|
88
|
-
margin-bottom:
|
|
112
|
+
margin-bottom: 4px;
|
|
89
113
|
}
|
|
90
114
|
|
|
91
115
|
.info-value {
|
|
92
116
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Courier New', monospace;
|
|
93
|
-
font-size:
|
|
117
|
+
font-size: 13px;
|
|
94
118
|
color: #1a1a1a;
|
|
95
119
|
word-break: break-all;
|
|
96
120
|
}
|
|
97
121
|
|
|
122
|
+
.settings-placeholder {
|
|
123
|
+
text-align: center;
|
|
124
|
+
padding: 48px 32px;
|
|
125
|
+
background: #fafafa;
|
|
126
|
+
border: 2px dashed #e8eaed;
|
|
127
|
+
border-radius: 8px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.settings-placeholder .section-title {
|
|
131
|
+
border-bottom: none;
|
|
132
|
+
margin-bottom: 12px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.coming-soon {
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
color: #999;
|
|
138
|
+
margin: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
98
141
|
.button-group {
|
|
99
142
|
display: flex;
|
|
100
143
|
gap: 12px;
|
|
101
|
-
margin-top: 32px;
|
|
102
144
|
flex-wrap: wrap;
|
|
103
145
|
}
|
|
104
146
|
|
|
@@ -149,104 +191,550 @@
|
|
|
149
191
|
.version-info {
|
|
150
192
|
font-size: 12px;
|
|
151
193
|
color: #999;
|
|
152
|
-
margin-top: 24px;
|
|
153
|
-
padding-top: 16px;
|
|
154
|
-
border-top: 1px solid #e8eaed;
|
|
155
194
|
text-align: center;
|
|
156
195
|
}
|
|
196
|
+
|
|
197
|
+
/* Logs panel styles */
|
|
198
|
+
.logs-panel {
|
|
199
|
+
background: white;
|
|
200
|
+
border-radius: 8px;
|
|
201
|
+
padding: 32px;
|
|
202
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
203
|
+
display: flex;
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
gap: 16px;
|
|
206
|
+
flex: 1;
|
|
207
|
+
min-height: 600px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.log-search-box {
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.log-search-box input {
|
|
215
|
+
width: 100%;
|
|
216
|
+
padding: 10px 36px 10px 12px;
|
|
217
|
+
border: 1px solid #e0e0e0;
|
|
218
|
+
border-radius: 6px;
|
|
219
|
+
font-size: 13px;
|
|
220
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto';
|
|
221
|
+
background: #ffffff;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.log-search-box input:focus {
|
|
225
|
+
outline: none;
|
|
226
|
+
border-color: #2196F3;
|
|
227
|
+
box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.log-search-clear {
|
|
231
|
+
position: absolute;
|
|
232
|
+
right: 10px;
|
|
233
|
+
top: 50%;
|
|
234
|
+
transform: translateY(-50%);
|
|
235
|
+
background: none;
|
|
236
|
+
border: none;
|
|
237
|
+
color: #999;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
display: none;
|
|
240
|
+
font-size: 18px;
|
|
241
|
+
padding: 0;
|
|
242
|
+
width: 20px;
|
|
243
|
+
height: 20px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.log-search-clear:hover {
|
|
247
|
+
color: #666;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.log-container {
|
|
251
|
+
background: #f9f9f9;
|
|
252
|
+
border: 1px solid #e8eaed;
|
|
253
|
+
border-radius: 6px;
|
|
254
|
+
padding: 16px;
|
|
255
|
+
max-height: 600px;
|
|
256
|
+
overflow-y: auto;
|
|
257
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
258
|
+
flex: 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.log-entry {
|
|
262
|
+
display: flex;
|
|
263
|
+
gap: 12px;
|
|
264
|
+
margin: 6px 0;
|
|
265
|
+
padding: 4px 0;
|
|
266
|
+
border-bottom: 1px solid #f0f0f0;
|
|
267
|
+
font-size: 12px;
|
|
268
|
+
align-items: flex-start;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.log-entry:last-child {
|
|
272
|
+
border-bottom: none;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.log-entry.hidden {
|
|
276
|
+
display: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.log-meta {
|
|
280
|
+
display: flex;
|
|
281
|
+
gap: 8px;
|
|
282
|
+
flex-shrink: 0;
|
|
283
|
+
align-items: center;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.log-time {
|
|
287
|
+
color: #999;
|
|
288
|
+
min-width: 100px;
|
|
289
|
+
font-size: 11px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.log-session {
|
|
293
|
+
color: #999;
|
|
294
|
+
font-size: 10px;
|
|
295
|
+
max-width: 150px;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
text-overflow: ellipsis;
|
|
298
|
+
white-space: nowrap;
|
|
299
|
+
cursor: help;
|
|
300
|
+
border-bottom: 1px dotted #ddd;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.log-type {
|
|
304
|
+
min-width: 80px;
|
|
305
|
+
flex-shrink: 0;
|
|
306
|
+
font-size: 11px;
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
text-transform: uppercase;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.log-type.connection { color: #9c27b0; }
|
|
312
|
+
.log-type.command { color: #2196F3; }
|
|
313
|
+
.log-type.response { color: #34a853; }
|
|
314
|
+
.log-type.tls { color: #ff9800; }
|
|
315
|
+
.log-type.data { color: #607d8b; }
|
|
316
|
+
.log-type.error { color: #f44336; }
|
|
317
|
+
|
|
318
|
+
.log-direction {
|
|
319
|
+
min-width: 60px;
|
|
320
|
+
flex-shrink: 0;
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
color: #666;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.log-direction.client::before {
|
|
326
|
+
content: '→ ';
|
|
327
|
+
color: #2196F3;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.log-direction.server::before {
|
|
331
|
+
content: '← ';
|
|
332
|
+
color: #34a853;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.log-message {
|
|
336
|
+
flex: 1;
|
|
337
|
+
color: #1a1a1a;
|
|
338
|
+
font-size: 12px;
|
|
339
|
+
word-break: break-word;
|
|
340
|
+
white-space: pre-wrap;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.log-message.error {
|
|
344
|
+
color: #f44336;
|
|
345
|
+
font-weight: 500;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.no-logs {
|
|
349
|
+
text-align: center;
|
|
350
|
+
padding: 40px 20px;
|
|
351
|
+
color: #999;
|
|
352
|
+
font-size: 14px;
|
|
353
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Session ID tooltip styles */
|
|
357
|
+
.session-tooltip-content {
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
gap: 8px;
|
|
361
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', monospace;
|
|
362
|
+
font-size: 12px;
|
|
363
|
+
padding: 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.session-id-value {
|
|
367
|
+
background: #f5f5f5;
|
|
368
|
+
padding: 6px 8px;
|
|
369
|
+
border-radius: 4px;
|
|
370
|
+
font-family: 'Monaco', 'Courier New', monospace;
|
|
371
|
+
color: #1a1a1a;
|
|
372
|
+
user-select: all;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.copy-session-btn {
|
|
376
|
+
background: none;
|
|
377
|
+
border: none;
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
padding: 4px;
|
|
380
|
+
display: flex;
|
|
381
|
+
align-items: center;
|
|
382
|
+
color: #2196F3;
|
|
383
|
+
transition: all 0.2s;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.copy-session-btn:hover {
|
|
387
|
+
color: #1976D2;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.copy-session-btn svg {
|
|
391
|
+
width: 14px;
|
|
392
|
+
height: 14px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.copy-session-btn.copied {
|
|
396
|
+
color: #34a853;
|
|
397
|
+
}
|
|
157
398
|
</style>
|
|
158
399
|
</head>
|
|
159
400
|
<body>
|
|
160
|
-
<div class="container">
|
|
161
|
-
|
|
162
|
-
<div class="
|
|
401
|
+
<div class="page-container">
|
|
402
|
+
<!-- LEFT COLUMN -->
|
|
403
|
+
<div class="left-column">
|
|
404
|
+
<div class="info-block">
|
|
405
|
+
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px;">
|
|
406
|
+
<div>
|
|
407
|
+
<h1>Server Information</h1>
|
|
408
|
+
<div class="page-subtitle">Configuration</div>
|
|
409
|
+
</div>
|
|
410
|
+
<a href="<%= settings.prefix.chomp("/") %>/" class="btn btn-primary" style="white-space: nowrap; margin-left: 16px;">
|
|
411
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
412
|
+
<line x1="19" y1="12" x2="5" y2="12"></line>
|
|
413
|
+
<polyline points="12 19 5 12 12 5"></polyline>
|
|
414
|
+
</svg>
|
|
415
|
+
Back
|
|
416
|
+
</a>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<div class="section-title">Network</div>
|
|
420
|
+
<div class="info-grid">
|
|
421
|
+
<div class="info-item">
|
|
422
|
+
<span class="info-label">Hostname</span>
|
|
423
|
+
<div class="info-value"><%= @hostname %></div>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<div class="info-item">
|
|
427
|
+
<span class="info-label">FQDN</span>
|
|
428
|
+
<div class="info-value"><%= @fqdn %></div>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
163
431
|
|
|
164
|
-
|
|
165
|
-
|
|
432
|
+
<div class="section-title" style="margin-top: 16px;">SMTP Server</div>
|
|
433
|
+
<div class="info-grid">
|
|
434
|
+
<div class="info-item">
|
|
435
|
+
<span class="info-label">IP Address</span>
|
|
436
|
+
<div class="info-value"><%= @smtp_ip %></div>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
<div class="info-item">
|
|
440
|
+
<span class="info-label">Port</span>
|
|
441
|
+
<div class="info-value"><%= @smtp_port %></div>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
166
444
|
|
|
167
|
-
|
|
168
|
-
<
|
|
169
|
-
|
|
445
|
+
<div class="section-title" style="margin-top: 16px;">HTTP Server</div>
|
|
446
|
+
<div class="info-grid">
|
|
447
|
+
<div class="info-item">
|
|
448
|
+
<span class="info-label">IP Address</span>
|
|
449
|
+
<div class="info-value"><%= @http_ip %></div>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div class="info-item">
|
|
453
|
+
<span class="info-label">Port</span>
|
|
454
|
+
<div class="info-value"><%= @http_port %></div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div class="info-item full">
|
|
458
|
+
<span class="info-label">Base Path</span>
|
|
459
|
+
<div class="info-value"><%= @http_path %></div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
170
462
|
</div>
|
|
171
463
|
|
|
172
|
-
<div class="info-
|
|
173
|
-
<
|
|
174
|
-
<
|
|
464
|
+
<div class="info-block settings-placeholder">
|
|
465
|
+
<div class="section-title">Server Settings</div>
|
|
466
|
+
<p class="coming-soon">Coming soon</p>
|
|
467
|
+
<div class="button-group" style="margin-top: 16px; justify-content: center;">
|
|
468
|
+
<a href="<%= File.join(settings.prefix.chomp("/"), "websocket-test") %>" class="btn btn-secondary">
|
|
469
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
470
|
+
<circle cx="12" cy="12" r="1"></circle>
|
|
471
|
+
<path d="M12 1v6m0 6v4"></path>
|
|
472
|
+
<path d="M4.22 4.22l4.24 4.24m5.08 5.08l4.24 4.24"></path>
|
|
473
|
+
<path d="M1 12h6m6 0h4"></path>
|
|
474
|
+
<path d="M4.22 19.78l4.24-4.24m5.08-5.08l4.24-4.24"></path>
|
|
475
|
+
</svg>
|
|
476
|
+
Diagnostics
|
|
477
|
+
</a>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="version-info" style="margin-top: 12px;">
|
|
480
|
+
MailCatcher v<%= @version %>
|
|
481
|
+
</div>
|
|
175
482
|
</div>
|
|
176
483
|
</div>
|
|
177
484
|
|
|
178
|
-
|
|
179
|
-
|
|
485
|
+
<!-- RIGHT COLUMN -->
|
|
486
|
+
<div class="right-column">
|
|
487
|
+
<div class="logs-panel">
|
|
488
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
489
|
+
<h2>Server Logs</h2>
|
|
490
|
+
<button id="autoRefreshBtn" class="btn btn-secondary" style="padding: 8px 12px; font-size: 12px;">
|
|
491
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px;">
|
|
492
|
+
<path d="M21.5 2v6h-6"></path>
|
|
493
|
+
<path d="M2.5 22v-6h6"></path>
|
|
494
|
+
<path d="M2 11.5a10 10 0 0 1 18.8-4.3"></path>
|
|
495
|
+
<path d="M22 12.5a10 10 0 0 1-18.8 4.2"></path>
|
|
496
|
+
</svg>
|
|
497
|
+
Auto-refresh: ON
|
|
498
|
+
</button>
|
|
499
|
+
</div>
|
|
180
500
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<
|
|
184
|
-
<div class="info-value"><%= @http_connections %></div>
|
|
501
|
+
<div class="log-search-box">
|
|
502
|
+
<input type="text" id="logSearch" placeholder="Filter logs..." />
|
|
503
|
+
<button class="log-search-clear" id="logSearchClear">×</button>
|
|
185
504
|
</div>
|
|
186
505
|
|
|
187
|
-
<div class="
|
|
188
|
-
<
|
|
189
|
-
|
|
506
|
+
<div class="log-container" id="logContainer">
|
|
507
|
+
<div class="no-logs">
|
|
508
|
+
Loading logs...
|
|
509
|
+
</div>
|
|
190
510
|
</div>
|
|
191
511
|
</div>
|
|
192
512
|
</div>
|
|
513
|
+
</div>
|
|
193
514
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
515
|
+
<script>
|
|
516
|
+
// Real-time log refresh system
|
|
517
|
+
var logContainer = document.getElementById('logContainer');
|
|
518
|
+
var searchInput = document.getElementById('logSearch');
|
|
519
|
+
var searchClear = document.getElementById('logSearchClear');
|
|
520
|
+
var autoRefreshBtn = document.getElementById('autoRefreshBtn');
|
|
521
|
+
|
|
522
|
+
var autoRefreshEnabled = true;
|
|
523
|
+
var refreshInterval = null;
|
|
524
|
+
var lastLogCount = 0;
|
|
525
|
+
var allLogEntries = [];
|
|
526
|
+
|
|
527
|
+
// Fetch logs from server
|
|
528
|
+
function fetchLogs() {
|
|
529
|
+
fetch('<%= settings.prefix.chomp("/") %>/logs.json')
|
|
530
|
+
.then(function(response) {
|
|
531
|
+
return response.json();
|
|
532
|
+
})
|
|
533
|
+
.then(function(data) {
|
|
534
|
+
allLogEntries = data.entries || [];
|
|
535
|
+
renderLogs();
|
|
536
|
+
})
|
|
537
|
+
.catch(function(error) {
|
|
538
|
+
console.error('Error fetching logs:', error);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
201
541
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
542
|
+
// Render logs with current search filter applied
|
|
543
|
+
function renderLogs() {
|
|
544
|
+
var query = searchInput.value.toLowerCase().trim();
|
|
545
|
+
|
|
546
|
+
logContainer.innerHTML = '';
|
|
547
|
+
|
|
548
|
+
if (allLogEntries.length === 0) {
|
|
549
|
+
logContainer.innerHTML = '<div class="no-logs">No server logs available</div>';
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
allLogEntries.forEach(function(entry) {
|
|
554
|
+
var searchable = JSON.stringify(entry).toLowerCase();
|
|
555
|
+
var shouldShow = !query || searchable.indexOf(query) >= 0;
|
|
556
|
+
|
|
557
|
+
var timeStr = '';
|
|
558
|
+
try {
|
|
559
|
+
var time = new Date(entry.timestamp);
|
|
560
|
+
var hours = String(time.getHours()).padStart(2, '0');
|
|
561
|
+
var minutes = String(time.getMinutes()).padStart(2, '0');
|
|
562
|
+
var seconds = String(time.getSeconds()).padStart(2, '0');
|
|
563
|
+
var ms = String(time.getMilliseconds()).padStart(3, '0');
|
|
564
|
+
timeStr = hours + ':' + minutes + ':' + seconds + '.' + ms;
|
|
565
|
+
} catch (e) {
|
|
566
|
+
timeStr = '??:??:??';
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
var sessionId = entry.session_id || '';
|
|
570
|
+
var sessionDisplay = sessionId.substring(0, 8) + '...';
|
|
571
|
+
|
|
572
|
+
var entryDiv = document.createElement('div');
|
|
573
|
+
entryDiv.className = 'log-entry' + (shouldShow ? '' : ' hidden');
|
|
574
|
+
entryDiv.setAttribute('data-searchable', searchable);
|
|
575
|
+
entryDiv.innerHTML =
|
|
576
|
+
'<div class="log-meta">' +
|
|
577
|
+
'<div class="log-time">' + timeStr + '</div>' +
|
|
578
|
+
'<div class="log-session" data-session-id="' + escapeHtml(sessionId) + '">' + sessionDisplay + '</div>' +
|
|
579
|
+
'</div>' +
|
|
580
|
+
'<div class="log-type ' + entry.type + '">' + entry.type + '</div>' +
|
|
581
|
+
'<div class="log-direction ' + entry.direction + '">' + entry.direction + '</div>' +
|
|
582
|
+
'<div class="log-message' + (entry.type === 'error' ? ' error' : '') + '">' +
|
|
583
|
+
escapeHtml(entry.message) +
|
|
584
|
+
'</div>';
|
|
585
|
+
|
|
586
|
+
logContainer.appendChild(entryDiv);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Initialize Tippy tooltips for session IDs
|
|
590
|
+
initSessionTooltips();
|
|
591
|
+
|
|
592
|
+
// Auto-scroll to bottom
|
|
593
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
594
|
+
}
|
|
207
595
|
|
|
208
|
-
|
|
209
|
-
|
|
596
|
+
// HTML escape utility
|
|
597
|
+
function escapeHtml(text) {
|
|
598
|
+
var map = {
|
|
599
|
+
'&': '&',
|
|
600
|
+
'<': '<',
|
|
601
|
+
'>': '>',
|
|
602
|
+
'"': '"',
|
|
603
|
+
"'": '''
|
|
604
|
+
};
|
|
605
|
+
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
|
606
|
+
}
|
|
210
607
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
608
|
+
// Filter logs based on search input
|
|
609
|
+
function filterEntries() {
|
|
610
|
+
renderLogs();
|
|
611
|
+
searchClear.style.display = searchInput.value ? 'block' : 'none';
|
|
612
|
+
}
|
|
215
613
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
614
|
+
// Toggle auto-refresh
|
|
615
|
+
function toggleAutoRefresh() {
|
|
616
|
+
autoRefreshEnabled = !autoRefreshEnabled;
|
|
617
|
+
|
|
618
|
+
if (autoRefreshEnabled) {
|
|
619
|
+
autoRefreshBtn.textContent = '';
|
|
620
|
+
autoRefreshBtn.innerHTML =
|
|
621
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px;">' +
|
|
622
|
+
'<path d="M21.5 2v6h-6"></path>' +
|
|
623
|
+
'<path d="M2.5 22v-6h6"></path>' +
|
|
624
|
+
'<path d="M2 11.5a10 10 0 0 1 18.8-4.3"></path>' +
|
|
625
|
+
'<path d="M22 12.5a10 10 0 0 1-18.8 4.2"></path>' +
|
|
626
|
+
'</svg> Auto-refresh: ON';
|
|
627
|
+
|
|
628
|
+
// Fetch any missed logs immediately
|
|
629
|
+
fetchLogs();
|
|
630
|
+
|
|
631
|
+
// Resume auto-refresh interval
|
|
632
|
+
refreshInterval = setInterval(function() {
|
|
633
|
+
fetchLogs();
|
|
634
|
+
}, 1000);
|
|
635
|
+
} else {
|
|
636
|
+
autoRefreshBtn.textContent = '';
|
|
637
|
+
autoRefreshBtn.innerHTML =
|
|
638
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 14px; height: 14px;">' +
|
|
639
|
+
'<path d="M21.5 2v6h-6"></path>' +
|
|
640
|
+
'<path d="M2.5 22v-6h6"></path>' +
|
|
641
|
+
'<path d="M2 11.5a10 10 0 0 1 18.8-4.3"></path>' +
|
|
642
|
+
'<path d="M22 12.5a10 10 0 0 1-18.8 4.2"></path>' +
|
|
643
|
+
'</svg> Auto-refresh: OFF';
|
|
644
|
+
|
|
645
|
+
// Stop auto-refresh interval
|
|
646
|
+
if (refreshInterval) {
|
|
647
|
+
clearInterval(refreshInterval);
|
|
648
|
+
refreshInterval = null;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
220
652
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
653
|
+
// Initialize Tippy tooltips for session IDs
|
|
654
|
+
function initSessionTooltips() {
|
|
655
|
+
var sessionElements = document.querySelectorAll('.log-session[data-session-id]');
|
|
656
|
+
sessionElements.forEach(function(element) {
|
|
657
|
+
// Destroy existing tooltip if it exists
|
|
658
|
+
if (element._tippy) {
|
|
659
|
+
element._tippy.destroy();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
var sessionId = element.getAttribute('data-session-id');
|
|
663
|
+
|
|
664
|
+
// Create tooltip content
|
|
665
|
+
var tooltipDiv = document.createElement('div');
|
|
666
|
+
tooltipDiv.className = 'session-tooltip-content';
|
|
667
|
+
tooltipDiv.innerHTML =
|
|
668
|
+
'<div class="session-id-value">' + escapeHtml(sessionId) + '</div>' +
|
|
669
|
+
'<button class="copy-session-btn" data-session-id="' + escapeHtml(sessionId) + '">' +
|
|
670
|
+
'<svg class="copy-icon" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
|
|
671
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184"></path>' +
|
|
672
|
+
'</svg>' +
|
|
673
|
+
'<svg class="checkmark-icon" style="display: none;" data-slot="icon" fill="none" stroke-width="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">' +
|
|
674
|
+
'<path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"></path>' +
|
|
675
|
+
'</svg>' +
|
|
676
|
+
'</button>';
|
|
677
|
+
|
|
678
|
+
// Create Tippy instance
|
|
679
|
+
tippy(element, {
|
|
680
|
+
content: tooltipDiv,
|
|
681
|
+
theme: 'light',
|
|
682
|
+
placement: 'top',
|
|
683
|
+
interactive: true,
|
|
684
|
+
onShow: function(instance) {
|
|
685
|
+
// Attach click handler when tooltip shows
|
|
686
|
+
var copyBtn = tooltipDiv.querySelector('.copy-session-btn');
|
|
687
|
+
if (copyBtn && !copyBtn._clickHandlerAttached) {
|
|
688
|
+
copyBtn._clickHandlerAttached = true;
|
|
689
|
+
copyBtn.addEventListener('click', function(e) {
|
|
690
|
+
e.preventDefault();
|
|
691
|
+
var sessionIdToCopy = copyBtn.getAttribute('data-session-id');
|
|
692
|
+
copyToClipboard(sessionIdToCopy, copyBtn);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
}
|
|
226
699
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
700
|
+
// Copy session ID to clipboard
|
|
701
|
+
function copyToClipboard(text, button) {
|
|
702
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
703
|
+
var copyIcon = button.querySelector('.copy-icon');
|
|
704
|
+
var checkmarkIcon = button.querySelector('.checkmark-icon');
|
|
705
|
+
|
|
706
|
+
// Show checkmark
|
|
707
|
+
if (copyIcon) copyIcon.style.display = 'none';
|
|
708
|
+
if (checkmarkIcon) checkmarkIcon.style.display = 'block';
|
|
709
|
+
button.classList.add('copied');
|
|
710
|
+
|
|
711
|
+
// Reset after 2 seconds
|
|
712
|
+
setTimeout(function() {
|
|
713
|
+
if (copyIcon) copyIcon.style.display = 'block';
|
|
714
|
+
if (checkmarkIcon) checkmarkIcon.style.display = 'none';
|
|
715
|
+
button.classList.remove('copied');
|
|
716
|
+
}, 2000);
|
|
717
|
+
}).catch(function(err) {
|
|
718
|
+
console.error('Failed to copy:', err);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
246
721
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
722
|
+
// Event listeners
|
|
723
|
+
searchInput.addEventListener('keyup', filterEntries);
|
|
724
|
+
searchClear.addEventListener('click', function() {
|
|
725
|
+
searchInput.value = '';
|
|
726
|
+
filterEntries();
|
|
727
|
+
searchInput.focus();
|
|
728
|
+
});
|
|
729
|
+
autoRefreshBtn.addEventListener('click', toggleAutoRefresh);
|
|
730
|
+
|
|
731
|
+
// Initial load and setup auto-refresh
|
|
732
|
+
fetchLogs();
|
|
733
|
+
refreshInterval = setInterval(function() {
|
|
734
|
+
if (autoRefreshEnabled) {
|
|
735
|
+
fetchLogs();
|
|
736
|
+
}
|
|
737
|
+
}, 1000); // Fetch every 1 second
|
|
738
|
+
</script>
|
|
251
739
|
</body>
|
|
252
740
|
</html>
|