@ngn-net/nestjs-telescope 0.1.6 → 0.1.8
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.
- package/README.md +42 -34
- package/dist/constants.d.ts +12 -3
- package/dist/constants.js +14 -5
- package/dist/controllers/telescope.controller.d.ts +5 -2
- package/dist/controllers/telescope.controller.js +41 -13
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/interfaces/telescope-options.interface.d.ts +9 -3
- package/dist/storage/entities/telescope-entry.entity.js +34 -7
- package/dist/storage/telescope-repository.service.d.ts +17 -7
- package/dist/storage/telescope-repository.service.js +42 -17
- package/dist/telescope.module.d.ts +6 -2
- package/dist/telescope.module.js +148 -44
- package/dist/telescope.service.d.ts +21 -1
- package/dist/telescope.service.js +72 -22
- package/dist/ui/index.html +1635 -0
- package/dist/watchers/cache.watcher.d.ts +5 -4
- package/dist/watchers/cache.watcher.js +50 -34
- package/dist/watchers/event.watcher.d.ts +7 -3
- package/dist/watchers/event.watcher.js +22 -6
- package/dist/watchers/exception.watcher.js +7 -1
- package/dist/watchers/http-request.watcher.d.ts +3 -1
- package/dist/watchers/http-request.watcher.js +25 -8
- package/dist/watchers/log.watcher.d.ts +1 -0
- package/dist/watchers/log.watcher.js +22 -17
- package/dist/watchers/mail.watcher.d.ts +1 -1
- package/dist/watchers/mail.watcher.js +28 -56
- package/dist/watchers/query.watcher.d.ts +6 -3
- package/dist/watchers/query.watcher.js +36 -8
- package/dist/watchers/queue.watcher.d.ts +2 -4
- package/dist/watchers/queue.watcher.js +38 -32
- package/dist/watchers/redis.watcher.d.ts +3 -1
- package/dist/watchers/redis.watcher.js +16 -4
- package/dist/watchers/schedule.watcher.d.ts +8 -3
- package/dist/watchers/schedule.watcher.js +25 -11
- package/package.json +51 -19
- package/ui/index.html +602 -235
package/ui/index.html
CHANGED
|
@@ -4,28 +4,31 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>NestJS Telescope Dashboard</title>
|
|
7
|
-
<!-- Google Fonts:
|
|
7
|
+
<!-- Google Fonts: Outfit (Premium Sans) & JetBrains Mono -->
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
11
11
|
|
|
12
12
|
<style>
|
|
13
13
|
:root {
|
|
14
|
-
--bg-main: #
|
|
15
|
-
--bg-sidebar:
|
|
16
|
-
--bg-card: rgba(
|
|
17
|
-
--bg-
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
14
|
+
--bg-main: #060913;
|
|
15
|
+
--bg-sidebar: rgba(10, 15, 30, 0.7);
|
|
16
|
+
--bg-card: rgba(16, 22, 42, 0.65);
|
|
17
|
+
--bg-card-hover: rgba(22, 30, 58, 0.85);
|
|
18
|
+
--bg-input: rgba(30, 41, 73, 0.5);
|
|
19
|
+
--border-color: rgba(255, 255, 255, 0.07);
|
|
20
|
+
--border-focus: rgba(99, 102, 241, 0.4);
|
|
21
|
+
--text-main: #f1f5f9;
|
|
22
|
+
--text-muted: #94a3b8;
|
|
21
23
|
--primary: #6366f1;
|
|
22
24
|
--primary-hover: #4f46e5;
|
|
25
|
+
--primary-glow: rgba(99, 102, 241, 0.3);
|
|
23
26
|
--accent-green: #10b981;
|
|
24
|
-
--accent-red: #
|
|
27
|
+
--accent-red: #f43f5e;
|
|
25
28
|
--accent-blue: #3b82f6;
|
|
26
29
|
--accent-purple: #8b5cf6;
|
|
27
30
|
--accent-amber: #f59e0b;
|
|
28
|
-
--font-sans: '
|
|
31
|
+
--font-sans: 'Outfit', sans-serif;
|
|
29
32
|
--font-mono: 'JetBrains Mono', monospace;
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -42,6 +45,9 @@
|
|
|
42
45
|
overflow: hidden;
|
|
43
46
|
height: 100vh;
|
|
44
47
|
display: flex;
|
|
48
|
+
background-image:
|
|
49
|
+
radial-gradient(circle at 10% 20%, rgba(99, 102, 241, 0.05) 0%, transparent 40%),
|
|
50
|
+
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.05) 0%, transparent 40%);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/* Scrollbars */
|
|
@@ -50,7 +56,7 @@
|
|
|
50
56
|
height: 6px;
|
|
51
57
|
}
|
|
52
58
|
::-webkit-scrollbar-track {
|
|
53
|
-
background: rgba(0, 0, 0, 0.
|
|
59
|
+
background: rgba(0, 0, 0, 0.2);
|
|
54
60
|
}
|
|
55
61
|
::-webkit-scrollbar-thumb {
|
|
56
62
|
background: rgba(255, 255, 255, 0.1);
|
|
@@ -66,14 +72,17 @@
|
|
|
66
72
|
display: flex;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
/* Sidebar */
|
|
75
|
+
/* Sidebar with Glassmorphism */
|
|
70
76
|
.sidebar {
|
|
71
77
|
width: 260px;
|
|
72
|
-
background
|
|
78
|
+
background: var(--bg-sidebar);
|
|
79
|
+
backdrop-filter: blur(12px);
|
|
80
|
+
-webkit-backdrop-filter: blur(12px);
|
|
73
81
|
border-right: 1px solid var(--border-color);
|
|
74
82
|
display: flex;
|
|
75
83
|
flex-direction: column;
|
|
76
84
|
flex-shrink: 0;
|
|
85
|
+
z-index: 10;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
.sidebar-header {
|
|
@@ -85,24 +94,24 @@
|
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
.logo {
|
|
88
|
-
background: linear-gradient(135deg, var(--primary), #
|
|
89
|
-
width:
|
|
90
|
-
height:
|
|
91
|
-
border-radius:
|
|
97
|
+
background: linear-gradient(135deg, var(--primary), #8b5cf6);
|
|
98
|
+
width: 38px;
|
|
99
|
+
height: 38px;
|
|
100
|
+
border-radius: 10px;
|
|
92
101
|
display: flex;
|
|
93
102
|
align-items: center;
|
|
94
103
|
justify-content: center;
|
|
95
104
|
font-weight: 700;
|
|
96
105
|
color: #fff;
|
|
97
|
-
font-size: 1.
|
|
98
|
-
box-shadow: 0 0
|
|
106
|
+
font-size: 1.3rem;
|
|
107
|
+
box-shadow: 0 0 20px var(--primary-glow);
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
.sidebar-title {
|
|
102
|
-
font-weight:
|
|
103
|
-
font-size: 1.
|
|
104
|
-
letter-spacing: -0.
|
|
105
|
-
background: linear-gradient(to right, #
|
|
111
|
+
font-weight: 700;
|
|
112
|
+
font-size: 1.2rem;
|
|
113
|
+
letter-spacing: -0.02em;
|
|
114
|
+
background: linear-gradient(to right, #ffffff, #cbd5e1);
|
|
106
115
|
-webkit-background-clip: text;
|
|
107
116
|
-webkit-text-fill-color: transparent;
|
|
108
117
|
}
|
|
@@ -112,7 +121,7 @@
|
|
|
112
121
|
padding: 16px 12px;
|
|
113
122
|
display: flex;
|
|
114
123
|
flex-direction: column;
|
|
115
|
-
gap:
|
|
124
|
+
gap: 6px;
|
|
116
125
|
overflow-y: auto;
|
|
117
126
|
flex-grow: 1;
|
|
118
127
|
}
|
|
@@ -120,38 +129,58 @@
|
|
|
120
129
|
.nav-item {
|
|
121
130
|
display: flex;
|
|
122
131
|
align-items: center;
|
|
123
|
-
|
|
132
|
+
justify-content: space-between;
|
|
124
133
|
padding: 10px 16px;
|
|
125
134
|
border-radius: 8px;
|
|
126
135
|
cursor: pointer;
|
|
127
136
|
color: var(--text-muted);
|
|
128
137
|
font-weight: 500;
|
|
129
|
-
font-size: 0.
|
|
130
|
-
transition: all 0.2s
|
|
138
|
+
font-size: 0.92rem;
|
|
139
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
131
140
|
text-decoration: none;
|
|
132
141
|
}
|
|
133
142
|
|
|
134
143
|
.nav-item:hover {
|
|
135
144
|
color: var(--text-main);
|
|
136
|
-
background-color: rgba(255, 255, 255, 0.
|
|
145
|
+
background-color: rgba(255, 255, 255, 0.04);
|
|
146
|
+
transform: translateX(4px);
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
.nav-item.active {
|
|
140
150
|
color: #fff;
|
|
141
|
-
background
|
|
151
|
+
background: linear-gradient(90deg, rgba(99, 102, 241, 0.15) 0%, rgba(99, 102, 241, 0.02) 100%);
|
|
142
152
|
border-left: 3px solid var(--primary);
|
|
153
|
+
box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.05);
|
|
143
154
|
}
|
|
144
155
|
|
|
145
|
-
.nav-
|
|
146
|
-
width: 18px;
|
|
147
|
-
height: 18px;
|
|
156
|
+
.nav-item-left {
|
|
148
157
|
display: flex;
|
|
149
158
|
align-items: center;
|
|
150
|
-
|
|
159
|
+
gap: 12px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.nav-icon {
|
|
163
|
+
font-size: 1.1rem;
|
|
164
|
+
width: 20px;
|
|
165
|
+
text-align: center;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.nav-count {
|
|
169
|
+
background-color: rgba(255, 255, 255, 0.07);
|
|
170
|
+
color: var(--text-muted);
|
|
171
|
+
font-size: 0.75rem;
|
|
172
|
+
padding: 2px 6px;
|
|
173
|
+
border-radius: 20px;
|
|
174
|
+
font-weight: 600;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.nav-item.active .nav-count {
|
|
178
|
+
background-color: var(--primary);
|
|
179
|
+
color: #fff;
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
.sidebar-footer {
|
|
154
|
-
padding: 16px;
|
|
183
|
+
padding: 16px 20px;
|
|
155
184
|
border-top: 1px solid var(--border-color);
|
|
156
185
|
display: flex;
|
|
157
186
|
align-items: center;
|
|
@@ -165,27 +194,30 @@
|
|
|
165
194
|
border: none;
|
|
166
195
|
color: var(--accent-red);
|
|
167
196
|
cursor: pointer;
|
|
168
|
-
font-weight:
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
font-family: var(--font-sans);
|
|
199
|
+
transition: opacity 0.2s;
|
|
169
200
|
}
|
|
170
201
|
|
|
171
202
|
.logout-btn:hover {
|
|
203
|
+
opacity: 0.8;
|
|
172
204
|
text-decoration: underline;
|
|
173
205
|
}
|
|
174
206
|
|
|
175
|
-
/* Main
|
|
207
|
+
/* Main Container */
|
|
176
208
|
.main-container {
|
|
177
209
|
flex-grow: 1;
|
|
178
210
|
display: flex;
|
|
179
211
|
flex-direction: column;
|
|
180
212
|
overflow: hidden;
|
|
181
|
-
background: radial-gradient(circle at top right, rgba(99, 102, 241, 0.
|
|
213
|
+
background: radial-gradient(circle at top right, rgba(99, 102, 241, 0.03), transparent 700px);
|
|
182
214
|
}
|
|
183
215
|
|
|
184
216
|
/* Header */
|
|
185
217
|
.header {
|
|
186
|
-
height:
|
|
218
|
+
height: 72px;
|
|
187
219
|
border-bottom: 1px solid var(--border-color);
|
|
188
|
-
padding: 0
|
|
220
|
+
padding: 0 28px;
|
|
189
221
|
display: flex;
|
|
190
222
|
align-items: center;
|
|
191
223
|
justify-content: space-between;
|
|
@@ -195,12 +227,13 @@
|
|
|
195
227
|
.header-left {
|
|
196
228
|
display: flex;
|
|
197
229
|
align-items: center;
|
|
198
|
-
gap:
|
|
230
|
+
gap: 20px;
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
.view-title {
|
|
202
|
-
font-size: 1.
|
|
203
|
-
font-weight:
|
|
234
|
+
font-size: 1.35rem;
|
|
235
|
+
font-weight: 700;
|
|
236
|
+
letter-spacing: -0.01em;
|
|
204
237
|
}
|
|
205
238
|
|
|
206
239
|
.header-right {
|
|
@@ -214,16 +247,17 @@
|
|
|
214
247
|
border: 1px solid var(--border-color);
|
|
215
248
|
border-radius: 8px;
|
|
216
249
|
color: var(--text-main);
|
|
217
|
-
padding:
|
|
250
|
+
padding: 9px 16px;
|
|
218
251
|
font-size: 0.875rem;
|
|
252
|
+
font-family: var(--font-sans);
|
|
219
253
|
outline: none;
|
|
220
|
-
width:
|
|
254
|
+
width: 260px;
|
|
221
255
|
transition: all 0.2s ease;
|
|
222
256
|
}
|
|
223
257
|
|
|
224
258
|
.search-input:focus {
|
|
225
|
-
border-color: var(--
|
|
226
|
-
box-shadow: 0 0
|
|
259
|
+
border-color: var(--border-focus);
|
|
260
|
+
box-shadow: 0 0 12px var(--primary-glow);
|
|
227
261
|
}
|
|
228
262
|
|
|
229
263
|
.btn {
|
|
@@ -231,9 +265,10 @@
|
|
|
231
265
|
color: #fff;
|
|
232
266
|
border: none;
|
|
233
267
|
border-radius: 8px;
|
|
234
|
-
padding:
|
|
268
|
+
padding: 9px 18px;
|
|
235
269
|
font-size: 0.875rem;
|
|
236
|
-
font-weight:
|
|
270
|
+
font-weight: 600;
|
|
271
|
+
font-family: var(--font-sans);
|
|
237
272
|
cursor: pointer;
|
|
238
273
|
transition: all 0.2s ease;
|
|
239
274
|
display: flex;
|
|
@@ -243,12 +278,17 @@
|
|
|
243
278
|
|
|
244
279
|
.btn:hover {
|
|
245
280
|
background-color: var(--primary-hover);
|
|
281
|
+
transform: translateY(-1px);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.btn:active {
|
|
285
|
+
transform: translateY(0);
|
|
246
286
|
}
|
|
247
287
|
|
|
248
288
|
.btn-danger {
|
|
249
|
-
background-color: rgba(
|
|
289
|
+
background-color: rgba(244, 63, 94, 0.1);
|
|
250
290
|
color: var(--accent-red);
|
|
251
|
-
border: 1px solid rgba(
|
|
291
|
+
border: 1px solid rgba(244, 63, 94, 0.2);
|
|
252
292
|
}
|
|
253
293
|
|
|
254
294
|
.btn-danger:hover {
|
|
@@ -263,13 +303,14 @@
|
|
|
263
303
|
font-size: 0.85rem;
|
|
264
304
|
color: var(--text-muted);
|
|
265
305
|
cursor: pointer;
|
|
306
|
+
user-select: none;
|
|
266
307
|
}
|
|
267
308
|
|
|
268
309
|
.switch {
|
|
269
310
|
position: relative;
|
|
270
311
|
display: inline-block;
|
|
271
|
-
width:
|
|
272
|
-
height:
|
|
312
|
+
width: 38px;
|
|
313
|
+
height: 22px;
|
|
273
314
|
}
|
|
274
315
|
|
|
275
316
|
.switch input {
|
|
@@ -286,7 +327,7 @@
|
|
|
286
327
|
right: 0;
|
|
287
328
|
bottom: 0;
|
|
288
329
|
background-color: var(--bg-input);
|
|
289
|
-
transition: .
|
|
330
|
+
transition: .2s;
|
|
290
331
|
border-radius: 34px;
|
|
291
332
|
border: 1px solid var(--border-color);
|
|
292
333
|
}
|
|
@@ -294,17 +335,18 @@
|
|
|
294
335
|
.slider:before {
|
|
295
336
|
position: absolute;
|
|
296
337
|
content: "";
|
|
297
|
-
height:
|
|
298
|
-
width:
|
|
338
|
+
height: 14px;
|
|
339
|
+
width: 14px;
|
|
299
340
|
left: 3px;
|
|
300
341
|
bottom: 3px;
|
|
301
342
|
background-color: var(--text-muted);
|
|
302
|
-
transition: .
|
|
343
|
+
transition: .2s;
|
|
303
344
|
border-radius: 50%;
|
|
304
345
|
}
|
|
305
346
|
|
|
306
347
|
input:checked + .slider {
|
|
307
348
|
background-color: var(--primary);
|
|
349
|
+
border-color: transparent;
|
|
308
350
|
}
|
|
309
351
|
|
|
310
352
|
input:checked + .slider:before {
|
|
@@ -316,19 +358,70 @@
|
|
|
316
358
|
.content-area {
|
|
317
359
|
flex-grow: 1;
|
|
318
360
|
overflow-y: auto;
|
|
319
|
-
padding:
|
|
361
|
+
padding: 28px;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-direction: column;
|
|
364
|
+
gap: 24px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Top Stats Grid */
|
|
368
|
+
.stats-grid {
|
|
369
|
+
display: grid;
|
|
370
|
+
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
|
371
|
+
gap: 16px;
|
|
372
|
+
margin-bottom: 4px;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.stat-card {
|
|
376
|
+
background: var(--bg-card);
|
|
377
|
+
border: 1px solid var(--border-color);
|
|
378
|
+
border-radius: 12px;
|
|
379
|
+
padding: 16px;
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
gap: 6px;
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.stat-card:hover {
|
|
388
|
+
background: var(--bg-card-hover);
|
|
389
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
390
|
+
transform: translateY(-2px);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.stat-card.active {
|
|
394
|
+
border-color: var(--primary);
|
|
395
|
+
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08) 0%, rgba(16, 22, 42, 0.8) 100%);
|
|
396
|
+
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.05);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.stat-header {
|
|
400
|
+
display: flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
justify-content: space-between;
|
|
403
|
+
color: var(--text-muted);
|
|
404
|
+
font-size: 0.8rem;
|
|
405
|
+
font-weight: 600;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.stat-value {
|
|
409
|
+
font-size: 1.6rem;
|
|
410
|
+
font-weight: 700;
|
|
411
|
+
color: #fff;
|
|
320
412
|
}
|
|
321
413
|
|
|
322
|
-
/*
|
|
414
|
+
/* Card & Table View */
|
|
323
415
|
.card {
|
|
324
|
-
background
|
|
416
|
+
background: var(--bg-card);
|
|
325
417
|
backdrop-filter: blur(10px);
|
|
418
|
+
-webkit-backdrop-filter: blur(10px);
|
|
326
419
|
border: 1px solid var(--border-color);
|
|
327
|
-
border-radius:
|
|
420
|
+
border-radius: 14px;
|
|
328
421
|
overflow: hidden;
|
|
422
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
329
423
|
}
|
|
330
424
|
|
|
331
|
-
/* List Table */
|
|
332
425
|
.table-container {
|
|
333
426
|
overflow-x: auto;
|
|
334
427
|
}
|
|
@@ -340,21 +433,22 @@
|
|
|
340
433
|
}
|
|
341
434
|
|
|
342
435
|
th {
|
|
343
|
-
padding:
|
|
344
|
-
font-size: 0.
|
|
436
|
+
padding: 16px 24px;
|
|
437
|
+
font-size: 0.78rem;
|
|
345
438
|
text-transform: uppercase;
|
|
346
|
-
letter-spacing: 0.
|
|
439
|
+
letter-spacing: 0.06em;
|
|
347
440
|
color: var(--text-muted);
|
|
348
441
|
border-bottom: 1px solid var(--border-color);
|
|
349
|
-
font-weight:
|
|
442
|
+
font-weight: 700;
|
|
443
|
+
background-color: rgba(10, 15, 30, 0.3);
|
|
350
444
|
}
|
|
351
445
|
|
|
352
446
|
td {
|
|
353
|
-
padding:
|
|
354
|
-
font-size: 0.
|
|
447
|
+
padding: 16px 24px;
|
|
448
|
+
font-size: 0.9rem;
|
|
355
449
|
border-bottom: 1px solid var(--border-color);
|
|
356
450
|
color: var(--text-main);
|
|
357
|
-
max-width:
|
|
451
|
+
max-width: 450px;
|
|
358
452
|
white-space: nowrap;
|
|
359
453
|
overflow: hidden;
|
|
360
454
|
text-overflow: ellipsis;
|
|
@@ -366,42 +460,59 @@
|
|
|
366
460
|
|
|
367
461
|
tr {
|
|
368
462
|
cursor: pointer;
|
|
369
|
-
transition: background-color 0.
|
|
463
|
+
transition: background-color 0.15s ease;
|
|
370
464
|
}
|
|
371
465
|
|
|
372
466
|
tr:hover {
|
|
373
|
-
background-color: rgba(255, 255, 255, 0.
|
|
467
|
+
background-color: rgba(255, 255, 255, 0.015);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
tr.selected {
|
|
471
|
+
background-color: rgba(99, 102, 241, 0.07);
|
|
374
472
|
}
|
|
375
473
|
|
|
376
|
-
/*
|
|
474
|
+
/* Badges */
|
|
377
475
|
.badge {
|
|
378
476
|
display: inline-flex;
|
|
379
477
|
align-items: center;
|
|
380
478
|
padding: 4px 8px;
|
|
381
479
|
border-radius: 6px;
|
|
382
|
-
font-size: 0.
|
|
383
|
-
font-weight:
|
|
384
|
-
letter-spacing: 0.
|
|
480
|
+
font-size: 0.72rem;
|
|
481
|
+
font-weight: 700;
|
|
482
|
+
letter-spacing: 0.03em;
|
|
385
483
|
}
|
|
386
484
|
|
|
387
485
|
.badge-method {
|
|
388
486
|
text-transform: uppercase;
|
|
389
487
|
}
|
|
488
|
+
|
|
489
|
+
.badge-request { background-color: rgba(59, 130, 246, 0.12); color: var(--accent-blue); }
|
|
490
|
+
.badge-query { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
491
|
+
.badge-cache { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
492
|
+
.badge-job { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
493
|
+
.badge-event { background-color: rgba(59, 130, 246, 0.12); color: var(--accent-blue); }
|
|
494
|
+
.badge-mail { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
495
|
+
.badge-log { background-color: rgba(148, 163, 184, 0.15); color: var(--text-main); }
|
|
496
|
+
.badge-exception { background-color: rgba(244, 63, 94, 0.12); color: var(--accent-red); }
|
|
497
|
+
.badge-scheduled_task { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
498
|
+
.badge-redis { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
499
|
+
|
|
390
500
|
.badge-get { background-color: rgba(16, 185, 129, 0.15); color: var(--accent-green); }
|
|
391
501
|
.badge-post { background-color: rgba(59, 130, 246, 0.15); color: var(--accent-blue); }
|
|
392
502
|
.badge-put { background-color: rgba(245, 158, 11, 0.15); color: var(--accent-amber); }
|
|
393
|
-
.badge-delete { background-color: rgba(
|
|
503
|
+
.badge-delete { background-color: rgba(244, 63, 94, 0.15); color: var(--accent-red); }
|
|
504
|
+
|
|
394
505
|
.badge-status-ok { background-color: rgba(16, 185, 129, 0.15); color: var(--accent-green); }
|
|
395
|
-
.badge-status-err { background-color: rgba(
|
|
506
|
+
.badge-status-err { background-color: rgba(244, 63, 94, 0.15); color: var(--accent-red); }
|
|
396
507
|
|
|
397
|
-
/* Login
|
|
508
|
+
/* Animated Login Screen */
|
|
398
509
|
.login-overlay {
|
|
399
510
|
position: fixed;
|
|
400
511
|
top: 0;
|
|
401
512
|
left: 0;
|
|
402
513
|
right: 0;
|
|
403
514
|
bottom: 0;
|
|
404
|
-
background
|
|
515
|
+
background: radial-gradient(circle at center, #0c122c 0%, #050714 100%);
|
|
405
516
|
display: flex;
|
|
406
517
|
align-items: center;
|
|
407
518
|
justify-content: center;
|
|
@@ -409,16 +520,24 @@
|
|
|
409
520
|
}
|
|
410
521
|
|
|
411
522
|
.login-card {
|
|
412
|
-
width:
|
|
413
|
-
background
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
523
|
+
width: 400px;
|
|
524
|
+
background: rgba(13, 20, 43, 0.7);
|
|
525
|
+
backdrop-filter: blur(20px);
|
|
526
|
+
-webkit-backdrop-filter: blur(20px);
|
|
527
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
528
|
+
border-radius: 20px;
|
|
529
|
+
padding: 40px;
|
|
530
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 40px rgba(99, 102, 241, 0.1);
|
|
418
531
|
display: flex;
|
|
419
532
|
flex-direction: column;
|
|
420
|
-
gap:
|
|
533
|
+
gap: 28px;
|
|
421
534
|
text-align: center;
|
|
535
|
+
animation: loginFloat 6s ease-in-out infinite;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@keyframes loginFloat {
|
|
539
|
+
0%, 100% { transform: translateY(0); }
|
|
540
|
+
50% { transform: translateY(-8px); }
|
|
422
541
|
}
|
|
423
542
|
|
|
424
543
|
.login-logo-wrapper {
|
|
@@ -426,13 +545,28 @@
|
|
|
426
545
|
justify-content: center;
|
|
427
546
|
}
|
|
428
547
|
|
|
548
|
+
.login-logo {
|
|
549
|
+
background: linear-gradient(135deg, var(--primary), #8b5cf6);
|
|
550
|
+
width: 60px;
|
|
551
|
+
height: 60px;
|
|
552
|
+
border-radius: 16px;
|
|
553
|
+
display: flex;
|
|
554
|
+
align-items: center;
|
|
555
|
+
justify-content: center;
|
|
556
|
+
font-weight: 800;
|
|
557
|
+
color: #fff;
|
|
558
|
+
font-size: 2rem;
|
|
559
|
+
box-shadow: 0 0 30px rgba(99, 102, 241, 0.4);
|
|
560
|
+
}
|
|
561
|
+
|
|
429
562
|
.login-input {
|
|
430
|
-
background-color: var(--bg-
|
|
563
|
+
background-color: var(--bg-input);
|
|
431
564
|
border: 1px solid var(--border-color);
|
|
432
565
|
border-radius: 8px;
|
|
433
566
|
color: var(--text-main);
|
|
434
|
-
padding:
|
|
567
|
+
padding: 14px;
|
|
435
568
|
font-size: 1rem;
|
|
569
|
+
font-family: var(--font-sans);
|
|
436
570
|
outline: none;
|
|
437
571
|
width: 100%;
|
|
438
572
|
text-align: center;
|
|
@@ -440,60 +574,65 @@
|
|
|
440
574
|
}
|
|
441
575
|
|
|
442
576
|
.login-input:focus {
|
|
443
|
-
border-color: var(--
|
|
577
|
+
border-color: var(--border-focus);
|
|
578
|
+
box-shadow: 0 0 12px var(--primary-glow);
|
|
444
579
|
}
|
|
445
580
|
|
|
446
581
|
.error-msg {
|
|
447
582
|
color: var(--accent-red);
|
|
448
583
|
font-size: 0.85rem;
|
|
584
|
+
font-weight: 500;
|
|
449
585
|
}
|
|
450
586
|
|
|
451
|
-
/*
|
|
587
|
+
/* Modal Styles with Blur & Entrance animation */
|
|
452
588
|
.modal-overlay {
|
|
453
589
|
position: fixed;
|
|
454
590
|
top: 0;
|
|
455
591
|
left: 0;
|
|
456
592
|
right: 0;
|
|
457
593
|
bottom: 0;
|
|
458
|
-
background-color: rgba(
|
|
459
|
-
backdrop-filter: blur(
|
|
594
|
+
background-color: rgba(2, 4, 12, 0.75);
|
|
595
|
+
backdrop-filter: blur(8px);
|
|
596
|
+
-webkit-backdrop-filter: blur(8px);
|
|
460
597
|
display: flex;
|
|
461
598
|
align-items: center;
|
|
462
599
|
justify-content: center;
|
|
463
600
|
z-index: 999;
|
|
464
|
-
padding:
|
|
601
|
+
padding: 30px;
|
|
465
602
|
}
|
|
466
603
|
|
|
467
604
|
.modal-card {
|
|
468
605
|
width: 100%;
|
|
469
|
-
max-width:
|
|
470
|
-
height:
|
|
471
|
-
background-color:
|
|
606
|
+
max-width: 950px;
|
|
607
|
+
height: 85%;
|
|
608
|
+
background-color: #0b1226;
|
|
472
609
|
border: 1px solid var(--border-color);
|
|
473
|
-
border-radius:
|
|
610
|
+
border-radius: 18px;
|
|
474
611
|
display: flex;
|
|
475
612
|
flex-direction: column;
|
|
476
613
|
overflow: hidden;
|
|
477
|
-
animation:
|
|
614
|
+
animation: modalEntrance 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
615
|
+
box-shadow: 0 25px 60px rgba(0,0,0,0.4), 0 0 2px rgba(255,255,255,0.1);
|
|
478
616
|
}
|
|
479
617
|
|
|
480
|
-
@keyframes
|
|
481
|
-
from { transform: scale(0.
|
|
482
|
-
to { transform: scale(1); opacity: 1; }
|
|
618
|
+
@keyframes modalEntrance {
|
|
619
|
+
from { transform: scale(0.96) translateY(10px); opacity: 0; }
|
|
620
|
+
to { transform: scale(1) translateY(0); opacity: 1; }
|
|
483
621
|
}
|
|
484
622
|
|
|
485
623
|
.modal-header {
|
|
486
|
-
padding:
|
|
624
|
+
padding: 22px 28px;
|
|
487
625
|
border-bottom: 1px solid var(--border-color);
|
|
488
626
|
display: flex;
|
|
489
627
|
align-items: center;
|
|
490
628
|
justify-content: space-between;
|
|
629
|
+
background-color: rgba(0, 0, 0, 0.15);
|
|
491
630
|
}
|
|
492
631
|
|
|
493
632
|
.modal-title-group {
|
|
494
633
|
display: flex;
|
|
495
634
|
align-items: center;
|
|
496
|
-
gap:
|
|
635
|
+
gap: 16px;
|
|
497
636
|
}
|
|
498
637
|
|
|
499
638
|
.modal-close {
|
|
@@ -501,12 +640,12 @@
|
|
|
501
640
|
border: none;
|
|
502
641
|
color: var(--text-muted);
|
|
503
642
|
cursor: pointer;
|
|
504
|
-
font-size: 1.
|
|
643
|
+
font-size: 1.8rem;
|
|
505
644
|
display: flex;
|
|
506
645
|
align-items: center;
|
|
507
646
|
justify-content: center;
|
|
508
|
-
width:
|
|
509
|
-
height:
|
|
647
|
+
width: 36px;
|
|
648
|
+
height: 36px;
|
|
510
649
|
border-radius: 50%;
|
|
511
650
|
transition: all 0.2s ease;
|
|
512
651
|
}
|
|
@@ -514,23 +653,24 @@
|
|
|
514
653
|
.modal-close:hover {
|
|
515
654
|
background-color: rgba(255, 255, 255, 0.05);
|
|
516
655
|
color: #fff;
|
|
656
|
+
transform: rotate(90deg);
|
|
517
657
|
}
|
|
518
658
|
|
|
519
659
|
.modal-tabs {
|
|
520
660
|
display: flex;
|
|
521
|
-
background-color: rgba(0, 0, 0, 0.
|
|
661
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
522
662
|
border-bottom: 1px solid var(--border-color);
|
|
523
|
-
padding: 0
|
|
663
|
+
padding: 0 28px;
|
|
524
664
|
}
|
|
525
665
|
|
|
526
666
|
.modal-tab {
|
|
527
|
-
padding: 14px
|
|
667
|
+
padding: 14px 22px;
|
|
528
668
|
cursor: pointer;
|
|
529
669
|
color: var(--text-muted);
|
|
530
|
-
font-weight:
|
|
670
|
+
font-weight: 600;
|
|
531
671
|
font-size: 0.9rem;
|
|
532
672
|
border-bottom: 2px solid transparent;
|
|
533
|
-
transition: all 0.2s
|
|
673
|
+
transition: all 0.2s;
|
|
534
674
|
}
|
|
535
675
|
|
|
536
676
|
.modal-tab:hover {
|
|
@@ -545,35 +685,36 @@
|
|
|
545
685
|
.modal-body {
|
|
546
686
|
flex-grow: 1;
|
|
547
687
|
overflow-y: auto;
|
|
548
|
-
padding:
|
|
688
|
+
padding: 28px;
|
|
689
|
+
background-color: rgba(10, 15, 30, 0.2);
|
|
549
690
|
}
|
|
550
691
|
|
|
551
|
-
/* JSON
|
|
692
|
+
/* JSON Display */
|
|
552
693
|
.json-code {
|
|
553
694
|
font-family: var(--font-mono);
|
|
554
|
-
font-size: 0.
|
|
555
|
-
background-color: rgba(
|
|
556
|
-
padding:
|
|
557
|
-
border-radius:
|
|
695
|
+
font-size: 0.825rem;
|
|
696
|
+
background-color: rgba(2, 4, 10, 0.6);
|
|
697
|
+
padding: 20px;
|
|
698
|
+
border-radius: 10px;
|
|
558
699
|
border: 1px solid var(--border-color);
|
|
559
700
|
white-space: pre-wrap;
|
|
560
701
|
word-break: break-all;
|
|
561
|
-
color: #
|
|
702
|
+
color: #e2e8f0;
|
|
562
703
|
overflow-x: auto;
|
|
563
704
|
}
|
|
564
705
|
|
|
565
706
|
.key-val-list {
|
|
566
707
|
display: flex;
|
|
567
708
|
flex-direction: column;
|
|
568
|
-
gap:
|
|
709
|
+
gap: 16px;
|
|
569
710
|
}
|
|
570
711
|
|
|
571
712
|
.key-val-row {
|
|
572
713
|
display: grid;
|
|
573
|
-
grid-template-columns:
|
|
574
|
-
gap:
|
|
714
|
+
grid-template-columns: 220px 1fr;
|
|
715
|
+
gap: 20px;
|
|
575
716
|
border-bottom: 1px solid var(--border-color);
|
|
576
|
-
padding-bottom:
|
|
717
|
+
padding-bottom: 14px;
|
|
577
718
|
}
|
|
578
719
|
|
|
579
720
|
.key-val-row:last-child {
|
|
@@ -583,22 +724,86 @@
|
|
|
583
724
|
.key-val-label {
|
|
584
725
|
font-weight: 600;
|
|
585
726
|
color: var(--text-muted);
|
|
586
|
-
font-size: 0.
|
|
727
|
+
font-size: 0.9rem;
|
|
587
728
|
}
|
|
588
729
|
|
|
589
730
|
.key-val-value {
|
|
590
|
-
font-size: 0.
|
|
731
|
+
font-size: 0.9rem;
|
|
591
732
|
word-break: break-all;
|
|
592
733
|
}
|
|
593
734
|
|
|
594
|
-
/*
|
|
735
|
+
/* Pagination controls */
|
|
736
|
+
.pagination-bar {
|
|
737
|
+
display: flex;
|
|
738
|
+
align-items: center;
|
|
739
|
+
justify-content: space-between;
|
|
740
|
+
padding: 14px 24px;
|
|
741
|
+
border-top: 1px solid var(--border-color);
|
|
742
|
+
background-color: rgba(10, 15, 30, 0.15);
|
|
743
|
+
font-size: 0.85rem;
|
|
744
|
+
color: var(--text-muted);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.pagination-buttons {
|
|
748
|
+
display: flex;
|
|
749
|
+
gap: 8px;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.pagination-btn {
|
|
753
|
+
background-color: var(--bg-input);
|
|
754
|
+
border: 1px solid var(--border-color);
|
|
755
|
+
color: var(--text-main);
|
|
756
|
+
padding: 6px 12px;
|
|
757
|
+
border-radius: 6px;
|
|
758
|
+
cursor: pointer;
|
|
759
|
+
font-family: var(--font-sans);
|
|
760
|
+
font-weight: 500;
|
|
761
|
+
transition: all 0.2s;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.pagination-btn:hover:not(:disabled) {
|
|
765
|
+
background-color: var(--primary);
|
|
766
|
+
border-color: transparent;
|
|
767
|
+
color: #fff;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.pagination-btn:disabled {
|
|
771
|
+
opacity: 0.4;
|
|
772
|
+
cursor: not-allowed;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.pagination-info {
|
|
776
|
+
font-weight: 500;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/* Keyboard Shortcuts Indicator */
|
|
780
|
+
.shortcuts-help {
|
|
781
|
+
display: flex;
|
|
782
|
+
gap: 12px;
|
|
783
|
+
font-size: 0.75rem;
|
|
784
|
+
color: var(--text-muted);
|
|
785
|
+
align-items: center;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.key-cap {
|
|
789
|
+
background: var(--bg-input);
|
|
790
|
+
border: 1px solid var(--border-color);
|
|
791
|
+
border-radius: 4px;
|
|
792
|
+
padding: 1px 5px;
|
|
793
|
+
font-family: var(--font-mono);
|
|
794
|
+
font-weight: 600;
|
|
795
|
+
box-shadow: 0 1px 0 rgba(255,255,255,0.1);
|
|
796
|
+
color: #fff;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/* Spinner */
|
|
595
800
|
.spinner {
|
|
596
|
-
border: 3px solid rgba(255, 255, 255, 0.
|
|
801
|
+
border: 3px solid rgba(255, 255, 255, 0.05);
|
|
597
802
|
border-top: 3px solid var(--primary);
|
|
598
803
|
border-radius: 50%;
|
|
599
|
-
width:
|
|
600
|
-
height:
|
|
601
|
-
animation: spin
|
|
804
|
+
width: 28px;
|
|
805
|
+
height: 28px;
|
|
806
|
+
animation: spin 0.8s linear infinite;
|
|
602
807
|
}
|
|
603
808
|
|
|
604
809
|
@keyframes spin {
|
|
@@ -610,19 +815,19 @@
|
|
|
610
815
|
display: flex;
|
|
611
816
|
align-items: center;
|
|
612
817
|
justify-content: center;
|
|
613
|
-
height:
|
|
818
|
+
height: 250px;
|
|
614
819
|
}
|
|
615
820
|
|
|
616
821
|
.empty-state {
|
|
617
822
|
text-align: center;
|
|
618
|
-
padding:
|
|
823
|
+
padding: 64px 32px;
|
|
619
824
|
color: var(--text-muted);
|
|
620
825
|
}
|
|
621
826
|
|
|
622
827
|
.empty-icon {
|
|
623
|
-
font-size:
|
|
624
|
-
margin-bottom:
|
|
625
|
-
opacity: 0.
|
|
828
|
+
font-size: 3rem;
|
|
829
|
+
margin-bottom: 16px;
|
|
830
|
+
opacity: 0.6;
|
|
626
831
|
}
|
|
627
832
|
</style>
|
|
628
833
|
</head>
|
|
@@ -657,36 +862,68 @@
|
|
|
657
862
|
const [loginError, setLoginError] = useState('');
|
|
658
863
|
const [activeTab, setActiveTab] = useState('all');
|
|
659
864
|
const [entries, setEntries] = useState([]);
|
|
865
|
+
const [stats, setStats] = useState({});
|
|
660
866
|
const [searchTerm, setSearchTerm] = useState('');
|
|
661
867
|
const [loading, setLoading] = useState(false);
|
|
662
868
|
const [polling, setPolling] = useState(true);
|
|
663
869
|
const [selectedEntry, setSelectedEntry] = useState(null);
|
|
664
870
|
const [modalTab, setModalTab] = useState('details');
|
|
665
871
|
|
|
872
|
+
// Pagination state
|
|
873
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
874
|
+
const [totalPages, setTotalPages] = useState(1);
|
|
875
|
+
const [totalEntries, setTotalEntries] = useState(0);
|
|
876
|
+
const [perPage, setPerPage] = useState(50);
|
|
877
|
+
|
|
666
878
|
const prefixPath = window.location.pathname.split('/api')[0].split('/')[1] || 'telescope';
|
|
667
879
|
|
|
880
|
+
// Load entries when dependencies change
|
|
668
881
|
useEffect(() => {
|
|
669
882
|
if (token) {
|
|
670
|
-
fetchEntries();
|
|
883
|
+
fetchEntries(1, false); // Reset page to 1 on tab or search change
|
|
671
884
|
}
|
|
672
885
|
}, [token, activeTab, searchTerm]);
|
|
673
886
|
|
|
887
|
+
// Polling effect
|
|
674
888
|
useEffect(() => {
|
|
675
889
|
let interval;
|
|
676
890
|
if (polling && token) {
|
|
677
891
|
interval = setInterval(() => {
|
|
678
|
-
fetchEntries(true);
|
|
892
|
+
fetchEntries(currentPage, true);
|
|
893
|
+
fetchStats();
|
|
679
894
|
}, 3000);
|
|
680
895
|
}
|
|
681
896
|
return () => clearInterval(interval);
|
|
682
|
-
}, [polling, token, activeTab, searchTerm]);
|
|
897
|
+
}, [polling, token, activeTab, searchTerm, currentPage]);
|
|
898
|
+
|
|
899
|
+
// Fetch stats once on boot, and then periodically
|
|
900
|
+
useEffect(() => {
|
|
901
|
+
if (token) {
|
|
902
|
+
fetchStats();
|
|
903
|
+
}
|
|
904
|
+
}, [token]);
|
|
905
|
+
|
|
906
|
+
const fetchStats = async () => {
|
|
907
|
+
try {
|
|
908
|
+
const res = await fetch(`/${prefixPath}/api/stats`, {
|
|
909
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
910
|
+
});
|
|
911
|
+
if (res.ok) {
|
|
912
|
+
const data = await res.json();
|
|
913
|
+
setStats(data);
|
|
914
|
+
}
|
|
915
|
+
} catch (e) {
|
|
916
|
+
console.error('Failed to fetch stats', e);
|
|
917
|
+
}
|
|
918
|
+
};
|
|
683
919
|
|
|
684
|
-
const fetchEntries = async (silent = false) => {
|
|
920
|
+
const fetchEntries = async (page = 1, silent = false) => {
|
|
685
921
|
if (!silent) setLoading(true);
|
|
686
922
|
try {
|
|
687
923
|
const typeQuery = activeTab !== 'all' ? `type=${activeTab}` : '';
|
|
688
924
|
const searchQuery = searchTerm ? `search=${encodeURIComponent(searchTerm)}` : '';
|
|
689
|
-
const
|
|
925
|
+
const paginationQuery = `page=${page}&perPage=${perPage}`;
|
|
926
|
+
const query = [typeQuery, searchQuery, paginationQuery].filter(Boolean).join('&');
|
|
690
927
|
|
|
691
928
|
const res = await fetch(`/${prefixPath}/api/entries?${query}`, {
|
|
692
929
|
headers: {
|
|
@@ -697,8 +934,18 @@
|
|
|
697
934
|
handleLogout();
|
|
698
935
|
return;
|
|
699
936
|
}
|
|
700
|
-
const
|
|
701
|
-
|
|
937
|
+
const result = await res.json();
|
|
938
|
+
if (result && typeof result === 'object' && Array.isArray(result.data)) {
|
|
939
|
+
setEntries(result.data);
|
|
940
|
+
setTotalEntries(result.total);
|
|
941
|
+
setTotalPages(result.totalPages);
|
|
942
|
+
setCurrentPage(result.page);
|
|
943
|
+
} else if (Array.isArray(result)) {
|
|
944
|
+
setEntries(result);
|
|
945
|
+
setTotalEntries(result.length);
|
|
946
|
+
setTotalPages(1);
|
|
947
|
+
setCurrentPage(1);
|
|
948
|
+
}
|
|
702
949
|
} catch (e) {
|
|
703
950
|
console.error(e);
|
|
704
951
|
} finally {
|
|
@@ -741,6 +988,7 @@
|
|
|
741
988
|
}
|
|
742
989
|
});
|
|
743
990
|
setEntries([]);
|
|
991
|
+
setStats({});
|
|
744
992
|
setSelectedEntry(null);
|
|
745
993
|
} catch (e) {
|
|
746
994
|
console.error(e);
|
|
@@ -764,17 +1012,49 @@
|
|
|
764
1012
|
return date.toLocaleDateString();
|
|
765
1013
|
};
|
|
766
1014
|
|
|
1015
|
+
// Keyboard navigation handler
|
|
1016
|
+
useEffect(() => {
|
|
1017
|
+
const handleKeyDown = (e) => {
|
|
1018
|
+
if (!selectedEntry) return;
|
|
1019
|
+
if (e.key === 'Escape') {
|
|
1020
|
+
setSelectedEntry(null);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const currentIndex = entries.findIndex(item => item.uuid === selectedEntry.uuid);
|
|
1025
|
+
if (currentIndex === -1) return;
|
|
1026
|
+
|
|
1027
|
+
if (e.key === 'ArrowDown') {
|
|
1028
|
+
e.preventDefault();
|
|
1029
|
+
const nextIndex = currentIndex + 1;
|
|
1030
|
+
if (nextIndex < entries.length) {
|
|
1031
|
+
setSelectedEntry(entries[nextIndex]);
|
|
1032
|
+
}
|
|
1033
|
+
} else if (e.key === 'ArrowUp') {
|
|
1034
|
+
e.preventDefault();
|
|
1035
|
+
const prevIndex = currentIndex - 1;
|
|
1036
|
+
if (prevIndex >= 0) {
|
|
1037
|
+
setSelectedEntry(entries[prevIndex]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
1043
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
1044
|
+
}, [selectedEntry, entries]);
|
|
1045
|
+
|
|
1046
|
+
// Login screen
|
|
767
1047
|
if (!token) {
|
|
768
1048
|
return (
|
|
769
1049
|
<div className="login-overlay">
|
|
770
1050
|
<form className="login-card" onSubmit={handleLogin}>
|
|
771
1051
|
<div className="login-logo-wrapper">
|
|
772
|
-
<div className="logo">T</div>
|
|
1052
|
+
<div className="login-logo">T</div>
|
|
773
1053
|
</div>
|
|
774
1054
|
<div>
|
|
775
|
-
<h2 style={{ marginBottom: '8px' }}>Telescope
|
|
776
|
-
<p style={{ color: 'var(--text-muted)', fontSize: '0.
|
|
777
|
-
|
|
1055
|
+
<h2 style={{ marginBottom: '8px', fontWeight: '700', letterSpacing: '-0.02em' }}>Telescope Dashboard</h2>
|
|
1056
|
+
<p style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>
|
|
1057
|
+
Provide the password to unlock monitoring dashboard
|
|
778
1058
|
</p>
|
|
779
1059
|
</div>
|
|
780
1060
|
<input
|
|
@@ -787,14 +1067,22 @@
|
|
|
787
1067
|
autoFocus
|
|
788
1068
|
/>
|
|
789
1069
|
{loginError && <div className="error-msg">{loginError}</div>}
|
|
790
|
-
<button type="submit" className="btn" style={{ justifyContent: 'center' }}>
|
|
791
|
-
Access
|
|
1070
|
+
<button type="submit" className="btn" style={{ justifyContent: 'center', padding: '12px' }}>
|
|
1071
|
+
Access Monitor
|
|
792
1072
|
</button>
|
|
793
1073
|
</form>
|
|
794
1074
|
</div>
|
|
795
1075
|
);
|
|
796
1076
|
}
|
|
797
1077
|
|
|
1078
|
+
// Format navigation count
|
|
1079
|
+
const getTabCount = (tabId) => {
|
|
1080
|
+
if (tabId === 'all') {
|
|
1081
|
+
return Object.values(stats).reduce((a, b) => a + b, 0);
|
|
1082
|
+
}
|
|
1083
|
+
return stats[tabId] || 0;
|
|
1084
|
+
};
|
|
1085
|
+
|
|
798
1086
|
return (
|
|
799
1087
|
<React.Fragment>
|
|
800
1088
|
{/* Sidebar */}
|
|
@@ -811,10 +1099,14 @@
|
|
|
811
1099
|
onClick={() => {
|
|
812
1100
|
setActiveTab(tab.id);
|
|
813
1101
|
setSelectedEntry(null);
|
|
1102
|
+
setCurrentPage(1);
|
|
814
1103
|
}}
|
|
815
1104
|
>
|
|
816
|
-
<
|
|
817
|
-
|
|
1105
|
+
<div className="nav-item-left">
|
|
1106
|
+
<span className="nav-icon">{tab.icon}</span>
|
|
1107
|
+
<span>{tab.label}</span>
|
|
1108
|
+
</div>
|
|
1109
|
+
<span className="nav-count">{getTabCount(tab.id)}</span>
|
|
818
1110
|
</li>
|
|
819
1111
|
))}
|
|
820
1112
|
</ul>
|
|
@@ -856,6 +1148,25 @@
|
|
|
856
1148
|
|
|
857
1149
|
{/* Content Area */}
|
|
858
1150
|
<div className="content-area">
|
|
1151
|
+
{/* Stats Grid at the top for quick insights */}
|
|
1152
|
+
{activeTab === 'all' && (
|
|
1153
|
+
<div className="stats-grid">
|
|
1154
|
+
{NAVIGATION_TABS.slice(1, 7).map((tab) => (
|
|
1155
|
+
<div
|
|
1156
|
+
key={tab.id}
|
|
1157
|
+
className={`stat-card ${activeTab === tab.id ? 'active' : ''}`}
|
|
1158
|
+
onClick={() => setActiveTab(tab.id)}
|
|
1159
|
+
>
|
|
1160
|
+
<div className="stat-header">
|
|
1161
|
+
<span>{tab.label}</span>
|
|
1162
|
+
<span>{tab.icon}</span>
|
|
1163
|
+
</div>
|
|
1164
|
+
<div className="stat-value">{stats[tab.id] || 0}</div>
|
|
1165
|
+
</div>
|
|
1166
|
+
))}
|
|
1167
|
+
</div>
|
|
1168
|
+
)}
|
|
1169
|
+
|
|
859
1170
|
{loading ? (
|
|
860
1171
|
<div className="loading-container">
|
|
861
1172
|
<div className="spinner"></div>
|
|
@@ -863,50 +1174,87 @@
|
|
|
863
1174
|
) : entries.length === 0 ? (
|
|
864
1175
|
<div className="card empty-state">
|
|
865
1176
|
<div className="empty-icon">📭</div>
|
|
866
|
-
<h3>No Entries Found</h3>
|
|
867
|
-
<p style={{ marginTop: '8px' }}>
|
|
1177
|
+
<h3 style={{ fontWeight: '600', color: '#fff' }}>No Entries Found</h3>
|
|
1178
|
+
<p style={{ marginTop: '8px', fontSize: '0.9rem' }}>
|
|
868
1179
|
Trigger some actions in your application to see activity recorded here.
|
|
869
1180
|
</p>
|
|
870
1181
|
</div>
|
|
871
1182
|
) : (
|
|
872
|
-
<div className="card
|
|
873
|
-
<table>
|
|
874
|
-
<
|
|
875
|
-
<
|
|
876
|
-
<
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
<
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1183
|
+
<div className="card" style={{ display: 'flex', flexDirection: 'column' }}>
|
|
1184
|
+
<div className="table-container">
|
|
1185
|
+
<table>
|
|
1186
|
+
<thead>
|
|
1187
|
+
<tr>
|
|
1188
|
+
<th style={{ width: '130px' }}>Type</th>
|
|
1189
|
+
<th>Payload Summary</th>
|
|
1190
|
+
<th style={{ width: '110px' }}>Status</th>
|
|
1191
|
+
<th style={{ width: '180px', textAlign: 'right' }}>Happened</th>
|
|
1192
|
+
</tr>
|
|
1193
|
+
</thead>
|
|
1194
|
+
<tbody>
|
|
1195
|
+
{entries.map((entry) => {
|
|
1196
|
+
const summary = getEntrySummary(entry);
|
|
1197
|
+
const isSelected = selectedEntry && selectedEntry.uuid === entry.uuid;
|
|
1198
|
+
return (
|
|
1199
|
+
<tr
|
|
1200
|
+
key={entry.uuid}
|
|
1201
|
+
className={isSelected ? 'selected' : ''}
|
|
1202
|
+
onClick={() => {
|
|
1203
|
+
setSelectedEntry(entry);
|
|
1204
|
+
setModalTab('details');
|
|
1205
|
+
}}
|
|
1206
|
+
>
|
|
1207
|
+
<td>
|
|
1208
|
+
<span className={`badge badge-${entry.type}`}>
|
|
1209
|
+
{entry.type.replace('_', ' ')}
|
|
1210
|
+
</span>
|
|
1211
|
+
</td>
|
|
1212
|
+
<td>
|
|
1213
|
+
<span style={{ fontWeight: '600', color: '#fff' }}>{summary.title}</span>
|
|
1214
|
+
<span style={{ color: 'var(--text-muted)', marginLeft: '10px', fontSize: '0.825rem', fontFamily: varMono(entry.type) }}>
|
|
1215
|
+
{summary.subtitle}
|
|
1216
|
+
</span>
|
|
1217
|
+
</td>
|
|
1218
|
+
<td>{summary.status}</td>
|
|
1219
|
+
<td style={{ textAlign: 'right', color: 'var(--text-muted)', fontSize: '0.85rem' }}>
|
|
1220
|
+
{getRelativeTime(entry.recordedAt)}
|
|
1221
|
+
</td>
|
|
1222
|
+
</tr>
|
|
1223
|
+
);
|
|
1224
|
+
})}
|
|
1225
|
+
</tbody>
|
|
1226
|
+
</table>
|
|
1227
|
+
</div>
|
|
1228
|
+
|
|
1229
|
+
{/* Pagination Footer */}
|
|
1230
|
+
<div className="pagination-bar">
|
|
1231
|
+
<div className="shortcuts-help">
|
|
1232
|
+
<span>Shortcut:</span>
|
|
1233
|
+
<span><span className="key-cap">▲</span> <span className="key-cap">▼</span> Navigate</span>
|
|
1234
|
+
<span><span className="key-cap">Esc</span> Close</span>
|
|
1235
|
+
</div>
|
|
1236
|
+
|
|
1237
|
+
<div className="pagination-info">
|
|
1238
|
+
Showing page {currentPage} of {totalPages} ({totalEntries} total entries)
|
|
1239
|
+
</div>
|
|
1240
|
+
|
|
1241
|
+
<div className="pagination-buttons">
|
|
1242
|
+
<button
|
|
1243
|
+
className="pagination-btn"
|
|
1244
|
+
disabled={currentPage <= 1}
|
|
1245
|
+
onClick={() => fetchEntries(currentPage - 1)}
|
|
1246
|
+
>
|
|
1247
|
+
Previous
|
|
1248
|
+
</button>
|
|
1249
|
+
<button
|
|
1250
|
+
className="pagination-btn"
|
|
1251
|
+
disabled={currentPage >= totalPages}
|
|
1252
|
+
onClick={() => fetchEntries(currentPage + 1)}
|
|
1253
|
+
>
|
|
1254
|
+
Next
|
|
1255
|
+
</button>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
910
1258
|
</div>
|
|
911
1259
|
)}
|
|
912
1260
|
</div>
|
|
@@ -918,11 +1266,11 @@
|
|
|
918
1266
|
<div className="modal-card" onClick={(e) => e.stopPropagation()}>
|
|
919
1267
|
<div className="modal-header">
|
|
920
1268
|
<div className="modal-title-group">
|
|
921
|
-
<span className={`badge badge
|
|
922
|
-
{selectedEntry.type}
|
|
1269
|
+
<span className={`badge badge-${selectedEntry.type}`}>
|
|
1270
|
+
{selectedEntry.type.replace('_', ' ')}
|
|
923
1271
|
</span>
|
|
924
|
-
<h3 style={{ fontSize: '1.
|
|
925
|
-
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem', fontFamily: 'var(--font-mono)' }}>
|
|
1272
|
+
<h3 style={{ fontSize: '1.25rem', fontWeight: '700', color: '#fff' }}>Entry Details</h3>
|
|
1273
|
+
<span style={{ color: 'var(--text-muted)', fontSize: '0.8rem', fontFamily: 'var(--font-mono)', letterSpacing: '0.05em' }}>
|
|
926
1274
|
{selectedEntry.uuid}
|
|
927
1275
|
</span>
|
|
928
1276
|
</div>
|
|
@@ -940,7 +1288,7 @@
|
|
|
940
1288
|
className={`modal-tab ${modalTab === 'content' ? 'active' : ''}`}
|
|
941
1289
|
onClick={() => setModalTab('content')}
|
|
942
1290
|
>
|
|
943
|
-
Raw
|
|
1291
|
+
Raw JSON
|
|
944
1292
|
</div>
|
|
945
1293
|
</div>
|
|
946
1294
|
|
|
@@ -949,12 +1297,14 @@
|
|
|
949
1297
|
<div className="key-val-list">
|
|
950
1298
|
<div className="key-val-row">
|
|
951
1299
|
<div className="key-val-label">Recorded At</div>
|
|
952
|
-
<div className="key-val-value"
|
|
1300
|
+
<div className="key-val-value" style={{ color: '#fff', fontWeight: '500' }}>
|
|
1301
|
+
{formatTime(selectedEntry.recordedAt)}
|
|
1302
|
+
</div>
|
|
953
1303
|
</div>
|
|
954
1304
|
<div className="key-val-row">
|
|
955
1305
|
<div className="key-val-label">Entry Type</div>
|
|
956
|
-
<div className="key-val-value" style={{ textTransform: 'capitalize' }}>
|
|
957
|
-
{selectedEntry.type}
|
|
1306
|
+
<div className="key-val-value" style={{ textTransform: 'capitalize', fontWeight: '500' }}>
|
|
1307
|
+
{selectedEntry.type.replace('_', ' ')}
|
|
958
1308
|
</div>
|
|
959
1309
|
</div>
|
|
960
1310
|
{renderStructuredDetails(selectedEntry)}
|
|
@@ -972,6 +1322,11 @@
|
|
|
972
1322
|
);
|
|
973
1323
|
}
|
|
974
1324
|
|
|
1325
|
+
// Determine font family for subtitle
|
|
1326
|
+
function varMono(type) {
|
|
1327
|
+
return (type === 'query' || type === 'redis' || type === 'request') ? 'var(--font-mono)' : 'var(--font-sans)';
|
|
1328
|
+
}
|
|
1329
|
+
|
|
975
1330
|
// Helper functions for displaying table items
|
|
976
1331
|
function getEntrySummary(entry) {
|
|
977
1332
|
const { type, content } = entry;
|
|
@@ -986,19 +1341,19 @@
|
|
|
986
1341
|
<span className="badge badge-status-ok">{status}</span>
|
|
987
1342
|
);
|
|
988
1343
|
return {
|
|
989
|
-
title: `${req.method || 'GET'}
|
|
990
|
-
subtitle:
|
|
1344
|
+
title: `${req.method || 'GET'}`,
|
|
1345
|
+
subtitle: `${req.url || '/'}`,
|
|
991
1346
|
status: statusBadge
|
|
992
1347
|
};
|
|
993
1348
|
case 'query':
|
|
994
1349
|
return {
|
|
995
1350
|
title: content.query || 'DB Query',
|
|
996
|
-
subtitle: `${content.operation || 'QUERY'} on ${content.entity || 'db'}`,
|
|
1351
|
+
subtitle: `${content.operation || 'QUERY'} on ${content.entity || 'db'} (${content.duration || 0}ms)`,
|
|
997
1352
|
status: <span className="badge badge-status-ok">success</span>
|
|
998
1353
|
};
|
|
999
1354
|
case 'cache':
|
|
1000
1355
|
return {
|
|
1001
|
-
title: `${content.action}
|
|
1356
|
+
title: `${content.action}`,
|
|
1002
1357
|
subtitle: content.key || '',
|
|
1003
1358
|
status: <span className="badge badge-status-ok">{content.action}</span>
|
|
1004
1359
|
};
|
|
@@ -1082,11 +1437,11 @@
|
|
|
1082
1437
|
<React.Fragment>
|
|
1083
1438
|
<div className="key-val-row">
|
|
1084
1439
|
<div className="key-val-label">HTTP Method</div>
|
|
1085
|
-
<div className="key-val-value">{content.request?.method}</div>
|
|
1440
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.request?.method}</div>
|
|
1086
1441
|
</div>
|
|
1087
1442
|
<div className="key-val-row">
|
|
1088
1443
|
<div className="key-val-label">URL</div>
|
|
1089
|
-
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)' }}>{content.request?.url}</div>
|
|
1444
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)', color: '#818cf8' }}>{content.request?.url}</div>
|
|
1090
1445
|
</div>
|
|
1091
1446
|
<div className="key-val-row">
|
|
1092
1447
|
<div className="key-val-label">IP Address</div>
|
|
@@ -1096,18 +1451,24 @@
|
|
|
1096
1451
|
<div className="key-val-label">Duration</div>
|
|
1097
1452
|
<div className="key-val-value">{content.duration} ms</div>
|
|
1098
1453
|
</div>
|
|
1099
|
-
|
|
1100
|
-
<div className="key-val-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1454
|
+
{content.request?.headers && (
|
|
1455
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1456
|
+
<div className="key-val-label">Request Headers</div>
|
|
1457
|
+
<pre className="json-code">{JSON.stringify(content.request?.headers, null, 2)}</pre>
|
|
1458
|
+
</div>
|
|
1459
|
+
)}
|
|
1460
|
+
{content.request?.body && (
|
|
1461
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1462
|
+
<div className="key-val-label">Request Body</div>
|
|
1463
|
+
<pre className="json-code">{JSON.stringify(content.request?.body, null, 2)}</pre>
|
|
1464
|
+
</div>
|
|
1465
|
+
)}
|
|
1466
|
+
{content.response && (
|
|
1467
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1468
|
+
<div className="key-val-label">Response Payload</div>
|
|
1469
|
+
<pre className="json-code">{JSON.stringify(content.response, null, 2)}</pre>
|
|
1470
|
+
</div>
|
|
1471
|
+
)}
|
|
1111
1472
|
</React.Fragment>
|
|
1112
1473
|
);
|
|
1113
1474
|
case 'query':
|
|
@@ -1115,7 +1476,7 @@
|
|
|
1115
1476
|
<React.Fragment>
|
|
1116
1477
|
<div className="key-val-row">
|
|
1117
1478
|
<div className="key-val-label">Operation</div>
|
|
1118
|
-
<div className="key-val-value" style={{ fontWeight: '600' }}>{content.operation}</div>
|
|
1479
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.operation}</div>
|
|
1119
1480
|
</div>
|
|
1120
1481
|
<div className="key-val-row">
|
|
1121
1482
|
<div className="key-val-label">Entity/Table</div>
|
|
@@ -1125,14 +1486,18 @@
|
|
|
1125
1486
|
<div className="key-val-label">SQL Query</div>
|
|
1126
1487
|
<pre className="json-code" style={{ color: '#818cf8', fontWeight: '500' }}>{content.query}</pre>
|
|
1127
1488
|
</div>
|
|
1128
|
-
|
|
1129
|
-
<div className="key-val-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1489
|
+
{content.parameters && content.parameters.length > 0 && (
|
|
1490
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1491
|
+
<div className="key-val-label">Parameters</div>
|
|
1492
|
+
<pre className="json-code">{JSON.stringify(content.parameters, null, 2)}</pre>
|
|
1493
|
+
</div>
|
|
1494
|
+
)}
|
|
1495
|
+
{content.data && (
|
|
1496
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1497
|
+
<div className="key-val-label">Entity Data</div>
|
|
1498
|
+
<pre className="json-code">{JSON.stringify(content.data, null, 2)}</pre>
|
|
1499
|
+
</div>
|
|
1500
|
+
)}
|
|
1136
1501
|
</React.Fragment>
|
|
1137
1502
|
);
|
|
1138
1503
|
case 'exception':
|
|
@@ -1144,7 +1509,7 @@
|
|
|
1144
1509
|
</div>
|
|
1145
1510
|
<div className="key-val-row">
|
|
1146
1511
|
<div className="key-val-label">Message</div>
|
|
1147
|
-
<div className="key-val-value" style={{ fontWeight: '500' }}>{content.message}</div>
|
|
1512
|
+
<div className="key-val-value" style={{ fontWeight: '500', color: '#fff' }}>{content.message}</div>
|
|
1148
1513
|
</div>
|
|
1149
1514
|
{content.status && (
|
|
1150
1515
|
<div className="key-val-row">
|
|
@@ -1155,7 +1520,7 @@
|
|
|
1155
1520
|
{content.stack && (
|
|
1156
1521
|
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1157
1522
|
<div className="key-val-label">Stack Trace</div>
|
|
1158
|
-
<pre className="json-code" style={{ color: '#
|
|
1523
|
+
<pre className="json-code" style={{ color: '#f43f5e', overflowX: 'auto' }}>{content.stack}</pre>
|
|
1159
1524
|
</div>
|
|
1160
1525
|
)}
|
|
1161
1526
|
</React.Fragment>
|
|
@@ -1165,7 +1530,7 @@
|
|
|
1165
1530
|
<React.Fragment>
|
|
1166
1531
|
<div className="key-val-row">
|
|
1167
1532
|
<div className="key-val-label">Level</div>
|
|
1168
|
-
<div className="key-val-value" style={{ textTransform: 'uppercase' }}>{content.level}</div>
|
|
1533
|
+
<div className="key-val-value" style={{ textTransform: 'uppercase', fontWeight: '600' }}>{content.level}</div>
|
|
1169
1534
|
</div>
|
|
1170
1535
|
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1171
1536
|
<div className="key-val-label">Log Message</div>
|
|
@@ -1178,7 +1543,7 @@
|
|
|
1178
1543
|
<React.Fragment>
|
|
1179
1544
|
<div className="key-val-row">
|
|
1180
1545
|
<div className="key-val-label">Action</div>
|
|
1181
|
-
<div className="key-val-value">{content.action}</div>
|
|
1546
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.action}</div>
|
|
1182
1547
|
</div>
|
|
1183
1548
|
<div className="key-val-row">
|
|
1184
1549
|
<div className="key-val-label">Key</div>
|
|
@@ -1203,7 +1568,7 @@
|
|
|
1203
1568
|
<React.Fragment>
|
|
1204
1569
|
<div className="key-val-row">
|
|
1205
1570
|
<div className="key-val-label">Job ID</div>
|
|
1206
|
-
<div className="key-val-value">{content.jobId}</div>
|
|
1571
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)' }}>{content.jobId}</div>
|
|
1207
1572
|
</div>
|
|
1208
1573
|
<div className="key-val-row">
|
|
1209
1574
|
<div className="key-val-label">Status</div>
|
|
@@ -1236,16 +1601,18 @@
|
|
|
1236
1601
|
</div>
|
|
1237
1602
|
<div className="key-val-row">
|
|
1238
1603
|
<div className="key-val-label">Subject</div>
|
|
1239
|
-
<div className="key-val-value" style={{ fontWeight: '600' }}>{content.subject}</div>
|
|
1240
|
-
</div>
|
|
1241
|
-
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1242
|
-
<div className="key-val-label">Plain Text Content</div>
|
|
1243
|
-
<pre className="json-code" style={{ whiteSpace: 'pre-wrap' }}>{content.text}</pre>
|
|
1604
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.subject}</div>
|
|
1244
1605
|
</div>
|
|
1606
|
+
{content.text && (
|
|
1607
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1608
|
+
<div className="key-val-label">Plain Text Content</div>
|
|
1609
|
+
<pre className="json-code" style={{ whiteSpace: 'pre-wrap' }}>{content.text}</pre>
|
|
1610
|
+
</div>
|
|
1611
|
+
)}
|
|
1245
1612
|
{content.html && (
|
|
1246
1613
|
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1247
1614
|
<div className="key-val-label">HTML Content</div>
|
|
1248
|
-
<iframe srcDoc={content.html} style={{ border: '1px solid var(--border-color)', borderRadius: '8px', background: '#fff', width: '100%', height: '
|
|
1615
|
+
<iframe srcDoc={content.html} style={{ border: '1px solid var(--border-color)', borderRadius: '8px', background: '#fff', width: '100%', height: '350px' }} />
|
|
1249
1616
|
</div>
|
|
1250
1617
|
)}
|
|
1251
1618
|
</React.Fragment>
|