react-native-debug-toolkit 3.0.0 → 3.1.3
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 +115 -97
- package/README.zh-CN.md +113 -95
- package/lib/commonjs/core/initialize.js +5 -0
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/index.js +23 -26
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js +24 -58
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +721 -0
- package/lib/commonjs/utils/DaemonClient.js.map +1 -0
- package/lib/commonjs/utils/{sessionReport.js → deviceReport.js} +3 -3
- package/lib/commonjs/utils/deviceReport.js.map +1 -0
- package/lib/module/core/initialize.js +6 -0
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/index.js +3 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/panel/StreamingSettingsModal.js +21 -55
- package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +703 -0
- package/lib/module/utils/DaemonClient.js.map +1 -0
- package/lib/module/utils/{sessionReport.js → deviceReport.js} +2 -2
- package/lib/module/utils/deviceReport.js.map +1 -0
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -10
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +141 -0
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -0
- package/lib/typescript/src/utils/{sessionReport.d.ts → deviceReport.d.ts} +4 -4
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -0
- package/node/daemon/src/cli.js +9 -2
- package/node/daemon/src/console/console.html +1052 -249
- package/node/daemon/src/constants.js +6 -0
- package/node/daemon/src/server.js +205 -123
- package/node/daemon/src/store.js +122 -45
- package/node/mcp/src/daemonClient.js +6 -6
- package/node/mcp/src/index.js +2 -2
- package/node/mcp/src/logs.js +5 -4
- package/node/mcp/src/tools.js +16 -16
- package/package.json +2 -2
- package/src/core/initialize.ts +8 -0
- package/src/index.ts +18 -10
- package/src/ui/panel/StreamingSettingsModal.tsx +25 -63
- package/src/utils/DaemonClient.ts +887 -0
- package/src/utils/{sessionReport.ts → deviceReport.ts} +6 -6
- package/lib/commonjs/utils/autoDetectDaemon.js +0 -141
- package/lib/commonjs/utils/autoDetectDaemon.js.map +0 -1
- package/lib/commonjs/utils/daemonConnection.js +0 -81
- package/lib/commonjs/utils/daemonConnection.js.map +0 -1
- package/lib/commonjs/utils/daemonSettings.js +0 -110
- package/lib/commonjs/utils/daemonSettings.js.map +0 -1
- package/lib/commonjs/utils/reportToDaemon.js +0 -112
- package/lib/commonjs/utils/reportToDaemon.js.map +0 -1
- package/lib/commonjs/utils/sessionReport.js.map +0 -1
- package/lib/commonjs/utils/streamToDaemon.js +0 -334
- package/lib/commonjs/utils/streamToDaemon.js.map +0 -1
- package/lib/module/utils/autoDetectDaemon.js +0 -136
- package/lib/module/utils/autoDetectDaemon.js.map +0 -1
- package/lib/module/utils/daemonConnection.js +0 -77
- package/lib/module/utils/daemonConnection.js.map +0 -1
- package/lib/module/utils/daemonSettings.js +0 -102
- package/lib/module/utils/daemonSettings.js.map +0 -1
- package/lib/module/utils/reportToDaemon.js +0 -105
- package/lib/module/utils/reportToDaemon.js.map +0 -1
- package/lib/module/utils/sessionReport.js.map +0 -1
- package/lib/module/utils/streamToDaemon.js +0 -328
- package/lib/module/utils/streamToDaemon.js.map +0 -1
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts +0 -15
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +0 -1
- package/lib/typescript/src/utils/daemonConnection.d.ts +0 -18
- package/lib/typescript/src/utils/daemonConnection.d.ts.map +0 -1
- package/lib/typescript/src/utils/daemonSettings.d.ts +0 -19
- package/lib/typescript/src/utils/daemonSettings.d.ts.map +0 -1
- package/lib/typescript/src/utils/reportToDaemon.d.ts +0 -34
- package/lib/typescript/src/utils/reportToDaemon.d.ts.map +0 -1
- package/lib/typescript/src/utils/sessionReport.d.ts.map +0 -1
- package/lib/typescript/src/utils/streamToDaemon.d.ts +0 -23
- package/lib/typescript/src/utils/streamToDaemon.d.ts.map +0 -1
- package/src/utils/autoDetectDaemon.ts +0 -175
- package/src/utils/daemonConnection.ts +0 -133
- package/src/utils/daemonSettings.ts +0 -134
- package/src/utils/reportToDaemon.ts +0 -172
- package/src/utils/streamToDaemon.ts +0 -419
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
--red:#ff1744;--red-dim:rgba(255,23,68,.12);
|
|
16
16
|
--amber:#ffab00;--amber-dim:rgba(255,171,0,.12);
|
|
17
17
|
--orange:#ff6e40;--orange-dim:rgba(255,110,64,.12);
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
--purple:#7c4dff;--purple-dim:rgba(124,77,255,.12);
|
|
19
|
+
--pink:#e040fb;--pink-dim:rgba(224,64,251,.12);
|
|
20
|
+
--font-mono:'SF Mono',Monaco,Consolas,monospace;
|
|
21
|
+
--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
20
22
|
--radius:6px;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -32,7 +34,6 @@ body{
|
|
|
32
34
|
repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,229,255,.012) 2px,rgba(0,229,255,.012) 4px);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
/* Scanline overlay */
|
|
36
37
|
body::after{
|
|
37
38
|
content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;
|
|
38
39
|
background:repeating-linear-gradient(0deg,transparent 0px,transparent 1px,rgba(0,0,0,.03) 1px,rgba(0,0,0,.03) 2px);
|
|
@@ -73,6 +74,9 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
73
74
|
.btn:hover{background:var(--surface3);color:var(--text);border-color:var(--cyan-mid)}
|
|
74
75
|
.btn:active{transform:scale(.97)}
|
|
75
76
|
.btn-icon{padding:6px 10px;font-size:14px}
|
|
77
|
+
.btn-sm{padding:3px 8px;font-size:10px;border-radius:3px}
|
|
78
|
+
.btn-ghost{background:transparent;border-color:transparent}
|
|
79
|
+
.btn-ghost:hover{background:var(--surface2);border-color:var(--border)}
|
|
76
80
|
|
|
77
81
|
/* Container */
|
|
78
82
|
.container{max-width:1160px;margin:0 auto;padding:28px 24px}
|
|
@@ -87,7 +91,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
87
91
|
border:1px solid var(--border);color:var(--cyan);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
/*
|
|
94
|
+
/* Device list */
|
|
91
95
|
.section-title{
|
|
92
96
|
font-size:13px;font-weight:600;color:var(--text2);
|
|
93
97
|
text-transform:uppercase;letter-spacing:.08em;
|
|
@@ -95,21 +99,26 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
95
99
|
}
|
|
96
100
|
.section-title::after{content:'';flex:1;height:1px;background:var(--border)}
|
|
97
101
|
|
|
98
|
-
.
|
|
99
|
-
.
|
|
100
|
-
display:grid;grid-template-columns:
|
|
102
|
+
.device-grid{display:flex;flex-direction:column;gap:6px}
|
|
103
|
+
.device-card{
|
|
104
|
+
display:grid;grid-template-columns:minmax(0,1.2fr) minmax(170px,.8fr) auto 20px;align-items:center;gap:16px;
|
|
101
105
|
padding:14px 18px;background:var(--surface);
|
|
102
106
|
border:1px solid var(--border);border-radius:var(--radius);
|
|
103
107
|
cursor:pointer;transition:all .15s;
|
|
104
108
|
}
|
|
105
|
-
.
|
|
109
|
+
.device-card:hover{
|
|
106
110
|
background:var(--surface2);border-color:var(--border2);
|
|
107
111
|
transform:translateX(3px);
|
|
108
112
|
box-shadow:0 0 0 1px var(--cyan-dim),0 4px 16px rgba(0,0,0,.3);
|
|
109
113
|
}
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
.
|
|
114
|
+
.device-id{font-family:var(--font-mono);font-size:12px;color:var(--text);font-weight:500}
|
|
115
|
+
.device-time{font-size:11px;color:var(--text3);font-family:var(--font-mono);margin-top:2px}
|
|
116
|
+
.device-title{font-size:13px;color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
117
|
+
.device-subtitle{font-family:var(--font-mono);font-size:11px;color:var(--text2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
118
|
+
.device-meta-group{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
119
|
+
.device-meta-line{font-family:var(--font-mono);font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
120
|
+
.device-meta-line strong{font-family:var(--font-sans);font-weight:600;color:var(--text2);margin-right:5px}
|
|
121
|
+
.device-tags{display:flex;gap:6px;flex-wrap:wrap}
|
|
113
122
|
.tag{
|
|
114
123
|
font-family:var(--font-mono);font-size:10px;font-weight:500;
|
|
115
124
|
padding:2px 8px;border-radius:3px;
|
|
@@ -121,8 +130,8 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
121
130
|
.tag-error{background:var(--red-dim);color:var(--red);border-color:rgba(255,23,68,.1)}
|
|
122
131
|
.tag-warn{background:var(--amber-dim);color:var(--amber);border-color:rgba(255,171,0,.1)}
|
|
123
132
|
.tag-track{background:var(--orange-dim);color:var(--orange);border-color:rgba(255,110,64,.1)}
|
|
124
|
-
.
|
|
125
|
-
.
|
|
133
|
+
.device-arrow{color:var(--text3);font-size:16px;transition:color .15s}
|
|
134
|
+
.device-card:hover .device-arrow{color:var(--cyan)}
|
|
126
135
|
|
|
127
136
|
/* Detail view */
|
|
128
137
|
.back-link{
|
|
@@ -170,12 +179,6 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
170
179
|
font-size:12px;color:var(--text2);display:flex;align-items:center;gap:6px;
|
|
171
180
|
font-family:var(--font-mono);cursor:pointer;
|
|
172
181
|
}
|
|
173
|
-
.toolbar input[type=number]{
|
|
174
|
-
width:56px;padding:4px 8px;
|
|
175
|
-
background:var(--surface);border:1px solid var(--border2);border-radius:4px;
|
|
176
|
-
color:var(--text);font-size:11px;font-family:var(--font-mono);
|
|
177
|
-
}
|
|
178
|
-
.toolbar input[type=number]:focus{outline:none;border-color:var(--cyan)}
|
|
179
182
|
.toggle{
|
|
180
183
|
position:relative;width:32px;height:18px;
|
|
181
184
|
background:var(--surface3);border-radius:9px;cursor:pointer;
|
|
@@ -189,11 +192,86 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
189
192
|
}
|
|
190
193
|
.toggle.on::after{transform:translateX(14px)}
|
|
191
194
|
|
|
192
|
-
/*
|
|
195
|
+
/* Search */
|
|
196
|
+
.search-wrap{
|
|
197
|
+
flex:1;min-width:160px;position:relative;
|
|
198
|
+
}
|
|
199
|
+
.search-input{
|
|
200
|
+
width:100%;padding:5px 10px 5px 28px;
|
|
201
|
+
background:var(--surface);border:1px solid var(--border2);border-radius:4px;
|
|
202
|
+
color:var(--text);font-size:12px;font-family:var(--font-mono);
|
|
203
|
+
transition:border-color .15s;
|
|
204
|
+
}
|
|
205
|
+
.search-input::placeholder{color:var(--text3)}
|
|
206
|
+
.search-input:focus{outline:none;border-color:var(--cyan)}
|
|
207
|
+
.search-icon{
|
|
208
|
+
position:absolute;left:8px;top:50%;transform:translateY(-50%);
|
|
209
|
+
color:var(--text3);font-size:13px;pointer-events:none;
|
|
210
|
+
}
|
|
211
|
+
.search-clear{
|
|
212
|
+
position:absolute;right:6px;top:50%;transform:translateY(-50%);
|
|
213
|
+
background:none;border:none;color:var(--text3);cursor:pointer;
|
|
214
|
+
font-size:14px;padding:2px 4px;line-height:1;
|
|
215
|
+
display:none;
|
|
216
|
+
}
|
|
217
|
+
.search-clear.visible{display:block}
|
|
218
|
+
.search-clear:hover{color:var(--text)}
|
|
219
|
+
.kbd{
|
|
220
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text3);
|
|
221
|
+
background:var(--surface2);border:1px solid var(--border2);
|
|
222
|
+
padding:1px 4px;border-radius:2px;letter-spacing:.02em;
|
|
223
|
+
}
|
|
224
|
+
.pager{
|
|
225
|
+
display:flex;align-items:center;justify-content:flex-end;gap:8px;
|
|
226
|
+
flex-wrap:wrap;color:var(--text3);font-family:var(--font-mono);font-size:11px;
|
|
227
|
+
}
|
|
228
|
+
.toolbar .pager{margin-left:auto}
|
|
229
|
+
.pager-bottom{margin-top:14px}
|
|
230
|
+
.pager-info{white-space:nowrap}
|
|
231
|
+
.page-btn{
|
|
232
|
+
padding:4px 9px;border:1px solid var(--border2);border-radius:4px;
|
|
233
|
+
background:var(--surface);color:var(--text2);font-family:var(--font-mono);
|
|
234
|
+
font-size:11px;cursor:pointer;
|
|
235
|
+
}
|
|
236
|
+
.page-btn:hover:not(:disabled){color:var(--cyan);border-color:var(--cyan-mid)}
|
|
237
|
+
.page-btn:disabled{opacity:.4;cursor:not-allowed}
|
|
238
|
+
.live-notice{
|
|
239
|
+
display:none;padding:4px 9px;border:1px solid var(--cyan-mid);border-radius:4px;
|
|
240
|
+
background:var(--cyan-dim);color:var(--cyan);font-family:var(--font-mono);
|
|
241
|
+
font-size:11px;cursor:pointer;
|
|
242
|
+
}
|
|
243
|
+
.live-notice.visible{display:inline-flex}
|
|
244
|
+
|
|
245
|
+
/* Curl help - collapsible */
|
|
246
|
+
.curl-panel{
|
|
247
|
+
margin-bottom:18px;padding:12px 14px;background:var(--bg2);
|
|
248
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
249
|
+
}
|
|
250
|
+
.curl-header{
|
|
251
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
252
|
+
cursor:pointer;user-select:none;
|
|
253
|
+
}
|
|
254
|
+
.curl-title{
|
|
255
|
+
font-size:11px;font-family:var(--font-mono);font-weight:700;
|
|
256
|
+
color:var(--cyan);text-transform:uppercase;letter-spacing:.08em;
|
|
257
|
+
}
|
|
258
|
+
.curl-toggle{
|
|
259
|
+
font-size:10px;color:var(--text3);transition:transform .2s;
|
|
260
|
+
}
|
|
261
|
+
.curl-toggle.open{transform:rotate(90deg)}
|
|
262
|
+
.curl-body{display:none;margin-top:10px}
|
|
263
|
+
.curl-body.open{display:block}
|
|
264
|
+
.curl-list{display:flex;flex-direction:column;gap:6px}
|
|
265
|
+
.curl-list code{
|
|
266
|
+
display:block;padding:7px 9px;background:rgba(0,0,0,.18);
|
|
267
|
+
border:1px solid rgba(42,63,102,.65);border-radius:4px;
|
|
268
|
+
color:var(--text2);font-family:var(--font-mono);font-size:11px;
|
|
269
|
+
white-space:pre-wrap;word-break:break-word;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Log entries - redesigned */
|
|
193
273
|
.log-list{display:flex;flex-direction:column;gap:2px}
|
|
194
274
|
.log-entry{
|
|
195
|
-
display:grid;grid-template-columns:80px 1fr 90px 30px;
|
|
196
|
-
align-items:center;gap:0;
|
|
197
275
|
background:var(--surface);border:1px solid transparent;border-radius:var(--radius);
|
|
198
276
|
cursor:pointer;transition:all .15s;overflow:hidden;
|
|
199
277
|
animation:fadeSlideIn .3s ease-out both;
|
|
@@ -203,51 +281,174 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
203
281
|
background:var(--surface2);border-color:var(--border2);
|
|
204
282
|
box-shadow:0 2px 8px rgba(0,0,0,.2);
|
|
205
283
|
}
|
|
206
|
-
.log-entry.expanded{
|
|
284
|
+
.log-entry.expanded{
|
|
285
|
+
border-color:var(--cyan-mid);background:var(--surface2);
|
|
286
|
+
box-shadow:0 2px 12px rgba(0,0,0,.25);
|
|
287
|
+
}
|
|
288
|
+
.log-entry.focused{outline:1px solid var(--cyan);outline-offset:-1px}
|
|
289
|
+
|
|
290
|
+
.log-row{
|
|
291
|
+
display:grid;grid-template-columns:90px 1fr auto auto 28px;
|
|
292
|
+
align-items:center;gap:0;
|
|
293
|
+
padding:0 4px 0 0;min-height:46px;
|
|
294
|
+
}
|
|
207
295
|
.log-type{
|
|
208
296
|
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
209
297
|
text-transform:uppercase;letter-spacing:.06em;
|
|
210
|
-
padding:0
|
|
211
|
-
|
|
298
|
+
padding:0 12px;height:100%;display:flex;align-items:center;
|
|
299
|
+
justify-content:center;text-align:center;
|
|
212
300
|
}
|
|
213
301
|
.log-type-network{color:var(--cyan)}
|
|
214
302
|
.log-type-console{color:var(--green)}
|
|
215
|
-
.log-type-navigation{color
|
|
303
|
+
.log-type-navigation{color:var(--purple)}
|
|
216
304
|
.log-type-track{color:var(--orange)}
|
|
217
|
-
.log-type-zustand{color
|
|
305
|
+
.log-type-zustand{color:var(--pink)}
|
|
218
306
|
.log-type-unknown{color:var(--text3)}
|
|
307
|
+
|
|
308
|
+
.log-summary-col{
|
|
309
|
+
padding:8px 12px;min-width:0;overflow:hidden;
|
|
310
|
+
}
|
|
219
311
|
.log-summary{
|
|
220
|
-
padding:0 14px;height:42px;display:flex;align-items:center;
|
|
221
312
|
font-family:var(--font-mono);font-size:12px;color:var(--text);
|
|
222
|
-
|
|
313
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
|
|
314
|
+
overflow:hidden;line-height:1.5;word-break:break-all;
|
|
315
|
+
}
|
|
316
|
+
.log-timestamp{
|
|
317
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text3);
|
|
318
|
+
margin-top:2px;
|
|
223
319
|
}
|
|
320
|
+
|
|
224
321
|
.log-status{
|
|
225
|
-
padding:0
|
|
322
|
+
padding:0 8px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
226
323
|
}
|
|
227
324
|
.badge{
|
|
228
325
|
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
229
|
-
padding:2px 8px;border-radius:3px;letter-spacing:.02em;
|
|
326
|
+
padding:2px 8px;border-radius:3px;letter-spacing:.02em;white-space:nowrap;
|
|
230
327
|
}
|
|
231
328
|
.badge-ok{background:var(--green-dim);color:var(--green)}
|
|
232
329
|
.badge-error{background:var(--red-dim);color:var(--red)}
|
|
233
330
|
.badge-warn{background:var(--amber-dim);color:var(--amber)}
|
|
234
331
|
.badge-info{background:var(--cyan-dim);color:var(--cyan)}
|
|
332
|
+
|
|
333
|
+
.log-copy{
|
|
334
|
+
padding:0 4px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
335
|
+
opacity:0;transition:opacity .15s;
|
|
336
|
+
}
|
|
337
|
+
.log-entry:hover .log-copy{opacity:1}
|
|
338
|
+
.copy-btn{
|
|
339
|
+
background:none;border:1px solid transparent;cursor:pointer;
|
|
340
|
+
color:var(--text3);font-size:13px;padding:2px 4px;border-radius:3px;
|
|
341
|
+
transition:all .15s;
|
|
342
|
+
}
|
|
343
|
+
.copy-btn:hover{color:var(--cyan);background:var(--cyan-dim);border-color:rgba(0,229,255,.15)}
|
|
344
|
+
|
|
235
345
|
.log-expand{
|
|
236
|
-
padding:0 8px;height:
|
|
237
|
-
color:var(--text3);font-size:10px;transition:color .15s;
|
|
346
|
+
padding:0 8px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
347
|
+
color:var(--text3);font-size:10px;transition:transform .2s,color .15s;
|
|
238
348
|
}
|
|
239
349
|
.log-entry:hover .log-expand{color:var(--cyan)}
|
|
240
|
-
.log-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
350
|
+
.log-entry.expanded .log-expand{transform:rotate(90deg);color:var(--cyan)}
|
|
351
|
+
|
|
352
|
+
/* Expanded detail panel */
|
|
353
|
+
.log-detail{
|
|
354
|
+
display:none;border-top:1px solid var(--border);
|
|
355
|
+
animation:detailFadeIn .2s ease-out;
|
|
245
356
|
}
|
|
246
|
-
.log-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
357
|
+
.log-entry.expanded .log-detail{display:block}
|
|
358
|
+
@keyframes detailFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
|
|
359
|
+
|
|
360
|
+
.log-detail-inner{padding:16px 18px}
|
|
361
|
+
|
|
362
|
+
/* Detail sections */
|
|
363
|
+
.detail-sections{display:flex;flex-direction:column;gap:10px}
|
|
364
|
+
.detail-section{
|
|
365
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
366
|
+
background:rgba(8,12,22,.35);overflow:hidden;
|
|
367
|
+
}
|
|
368
|
+
.detail-section-header{
|
|
369
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
370
|
+
padding:7px 12px;border-bottom:1px solid var(--border);
|
|
371
|
+
background:rgba(0,229,255,.03);
|
|
372
|
+
}
|
|
373
|
+
.detail-section-title{
|
|
374
|
+
font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
|
375
|
+
color:var(--cyan);font-family:var(--font-mono);
|
|
376
|
+
}
|
|
377
|
+
.detail-section-copy{
|
|
378
|
+
background:none;border:none;cursor:pointer;
|
|
379
|
+
color:var(--text3);font-size:11px;padding:1px 4px;border-radius:2px;
|
|
380
|
+
transition:color .15s;
|
|
381
|
+
}
|
|
382
|
+
.detail-section-copy:hover{color:var(--cyan)}
|
|
383
|
+
.detail-section-body{padding:0}
|
|
384
|
+
|
|
385
|
+
/* Detail key-value table */
|
|
386
|
+
.detail-table{width:100%;border-collapse:collapse}
|
|
387
|
+
.detail-table tr{border-bottom:1px solid rgba(30,45,74,.5)}
|
|
388
|
+
.detail-table tr:last-child{border-bottom:none}
|
|
389
|
+
.detail-table th,.detail-table td{
|
|
390
|
+
padding:8px 12px;vertical-align:top;font-size:12px;
|
|
391
|
+
}
|
|
392
|
+
.detail-table th{
|
|
393
|
+
width:100px;color:var(--text3);font-family:var(--font-mono);font-weight:500;
|
|
394
|
+
text-align:left;white-space:nowrap;
|
|
395
|
+
}
|
|
396
|
+
.detail-table td{color:var(--text2);font-family:var(--font-mono);word-break:break-word;line-height:1.6}
|
|
397
|
+
|
|
398
|
+
/* JSON blocks */
|
|
399
|
+
.json-block{
|
|
400
|
+
margin:0;padding:10px 12px;font-family:var(--font-mono);font-size:11px;
|
|
401
|
+
line-height:1.7;color:var(--text2);white-space:pre-wrap;word-break:break-word;
|
|
402
|
+
max-height:320px;overflow:auto;background:rgba(0,0,0,.16);
|
|
403
|
+
}
|
|
404
|
+
.json-block .json-key{color:var(--cyan)}
|
|
405
|
+
.json-block .json-string{color:var(--green)}
|
|
406
|
+
.json-block .json-number{color:var(--amber)}
|
|
407
|
+
.json-block .json-bool{color:var(--purple)}
|
|
408
|
+
.json-block .json-null{color:var(--text3)}
|
|
409
|
+
.json-compact{max-height:160px;font-size:10px;line-height:1.5;padding:8px 10px}
|
|
410
|
+
|
|
411
|
+
/* Value pills */
|
|
412
|
+
.value-list{display:flex;flex-direction:column;gap:6px;padding:10px 12px}
|
|
413
|
+
.value-pill{
|
|
414
|
+
display:block;padding:8px 10px;border:1px solid rgba(42,63,102,.6);
|
|
415
|
+
border-radius:4px;background:rgba(0,0,0,.1);font-family:var(--font-mono);
|
|
416
|
+
font-size:12px;color:var(--text2);word-break:break-word;line-height:1.6;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* Network detail hero */
|
|
420
|
+
.network-hero{
|
|
421
|
+
padding:12px 14px;border-bottom:1px solid rgba(30,45,74,.5);
|
|
422
|
+
display:flex;align-items:center;gap:10px;
|
|
423
|
+
}
|
|
424
|
+
.method-badge{
|
|
425
|
+
font-family:var(--font-mono);font-size:11px;font-weight:700;
|
|
426
|
+
padding:3px 10px;border-radius:3px;letter-spacing:.04em;
|
|
427
|
+
}
|
|
428
|
+
.method-get{background:var(--cyan-dim);color:var(--cyan)}
|
|
429
|
+
.method-post{background:var(--green-dim);color:var(--green)}
|
|
430
|
+
.method-put{background:var(--amber-dim);color:var(--amber)}
|
|
431
|
+
.method-patch{background:var(--amber-dim);color:var(--amber)}
|
|
432
|
+
.method-delete{background:var(--red-dim);color:var(--red)}
|
|
433
|
+
.network-url{
|
|
434
|
+
font-family:var(--font-mono);font-size:12px;color:var(--text);
|
|
435
|
+
word-break:break-all;line-height:1.5;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* Navigation hero */
|
|
439
|
+
.nav-hero{
|
|
440
|
+
padding:12px 14px;border-bottom:1px solid rgba(30,45,74,.5);
|
|
441
|
+
display:flex;align-items:center;gap:8px;
|
|
442
|
+
font-family:var(--font-mono);font-size:12px;
|
|
443
|
+
}
|
|
444
|
+
.nav-from{color:var(--text3)}
|
|
445
|
+
.nav-arrow{color:var(--cyan);font-size:14px}
|
|
446
|
+
.nav-to{color:var(--text);font-weight:500}
|
|
447
|
+
|
|
448
|
+
/* Entry footer */
|
|
449
|
+
.entry-footer{
|
|
450
|
+
display:flex;align-items:center;gap:8px;
|
|
451
|
+
padding:10px 0 0;margin-top:10px;border-top:1px solid rgba(30,45,74,.5);
|
|
251
452
|
}
|
|
252
453
|
|
|
253
454
|
/* Actions bar */
|
|
@@ -302,13 +503,23 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
302
503
|
@keyframes livePulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
303
504
|
.live-badge-dot{width:6px;height:6px;border-radius:50%;background:var(--cyan)}
|
|
304
505
|
|
|
506
|
+
/* Search highlight */
|
|
507
|
+
mark{
|
|
508
|
+
background:rgba(0,229,255,.25);color:var(--text);
|
|
509
|
+
border-radius:2px;padding:0 1px;
|
|
510
|
+
}
|
|
511
|
+
|
|
305
512
|
/* Responsive */
|
|
306
513
|
@media(max-width:640px){
|
|
307
514
|
.container{padding:16px}
|
|
308
|
-
.
|
|
309
|
-
.log-
|
|
515
|
+
.device-card{grid-template-columns:1fr;gap:8px}
|
|
516
|
+
.log-row{grid-template-columns:72px 1fr auto 28px}
|
|
517
|
+
.log-copy{display:none}
|
|
518
|
+
.detail-table th{width:80px}
|
|
310
519
|
.detail-header{padding:14px 16px}
|
|
311
520
|
.tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
521
|
+
.toolbar{gap:8px}
|
|
522
|
+
.search-wrap{min-width:120px;flex-basis:100%}
|
|
312
523
|
}
|
|
313
524
|
</style>
|
|
314
525
|
</head>
|
|
@@ -320,6 +531,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
320
531
|
</div>
|
|
321
532
|
<div class="header-right">
|
|
322
533
|
<span class="header-meta" id="status"></span>
|
|
534
|
+
<span class="header-meta" id="ipHint" style="color:var(--text2);display:none"></span>
|
|
323
535
|
<button class="btn" onclick="refresh()">
|
|
324
536
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>
|
|
325
537
|
Refresh
|
|
@@ -336,28 +548,62 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
336
548
|
var app = document.getElementById('app');
|
|
337
549
|
var statusEl = document.getElementById('status');
|
|
338
550
|
var toastEl = document.getElementById('toast');
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
551
|
+
var pulseDot = document.getElementById('pulseDot');
|
|
552
|
+
var currentDevice = null;
|
|
553
|
+
var expandedRows = {};
|
|
554
|
+
var authToken = null;
|
|
555
|
+
var searchTerm = '';
|
|
556
|
+
var focusedIndex = -1;
|
|
557
|
+
var PAGE_SIZE = 200;
|
|
558
|
+
var currentPage = 1;
|
|
559
|
+
var pendingLiveCount = 0;
|
|
560
|
+
var liveSequence = 0;
|
|
561
|
+
|
|
562
|
+
try {
|
|
563
|
+
var params = new URLSearchParams(location.search);
|
|
564
|
+
authToken = params.get('token') || localStorage.getItem('debugToolkitToken');
|
|
565
|
+
if (authToken) localStorage.setItem('debugToolkitToken', authToken);
|
|
566
|
+
} catch {
|
|
567
|
+
authToken = null;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function withAuth(path) {
|
|
571
|
+
if (!authToken) return path;
|
|
572
|
+
var join = path.indexOf('?') >= 0 ? '&' : '?';
|
|
573
|
+
return path + join + 'token=' + encodeURIComponent(authToken);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function api(path) {
|
|
577
|
+
return fetch(withAuth(path)).then(function(r) {
|
|
578
|
+
return r.json().then(function(body) {
|
|
579
|
+
if (!r.ok) throw new Error(body && body.error ? body.error : ('HTTP ' + r.status));
|
|
580
|
+
return body;
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function absoluteUrl(path) {
|
|
586
|
+
return location.origin + withAuth(path);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function curlCommand(path) {
|
|
590
|
+
return "curl '" + absoluteUrl(path).replace(/'/g, "'\\''") + "'";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function renderCurlPanel(title, commands) {
|
|
594
|
+
var id = 'curl-' + Math.random().toString(36).slice(2,8);
|
|
595
|
+
var html = '<div class="curl-panel">';
|
|
596
|
+
html += '<div class="curl-header" onclick="toggleCurl(\'' + id + '\')">';
|
|
597
|
+
html += '<div class="curl-title">' + escapeHtml(title) + '</div>';
|
|
598
|
+
html += '<span class="curl-toggle" id="toggle-' + id + '">▶</span>';
|
|
599
|
+
html += '</div>';
|
|
600
|
+
html += '<div class="curl-body" id="' + id + '"><div class="curl-list">';
|
|
601
|
+
commands.forEach(function(command) {
|
|
602
|
+
html += '<code>' + escapeHtml(command) + '</code>';
|
|
603
|
+
});
|
|
604
|
+
html += '</div></div></div>';
|
|
605
|
+
return html;
|
|
606
|
+
}
|
|
361
607
|
|
|
362
608
|
function showToast(msg) {
|
|
363
609
|
toastEl.textContent = msg;
|
|
@@ -385,10 +631,52 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
385
631
|
if (!iso) return '';
|
|
386
632
|
try {
|
|
387
633
|
var d = new Date(iso);
|
|
388
|
-
return
|
|
634
|
+
var pad = function(n) { return String(n).padStart(2, '0'); };
|
|
635
|
+
return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + String(d.getMilliseconds()).padStart(3,'0').slice(0,2);
|
|
389
636
|
} catch { return iso; }
|
|
390
637
|
}
|
|
391
638
|
|
|
639
|
+
function readObject(value) {
|
|
640
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function readTimestamp(entry) {
|
|
644
|
+
if (!entry || typeof entry !== 'object') return 0;
|
|
645
|
+
var value = entry.timestamp || entry.time || entry.createdAt;
|
|
646
|
+
if (typeof value === 'number') return value;
|
|
647
|
+
if (typeof value === 'string') {
|
|
648
|
+
var parsed = Date.parse(value);
|
|
649
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
650
|
+
}
|
|
651
|
+
return 0;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function labelForType(type) {
|
|
655
|
+
var labels = {
|
|
656
|
+
network: 'Network',
|
|
657
|
+
console: 'Console',
|
|
658
|
+
navigation: 'Navigation',
|
|
659
|
+
track: 'Track',
|
|
660
|
+
zustand: 'State',
|
|
661
|
+
clipboard: 'Clipboard',
|
|
662
|
+
environment: 'Environment',
|
|
663
|
+
};
|
|
664
|
+
return labels[type] || (type ? type.charAt(0).toUpperCase() + type.slice(1) : 'Unknown');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function formatDevice(device) {
|
|
668
|
+
if (!device || typeof device !== 'object') return 'Unknown device';
|
|
669
|
+
var parts = [];
|
|
670
|
+
if (device.platform) parts.push(String(device.platform).toUpperCase());
|
|
671
|
+
if (device.model) parts.push(String(device.model));
|
|
672
|
+
if (device.osVersion) parts.push('OS ' + String(device.osVersion));
|
|
673
|
+
return parts.length ? parts.join(' / ') : 'Unknown device';
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function formatIp(source) {
|
|
677
|
+
return source && source.ip ? String(source.ip) : 'unknown ip';
|
|
678
|
+
}
|
|
679
|
+
|
|
392
680
|
function statusBadge(entry) {
|
|
393
681
|
if (!entry || typeof entry !== 'object') return '<span class="badge badge-info">-</span>';
|
|
394
682
|
if (entry.error) return '<span class="badge badge-error">ERR</span>';
|
|
@@ -401,25 +689,33 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
401
689
|
}
|
|
402
690
|
if (entry.level === 'error') return '<span class="badge badge-error">ERR</span>';
|
|
403
691
|
if (entry.level === 'warn') return '<span class="badge badge-warn">WRN</span>';
|
|
404
|
-
return '
|
|
692
|
+
return '';
|
|
405
693
|
}
|
|
406
694
|
|
|
407
695
|
function summarize(entry) {
|
|
408
696
|
if (!entry || typeof entry !== 'object') return escapeHtml(String(entry));
|
|
697
|
+
if (entry.request && typeof entry.request === 'object') {
|
|
698
|
+
return escapeHtml((entry.request.method || 'GET') + ' ' + (entry.request.url || '-'));
|
|
699
|
+
}
|
|
409
700
|
if (entry.method && entry.url) return escapeHtml(entry.method + ' ' + entry.url);
|
|
410
|
-
if (entry.level && entry.data
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return escapeHtml(
|
|
701
|
+
if (entry.level && entry.data !== undefined) {
|
|
702
|
+
var data = Array.isArray(entry.data) ? entry.data.map(formatInlineValue).join(' ') : formatInlineValue(entry.data);
|
|
703
|
+
return escapeHtml(data.substring(0, 200));
|
|
704
|
+
}
|
|
705
|
+
if (entry.from || entry.to) return escapeHtml((entry.from || '-') + ' -> ' + (entry.to || '-'));
|
|
706
|
+
if (entry.eventName) return escapeHtml(String(entry.eventName));
|
|
707
|
+
if (entry.event) return escapeHtml(String(entry.event));
|
|
708
|
+
if (entry.action) return escapeHtml(String(entry.action));
|
|
709
|
+
return escapeHtml(JSON.stringify(entry).substring(0, 200));
|
|
415
710
|
}
|
|
416
711
|
|
|
417
712
|
function getLogType(entry) {
|
|
418
713
|
if (!entry || typeof entry !== 'object') return 'unknown';
|
|
419
|
-
if (entry.method && entry.url) return 'network';
|
|
714
|
+
if (entry.request || entry.response || (entry.method && entry.url)) return 'network';
|
|
420
715
|
if (entry.level && entry.data !== undefined) return 'console';
|
|
421
|
-
if (entry.path) return 'navigation';
|
|
422
|
-
if (entry.event) return 'track';
|
|
716
|
+
if (entry.from || entry.to || entry.path) return 'navigation';
|
|
717
|
+
if (entry.eventName || entry.event) return 'track';
|
|
718
|
+
if (entry.prevState !== undefined || entry.nextState !== undefined || entry.storeName) return 'zustand';
|
|
423
719
|
if (entry.action) return 'zustand';
|
|
424
720
|
return 'unknown';
|
|
425
721
|
}
|
|
@@ -452,37 +748,245 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
452
748
|
return logType + '-fallback-' + index + '-' + toKeyPart(stringifyForKey(entry));
|
|
453
749
|
}
|
|
454
750
|
|
|
751
|
+
function parseJsonString(value) {
|
|
752
|
+
if (typeof value !== 'string') return value;
|
|
753
|
+
var trimmed = value.trim();
|
|
754
|
+
if (!trimmed || !/^[\[{]/.test(trimmed)) return value;
|
|
755
|
+
try {
|
|
756
|
+
return JSON.parse(trimmed);
|
|
757
|
+
} catch {
|
|
758
|
+
return value;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function formatInlineValue(value) {
|
|
763
|
+
var parsed = parseJsonString(value);
|
|
764
|
+
if (parsed === null || parsed === undefined) return String(parsed);
|
|
765
|
+
if (typeof parsed === 'string') return parsed;
|
|
766
|
+
if (typeof parsed === 'number' || typeof parsed === 'boolean') return String(parsed);
|
|
767
|
+
try {
|
|
768
|
+
return JSON.stringify(parsed);
|
|
769
|
+
} catch {
|
|
770
|
+
return String(parsed);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function highlightJson(json) {
|
|
775
|
+
var str = typeof json === 'string' ? json : JSON.stringify(json, null, 2);
|
|
776
|
+
var escaped = escapeHtml(str);
|
|
777
|
+
return escaped
|
|
778
|
+
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
|
|
779
|
+
.replace(/: "((?:[^"\\]|\\.)*)"/g, ': <span class="json-string">"$1"</span>')
|
|
780
|
+
.replace(/: (\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
|
|
781
|
+
.replace(/: (true|false)/g, ': <span class="json-bool">$1</span>')
|
|
782
|
+
.replace(/: (null)/g, ': <span class="json-null">$1</span>');
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function renderValue(value) {
|
|
786
|
+
var parsed = parseJsonString(value);
|
|
787
|
+
if (parsed === null || parsed === undefined) {
|
|
788
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
789
|
+
}
|
|
790
|
+
if (typeof parsed === 'string' || typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
791
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
792
|
+
}
|
|
793
|
+
return '<pre class="json-block">' + highlightJson(parsed) + '</pre>';
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function renderRows(rows) {
|
|
797
|
+
var html = '<table class="detail-table"><tbody>';
|
|
798
|
+
rows.forEach(function(row) {
|
|
799
|
+
if (row[1] === undefined || row[1] === null || row[1] === '') return;
|
|
800
|
+
html += '<tr><th>' + escapeHtml(row[0]) + '</th><td>' + renderValue(row[1]) + '</td></tr>';
|
|
801
|
+
});
|
|
802
|
+
html += '</tbody></table>';
|
|
803
|
+
return html;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function renderSection(title, content, dataForCopy) {
|
|
807
|
+
var copyAttr = dataForCopy !== undefined
|
|
808
|
+
? ' data-copy="' + escapeHtml(typeof dataForCopy === 'string' ? dataForCopy : JSON.stringify(dataForCopy)) + '"'
|
|
809
|
+
: '';
|
|
810
|
+
return '<div class="detail-section"><div class="detail-section-header">' +
|
|
811
|
+
'<span class="detail-section-title">' + escapeHtml(title) + '</span>' +
|
|
812
|
+
'<button class="detail-section-copy" onclick="event.stopPropagation();copySectionData(this)" title="Copy section"' + copyAttr + '>⎘</button>' +
|
|
813
|
+
'</div><div class="detail-section-body">' + content + '</div></div>';
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function renderObjectSection(title, value) {
|
|
817
|
+
var object = readObject(value);
|
|
818
|
+
if (!object) return '';
|
|
819
|
+
return renderSection(title, renderValue(object), object);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function renderConsoleDetails(entry) {
|
|
823
|
+
var messages = Array.isArray(entry.data) ? entry.data : [entry.data];
|
|
824
|
+
return renderSection('Console', renderRows([
|
|
825
|
+
['Level', entry.level],
|
|
826
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
827
|
+
])) + renderSection('Messages',
|
|
828
|
+
'<div class="value-list">' + messages.map(renderValue).join('') + '</div>',
|
|
829
|
+
entry.data);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function renderNetworkDetails(entry) {
|
|
833
|
+
var request = readObject(entry.request) || entry;
|
|
834
|
+
var response = readObject(entry.response);
|
|
835
|
+
var method = (request.method || 'GET').toUpperCase();
|
|
836
|
+
var methodClass = 'method-' + method.toLowerCase();
|
|
837
|
+
|
|
838
|
+
var hero = '<div class="network-hero"><span class="method-badge ' + methodClass + '">' + escapeHtml(method) + '</span>' +
|
|
839
|
+
'<span class="network-url">' + escapeHtml(request.url || '-') + '</span></div>';
|
|
840
|
+
|
|
841
|
+
var reqRows = [
|
|
842
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
843
|
+
['Duration', entry.duration !== undefined ? entry.duration + 'ms' : ''],
|
|
844
|
+
];
|
|
845
|
+
if (request.headers) reqRows.push(['Headers', request.headers]);
|
|
846
|
+
if (request.body) reqRows.push(['Body', request.body]);
|
|
847
|
+
|
|
848
|
+
var html = renderSection('Request', hero + renderRows(reqRows), request);
|
|
849
|
+
|
|
850
|
+
if (response) {
|
|
851
|
+
var resRows = [
|
|
852
|
+
['Status', response.status !== undefined ? response.status + (response.statusText ? ' ' + response.statusText : '') : ''],
|
|
853
|
+
['Success', response.success !== undefined ? String(response.success) : ''],
|
|
854
|
+
];
|
|
855
|
+
if (response.headers) resRows.push(['Headers', response.headers]);
|
|
856
|
+
if (response.data) resRows.push(['Data', response.data]);
|
|
857
|
+
html += renderSection('Response', renderRows(resRows), response);
|
|
858
|
+
}
|
|
859
|
+
if (entry.error) {
|
|
860
|
+
html += renderSection('Error', renderValue(entry.error), entry.error);
|
|
861
|
+
}
|
|
862
|
+
return html;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function renderNavigationDetails(entry) {
|
|
866
|
+
var hero = '<div class="nav-hero">';
|
|
867
|
+
if (entry.from || entry.path) hero += '<span class="nav-from">' + escapeHtml(entry.from || entry.path || '-') + '</span>';
|
|
868
|
+
if (entry.to) hero += '<span class="nav-arrow">→</span><span class="nav-to">' + escapeHtml(entry.to) + '</span>';
|
|
869
|
+
hero += '</div>';
|
|
870
|
+
return renderSection('Navigation', hero + renderRows([
|
|
871
|
+
['Action', entry.action],
|
|
872
|
+
['Duration', entry.duration !== undefined ? entry.duration + 'ms' : ''],
|
|
873
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
874
|
+
]));
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function renderTrackDetails(entry) {
|
|
878
|
+
return renderSection('Event', renderRows([
|
|
879
|
+
['Name', entry.eventName || entry.event],
|
|
880
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
881
|
+
])) + renderSection('Payload', renderValue(entry), entry);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function renderStateDetails(entry) {
|
|
885
|
+
return renderSection('State', renderRows([
|
|
886
|
+
['Store', entry.storeName],
|
|
887
|
+
['Action', entry.action],
|
|
888
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
889
|
+
])) +
|
|
890
|
+
(entry.prevState !== undefined ? renderSection('Prev', renderValueCompact(entry.prevState), entry.prevState) : '') +
|
|
891
|
+
(entry.nextState !== undefined ? renderSection('Next', renderValueCompact(entry.nextState), entry.nextState) : '');
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function renderValueCompact(value) {
|
|
895
|
+
var parsed = parseJsonString(value);
|
|
896
|
+
if (parsed === null || parsed === undefined) {
|
|
897
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
898
|
+
}
|
|
899
|
+
if (typeof parsed === 'string' || typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
900
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
901
|
+
}
|
|
902
|
+
return '<pre class="json-block json-compact">' + highlightJson(parsed) + '</pre>';
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function renderLogDetails(entry, type) {
|
|
906
|
+
var logType = type || getLogType(entry);
|
|
907
|
+
if (!entry || typeof entry !== 'object') {
|
|
908
|
+
return renderSection('Value', renderValue(entry));
|
|
909
|
+
}
|
|
910
|
+
if (logType === 'network') return renderNetworkDetails(entry);
|
|
911
|
+
if (logType === 'console') return renderConsoleDetails(entry);
|
|
912
|
+
if (logType === 'navigation') return renderNavigationDetails(entry);
|
|
913
|
+
if (logType === 'track') return renderTrackDetails(entry);
|
|
914
|
+
if (logType === 'zustand') return renderStateDetails(entry);
|
|
915
|
+
return renderObjectSection(labelForType(logType), entry) || renderSection('Raw', renderValue(entry));
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// --- Search ---
|
|
919
|
+
|
|
920
|
+
function matchSearch(text) {
|
|
921
|
+
if (!searchTerm) return text;
|
|
922
|
+
var escaped = escapeHtml(text);
|
|
923
|
+
var re = new RegExp('(' + escapeRegex(searchTerm) + ')', 'gi');
|
|
924
|
+
return escaped.replace(re, '<mark>$1</mark>');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function escapeRegex(s) {
|
|
928
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function entryMatchesSearch(entry) {
|
|
932
|
+
if (!searchTerm) return true;
|
|
933
|
+
var term = searchTerm.toLowerCase();
|
|
934
|
+
var text = summarize(entry);
|
|
935
|
+
// Remove HTML for matching
|
|
936
|
+
var div = document.createElement('div');
|
|
937
|
+
div.innerHTML = text;
|
|
938
|
+
return div.textContent.toLowerCase().indexOf(term) >= 0;
|
|
939
|
+
}
|
|
940
|
+
|
|
455
941
|
// --- Views ---
|
|
456
942
|
|
|
457
943
|
function renderList() {
|
|
458
|
-
|
|
944
|
+
currentDevice = null;
|
|
459
945
|
expandedRows = {};
|
|
946
|
+
searchTerm = '';
|
|
947
|
+
focusedIndex = -1;
|
|
460
948
|
pulseDot.style.background = 'var(--text3)';
|
|
461
949
|
pulseDot.style.boxShadow = 'none';
|
|
462
950
|
statusEl.textContent = 'fetching...';
|
|
463
|
-
api('/
|
|
951
|
+
api('/devices').then(function(data) {
|
|
464
952
|
statusEl.textContent = '';
|
|
465
953
|
pulseDot.style.background = 'var(--cyan)';
|
|
466
954
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
467
|
-
var
|
|
468
|
-
if (!
|
|
955
|
+
var devices = data.devices || [];
|
|
956
|
+
if (!devices.length) {
|
|
469
957
|
app.innerHTML =
|
|
470
958
|
'<div class="empty">' +
|
|
471
959
|
'<div class="empty-icon">_</div>' +
|
|
472
|
-
'<p>No
|
|
960
|
+
'<p>No device logs received yet.</p>' +
|
|
473
961
|
'<p style="margin-top:12px;font-size:13px">POST a report to <code>/report</code> to see data here.</p>' +
|
|
474
|
-
'</div>'
|
|
962
|
+
'</div>' +
|
|
963
|
+
renderCurlPanel('Curl quick read', [
|
|
964
|
+
curlCommand('/health'),
|
|
965
|
+
curlCommand('/devices'),
|
|
966
|
+
curlCommand('/devices/latest'),
|
|
967
|
+
]);
|
|
475
968
|
return;
|
|
476
969
|
}
|
|
477
|
-
var html = '<div class="section-title">
|
|
478
|
-
html += '
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
970
|
+
var html = '<div class="section-title">Devices <span style="color:var(--text3);font-weight:400">(' + devices.length + ')</span></div>';
|
|
971
|
+
html += renderCurlPanel('Curl quick read', [
|
|
972
|
+
curlCommand('/health'),
|
|
973
|
+
curlCommand('/devices'),
|
|
974
|
+
curlCommand('/devices/latest'),
|
|
975
|
+
]);
|
|
976
|
+
html += '<div class="device-grid">';
|
|
977
|
+
devices.forEach(function(deviceLog, i) {
|
|
978
|
+
var lc = deviceLog.logCount || {};
|
|
979
|
+
var deviceText = formatDevice(deviceLog.device);
|
|
980
|
+
var ipText = formatIp(deviceLog.source);
|
|
981
|
+
html += '<div class="device-card" data-device-id="' + escapeHtml(deviceLog.deviceId) + '" style="animation-delay:' + (i * 40) + 'ms">';
|
|
982
|
+
html += '<div><div class="device-title">' + escapeHtml(deviceText) + '</div>';
|
|
983
|
+
html += '<div class="device-subtitle">IP ' + escapeHtml(ipText) + '</div></div>';
|
|
984
|
+
html += '<div class="device-meta-group">';
|
|
985
|
+
html += '<div class="device-meta-line"><strong>Device</strong>' + escapeHtml(deviceLog.deviceId) + '</div>';
|
|
986
|
+
html += '<div class="device-meta-line"><strong>Last seen</strong>' + formatTime(deviceLog.lastSeenAt || deviceLog.receivedAt) + '</div>';
|
|
987
|
+
html += '</div>';
|
|
988
|
+
html += '<div class="device-tags">' + renderDeviceTags(lc) + '</div>';
|
|
989
|
+
html += '<div class="device-arrow">›</div>';
|
|
486
990
|
html += '</div>';
|
|
487
991
|
});
|
|
488
992
|
html += '</div>';
|
|
@@ -495,14 +999,19 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
495
999
|
});
|
|
496
1000
|
}
|
|
497
1001
|
|
|
498
|
-
function renderDetail(
|
|
1002
|
+
function renderDetail(deviceId) {
|
|
499
1003
|
expandedRows = {};
|
|
1004
|
+
searchTerm = '';
|
|
1005
|
+
focusedIndex = -1;
|
|
1006
|
+
currentPage = 1;
|
|
1007
|
+
window._currentFilterType = '';
|
|
1008
|
+
window._failedOnly = false;
|
|
500
1009
|
statusEl.textContent = 'loading...';
|
|
501
|
-
api('/
|
|
1010
|
+
api('/devices/' + encodeURIComponent(deviceId)).then(function(data) {
|
|
502
1011
|
statusEl.textContent = '';
|
|
503
1012
|
pulseDot.style.background = 'var(--cyan)';
|
|
504
1013
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
505
|
-
|
|
1014
|
+
currentDevice = data;
|
|
506
1015
|
var report = data.report || {};
|
|
507
1016
|
var logs = report.logs || {};
|
|
508
1017
|
var logTypes = Object.keys(logs);
|
|
@@ -510,19 +1019,22 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
510
1019
|
var html = '';
|
|
511
1020
|
|
|
512
1021
|
// Back link
|
|
513
|
-
html += '<a href="#" class="back-link"
|
|
1022
|
+
html += '<a href="#" class="back-link">';
|
|
514
1023
|
html += '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>';
|
|
515
|
-
html += 'All
|
|
1024
|
+
html += 'All devices</a>';
|
|
516
1025
|
|
|
517
|
-
//
|
|
1026
|
+
// Device header card
|
|
518
1027
|
html += '<div class="detail-header">';
|
|
519
|
-
html += '<div class="detail-id">' + escapeHtml(data.
|
|
1028
|
+
html += '<div class="detail-id">' + escapeHtml(data.deviceId) + '</div>';
|
|
520
1029
|
html += '<div class="detail-meta">';
|
|
521
|
-
html += '<span class="detail-meta-item"><strong>
|
|
1030
|
+
html += '<span class="detail-meta-item"><strong>Last seen</strong> ' + formatTime(data.lastSeenAt || data.receivedAt) + '</span>';
|
|
522
1031
|
var totalLogs = Object.values(data.logCount || {}).reduce(function(a, b) { return a + b; }, 0);
|
|
523
1032
|
html += '<span class="detail-meta-item" data-type="Entries"><strong>Entries</strong> ' + totalLogs + '</span>';
|
|
1033
|
+
if (data.source && data.source.ip) {
|
|
1034
|
+
html += '<span class="detail-meta-item"><strong>IP</strong> ' + escapeHtml(data.source.ip) + '</span>';
|
|
1035
|
+
}
|
|
524
1036
|
Object.entries(data.logCount || {}).forEach(function(e) {
|
|
525
|
-
html += '<span class="detail-meta-item" data-type="' + e[0] + '"><strong>' + e[0] + '</strong> ' + e[1] + '</span>';
|
|
1037
|
+
html += '<span class="detail-meta-item" data-type="' + e[0] + '"><strong>' + escapeHtml(labelForType(e[0])) + '</strong> ' + e[1] + '</span>';
|
|
526
1038
|
});
|
|
527
1039
|
html += '</div>';
|
|
528
1040
|
|
|
@@ -543,45 +1055,70 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
543
1055
|
|
|
544
1056
|
html += '</div>';
|
|
545
1057
|
|
|
1058
|
+
html += renderCurlPanel('Curl this device', [
|
|
1059
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId)),
|
|
1060
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?limit=200'),
|
|
1061
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?type=network&failedOnly=true&limit=50'),
|
|
1062
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?type=console&limit=200'),
|
|
1063
|
+
]);
|
|
1064
|
+
|
|
546
1065
|
// Tabs
|
|
547
1066
|
html += '<div class="tabs">';
|
|
548
1067
|
html += '<button class="tab active" data-type="" onclick="filterType(this,\'\')">All<span class="count">' + totalLogs + '</span></button>';
|
|
549
1068
|
logTypes.forEach(function(t) {
|
|
550
1069
|
var count = logs[t] ? logs[t].length : 0;
|
|
551
|
-
html += '<button class="tab" data-type="' + t + '" onclick="filterType(this,\'' + t + '\')">' + escapeHtml(t) + '<span class="count">' + count + '</span></button>';
|
|
1070
|
+
html += '<button class="tab" data-type="' + t + '" onclick="filterType(this,\'' + t + '\')">' + escapeHtml(labelForType(t)) + '<span class="count">' + count + '</span></button>';
|
|
552
1071
|
});
|
|
553
1072
|
html += '</div>';
|
|
554
1073
|
|
|
555
|
-
// Toolbar
|
|
1074
|
+
// Toolbar with search
|
|
556
1075
|
html += '<div class="toolbar">';
|
|
1076
|
+
html += '<div class="search-wrap">';
|
|
1077
|
+
html += '<span class="search-icon">⚲</span>';
|
|
1078
|
+
html += '<input class="search-input" id="searchInput" type="text" placeholder="Search logs..." autocomplete="off">';
|
|
1079
|
+
html += '<button class="search-clear" id="searchClear" onclick="clearSearch()">×</button>';
|
|
1080
|
+
html += '</div>';
|
|
557
1081
|
html += '<label>Failed only <div class="toggle" id="failedToggle" onclick="toggleFailed()"></div></label>';
|
|
558
|
-
html += '<
|
|
1082
|
+
html += '<button class="live-notice" id="liveNotice" onclick="showLiveUpdates()">0 new logs</button>';
|
|
1083
|
+
html += '<div class="pager" id="pagerTop"></div>';
|
|
559
1084
|
html += '</div>';
|
|
560
1085
|
|
|
561
1086
|
// Log container
|
|
562
1087
|
html += '<div id="logsContainer"></div>';
|
|
1088
|
+
html += '<div class="pager pager-bottom" id="pagerBottom"></div>';
|
|
563
1089
|
|
|
564
1090
|
// Actions
|
|
565
1091
|
html += '<div class="actions">';
|
|
566
1092
|
html += '<button class="btn" onclick="copyJSON()">';
|
|
567
1093
|
html += '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
568
1094
|
html += 'Copy JSON</button>';
|
|
1095
|
+
html += '<span style="flex:1"></span>';
|
|
1096
|
+
html += '<span style="font-size:10px;color:var(--text3);font-family:var(--font-mono)"><span class="kbd">/</span> search <span class="kbd">j</span><span class="kbd">k</span> navigate <span class="kbd">Enter</span> expand <span class="kbd">Esc</span> back</span>';
|
|
569
1097
|
html += '</div>';
|
|
570
1098
|
|
|
571
1099
|
app.innerHTML = html;
|
|
572
|
-
|
|
573
|
-
|
|
1100
|
+
|
|
1101
|
+
// Wire up search
|
|
1102
|
+
var searchInput = document.getElementById('searchInput');
|
|
1103
|
+
searchInput.addEventListener('input', function() {
|
|
1104
|
+
searchTerm = this.value.trim();
|
|
1105
|
+
var clearBtn = document.getElementById('searchClear');
|
|
1106
|
+
if (clearBtn) clearBtn.classList.toggle('visible', searchTerm.length > 0);
|
|
1107
|
+
applyFilters();
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
renderLogs(logs, '', false);
|
|
574
1111
|
}).catch(function(err) {
|
|
575
1112
|
statusEl.textContent = '';
|
|
576
1113
|
app.innerHTML = '<div class="empty" style="color:var(--red)">Failed to load: ' + escapeHtml(err.message) + '</div>';
|
|
577
1114
|
});
|
|
578
1115
|
}
|
|
579
1116
|
|
|
580
|
-
function
|
|
1117
|
+
function collectLogEntries(logs, type, failedOnly) {
|
|
581
1118
|
var entries = [];
|
|
582
1119
|
if (type && logs[type]) {
|
|
583
1120
|
entries = Array.isArray(logs[type])
|
|
584
|
-
? logs[type].map(function(entry) { return { type: type, entry: entry }; })
|
|
1121
|
+
? logs[type].map(function(entry, index) { return { type: type, entry: entry, order: index }; })
|
|
585
1122
|
: [];
|
|
586
1123
|
} else {
|
|
587
1124
|
Object.entries(logs).forEach(function(logGroup) {
|
|
@@ -589,7 +1126,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
589
1126
|
var value = logGroup[1];
|
|
590
1127
|
if (Array.isArray(value)) {
|
|
591
1128
|
value.forEach(function(entry) {
|
|
592
|
-
entries.push({ type: logType, entry: entry });
|
|
1129
|
+
entries.push({ type: logType, entry: entry, order: entries.length });
|
|
593
1130
|
});
|
|
594
1131
|
}
|
|
595
1132
|
});
|
|
@@ -599,28 +1136,88 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
599
1136
|
entries = entries.filter(function(item) { return isFailedEntry(item.entry); });
|
|
600
1137
|
}
|
|
601
1138
|
|
|
602
|
-
|
|
1139
|
+
// Search filter
|
|
1140
|
+
if (searchTerm) {
|
|
1141
|
+
entries = entries.filter(function(item) { return entryMatchesSearch(item.entry); });
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
entries = entries
|
|
1145
|
+
.slice()
|
|
1146
|
+
.sort(function(a, b) {
|
|
1147
|
+
var byTime = readTimestamp(b.entry) - readTimestamp(a.entry);
|
|
1148
|
+
return byTime || b.order - a.order;
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
return entries;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function renderPagination(total, page, pageSize) {
|
|
1155
|
+
var totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
1156
|
+
var start = total === 0 ? 0 : ((page - 1) * pageSize) + 1;
|
|
1157
|
+
var end = Math.min(page * pageSize, total);
|
|
1158
|
+
var html = '<span class="pager-info">' + start + '-' + end + ' / ' + total + ' · ' + pageSize + ' per page</span>';
|
|
1159
|
+
html += '<button class="page-btn" onclick="goToPage(' + (page - 1) + ')"' + (page <= 1 ? ' disabled' : '') + '>Prev</button>';
|
|
1160
|
+
html += '<span class="pager-info">' + page + ' / ' + totalPages + '</span>';
|
|
1161
|
+
html += '<button class="page-btn" onclick="goToPage(' + (page + 1) + ')"' + (page >= totalPages ? ' disabled' : '') + '>Next</button>';
|
|
1162
|
+
|
|
1163
|
+
['pagerTop', 'pagerBottom'].forEach(function(id) {
|
|
1164
|
+
var el = document.getElementById(id);
|
|
1165
|
+
if (el) el.innerHTML = html;
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function renderLogEntryHtml(entry, type, rowId, index, isExpanded) {
|
|
1170
|
+
var lt = type || getLogType(entry);
|
|
1171
|
+
var typeClass = toKeyPart(lt);
|
|
1172
|
+
var ts = readTimestamp(entry);
|
|
1173
|
+
var html = '<div class="log-entry' + (isExpanded ? ' expanded' : '') + '" id="entry-' + rowId + '" data-index="' + index + '" data-sort="' + ts + '">';
|
|
1174
|
+
html += '<div class="log-row" onclick="toggleRow(\'' + rowId + '\')">';
|
|
1175
|
+
html += '<div class="log-type log-type-' + typeClass + '">' + escapeHtml(labelForType(lt)) + '</div>';
|
|
1176
|
+
html += '<div class="log-summary-col">';
|
|
1177
|
+
html += '<div class="log-summary">' + matchSearch(summarize(entry)) + '</div>';
|
|
1178
|
+
if (ts) {
|
|
1179
|
+
html += '<div class="log-timestamp">' + formatTimeShort(new Date(ts).toISOString()) + '</div>';
|
|
1180
|
+
}
|
|
1181
|
+
html += '</div>';
|
|
1182
|
+
html += '<div class="log-status">' + statusBadge(entry) + '</div>';
|
|
1183
|
+
html += '<div class="log-copy" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')"><button class="copy-btn" title="Copy entry JSON">⎘</button></div>';
|
|
1184
|
+
html += '<div class="log-expand">' + (isExpanded ? '▶' : '▶') + '</div>';
|
|
1185
|
+
html += '</div>';
|
|
1186
|
+
html += '<div class="log-detail' + (isExpanded ? '' : '') + '" id="detail-' + rowId + '">';
|
|
1187
|
+
html += '<div class="log-detail-inner"><div class="detail-sections">';
|
|
1188
|
+
html += renderLogDetails(entry, lt);
|
|
1189
|
+
html += '<div class="entry-footer">';
|
|
1190
|
+
html += '<button class="btn btn-sm" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')">⎘ Copy JSON</button>';
|
|
1191
|
+
html += '</div>';
|
|
1192
|
+
html += '</div></div></div>';
|
|
1193
|
+
html += '</div>';
|
|
1194
|
+
return html;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function renderLogs(logs, type, failedOnly) {
|
|
1198
|
+
var allEntries = collectLogEntries(logs, type, failedOnly);
|
|
1199
|
+
var totalPages = Math.max(1, Math.ceil(allEntries.length / PAGE_SIZE));
|
|
1200
|
+
if (currentPage > totalPages) currentPage = totalPages;
|
|
1201
|
+
if (currentPage < 1) currentPage = 1;
|
|
1202
|
+
|
|
1203
|
+
var startIndex = (currentPage - 1) * PAGE_SIZE;
|
|
1204
|
+
var entries = allEntries.slice(startIndex, startIndex + PAGE_SIZE);
|
|
1205
|
+
renderPagination(allEntries.length, currentPage, PAGE_SIZE);
|
|
603
1206
|
|
|
604
1207
|
var container = document.getElementById('logsContainer');
|
|
605
1208
|
if (!entries.length) {
|
|
606
|
-
container.innerHTML = '<div class="empty" style="padding:40px"><div class="empty-icon" style="font-size:24px">0</div><p style="font-size:13px">
|
|
1209
|
+
container.innerHTML = '<div class="empty" style="padding:40px"><div class="empty-icon" style="font-size:24px">0</div><p style="font-size:13px">' +
|
|
1210
|
+
(searchTerm ? 'No logs match "' + escapeHtml(searchTerm) + '"' : 'No logs match filters.') +
|
|
1211
|
+
'</p></div>';
|
|
607
1212
|
return;
|
|
608
1213
|
}
|
|
609
1214
|
|
|
1215
|
+
focusedIndex = -1;
|
|
610
1216
|
var html = '<div class="log-list">';
|
|
611
1217
|
entries.forEach(function(item, i) {
|
|
612
|
-
var
|
|
613
|
-
var rowId = getLogEntryKey(entry, item.type,
|
|
614
|
-
|
|
615
|
-
var typeClass = toKeyPart(lt);
|
|
616
|
-
var isExpanded = expandedRows[rowId];
|
|
617
|
-
html += '<div class="log-entry' + (isExpanded ? ' expanded' : '') + '" id="entry-' + rowId + '" onclick="toggleRow(\'' + rowId + '\')">';
|
|
618
|
-
html += '<div class="log-type log-type-' + typeClass + '">' + escapeHtml(lt.substring(0,4)) + '</div>';
|
|
619
|
-
html += '<div class="log-summary">' + summarize(entry) + '</div>';
|
|
620
|
-
html += '<div class="log-status">' + statusBadge(entry) + '</div>';
|
|
621
|
-
html += '<div class="log-expand">' + (isExpanded ? '▼' : '▶') + '</div>';
|
|
622
|
-
html += '<div class="log-json' + (isExpanded ? ' open' : '') + '" id="json-' + rowId + '"><pre>' + escapeHtml(JSON.stringify(entry, null, 2)) + '</pre></div>';
|
|
623
|
-
html += '</div>';
|
|
1218
|
+
var absoluteIndex = startIndex + i;
|
|
1219
|
+
var rowId = getLogEntryKey(item.entry, item.type, absoluteIndex);
|
|
1220
|
+
html += renderLogEntryHtml(item.entry, item.type, rowId, i, expandedRows[rowId]);
|
|
624
1221
|
});
|
|
625
1222
|
html += '</div>';
|
|
626
1223
|
container.innerHTML = html;
|
|
@@ -632,30 +1229,52 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
632
1229
|
return {
|
|
633
1230
|
type: window._currentFilterType || '',
|
|
634
1231
|
failedOnly: window._failedOnly || false,
|
|
635
|
-
|
|
1232
|
+
page: currentPage,
|
|
636
1233
|
};
|
|
637
1234
|
}
|
|
638
1235
|
|
|
639
1236
|
function rerenderVisibleLogs() {
|
|
640
|
-
if (!
|
|
641
|
-
var logs =
|
|
1237
|
+
if (!currentDevice) return;
|
|
1238
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
642
1239
|
var options = readVisibleLogOptions();
|
|
643
|
-
renderLogs(logs, options.type, options.
|
|
1240
|
+
renderLogs(logs, options.type, options.failedOnly);
|
|
1241
|
+
updateLiveNotice();
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function updateCurrentPagination() {
|
|
1245
|
+
if (!currentDevice) return;
|
|
1246
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
1247
|
+
var options = readVisibleLogOptions();
|
|
1248
|
+
var total = collectLogEntries(logs, options.type, options.failedOnly).length;
|
|
1249
|
+
renderPagination(total, currentPage, PAGE_SIZE);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function updateLiveNotice() {
|
|
1253
|
+
var notice = document.getElementById('liveNotice');
|
|
1254
|
+
if (!notice) return;
|
|
1255
|
+
if (pendingLiveCount > 0) {
|
|
1256
|
+
notice.textContent = pendingLiveCount + ' new logs';
|
|
1257
|
+
notice.classList.add('visible');
|
|
1258
|
+
} else {
|
|
1259
|
+
notice.classList.remove('visible');
|
|
1260
|
+
}
|
|
644
1261
|
}
|
|
645
1262
|
|
|
646
|
-
function
|
|
647
|
-
if (!
|
|
1263
|
+
function refreshCurrentDevice() {
|
|
1264
|
+
if (!currentDevice) {
|
|
648
1265
|
renderList();
|
|
649
1266
|
return Promise.resolve();
|
|
650
1267
|
}
|
|
651
1268
|
|
|
652
1269
|
statusEl.textContent = 'refreshing...';
|
|
653
|
-
return api('/
|
|
1270
|
+
return api('/devices/' + encodeURIComponent(currentDevice.deviceId)).then(function(data) {
|
|
654
1271
|
statusEl.textContent = '';
|
|
655
1272
|
if (!data) return;
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1273
|
+
currentDevice.report = data.report;
|
|
1274
|
+
currentDevice.logCount = data.logCount;
|
|
1275
|
+
currentDevice.receivedAt = data.receivedAt;
|
|
1276
|
+
currentDevice.lastSeenAt = data.lastSeenAt;
|
|
1277
|
+
pendingLiveCount = 0;
|
|
659
1278
|
rerenderVisibleLogs();
|
|
660
1279
|
updateTabCounts();
|
|
661
1280
|
}).catch(function(err) {
|
|
@@ -665,7 +1284,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
665
1284
|
}
|
|
666
1285
|
|
|
667
1286
|
function refreshCurrentView() {
|
|
668
|
-
return
|
|
1287
|
+
return refreshCurrentDevice();
|
|
669
1288
|
}
|
|
670
1289
|
|
|
671
1290
|
window.refresh = function() { refreshCurrentView(); };
|
|
@@ -685,31 +1304,61 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
685
1304
|
};
|
|
686
1305
|
|
|
687
1306
|
window.applyFilters = function() {
|
|
688
|
-
if (!
|
|
1307
|
+
if (!currentDevice) return;
|
|
689
1308
|
expandedRows = {};
|
|
1309
|
+
currentPage = 1;
|
|
1310
|
+
pendingLiveCount = 0;
|
|
690
1311
|
rerenderVisibleLogs();
|
|
691
1312
|
};
|
|
692
1313
|
|
|
1314
|
+
window.goToPage = function(page) {
|
|
1315
|
+
if (!currentDevice) return;
|
|
1316
|
+
currentPage = Math.max(1, Math.floor(page));
|
|
1317
|
+
expandedRows = {};
|
|
1318
|
+
if (currentPage === 1) pendingLiveCount = 0;
|
|
1319
|
+
rerenderVisibleLogs();
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
window.showLiveUpdates = function() {
|
|
1323
|
+
if (!currentDevice) return;
|
|
1324
|
+
currentPage = 1;
|
|
1325
|
+
expandedRows = {};
|
|
1326
|
+
pendingLiveCount = 0;
|
|
1327
|
+
rerenderVisibleLogs();
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
window.clearSearch = function() {
|
|
1331
|
+
var input = document.getElementById('searchInput');
|
|
1332
|
+
if (input) input.value = '';
|
|
1333
|
+
searchTerm = '';
|
|
1334
|
+
var clearBtn = document.getElementById('searchClear');
|
|
1335
|
+
if (clearBtn) clearBtn.classList.remove('visible');
|
|
1336
|
+
applyFilters();
|
|
1337
|
+
};
|
|
1338
|
+
|
|
693
1339
|
window.toggleRow = function(rowId) {
|
|
694
1340
|
var entry = document.getElementById('entry-' + rowId);
|
|
695
|
-
var
|
|
696
|
-
|
|
697
|
-
if (!entry || !json) return;
|
|
1341
|
+
var detail = document.getElementById('detail-' + rowId);
|
|
1342
|
+
if (!entry || !detail) return;
|
|
698
1343
|
expandedRows[rowId] = !expandedRows[rowId];
|
|
699
1344
|
if (expandedRows[rowId]) {
|
|
700
1345
|
entry.classList.add('expanded');
|
|
701
|
-
json.classList.add('open');
|
|
702
|
-
if (expand) expand.innerHTML = '▼';
|
|
703
1346
|
} else {
|
|
704
1347
|
entry.classList.remove('expanded');
|
|
705
|
-
json.classList.remove('open');
|
|
706
|
-
if (expand) expand.innerHTML = '▶';
|
|
707
1348
|
}
|
|
708
1349
|
};
|
|
709
1350
|
|
|
1351
|
+
window.toggleCurl = function(id) {
|
|
1352
|
+
var body = document.getElementById(id);
|
|
1353
|
+
var toggle = document.getElementById('toggle-' + id);
|
|
1354
|
+
if (!body) return;
|
|
1355
|
+
body.classList.toggle('open');
|
|
1356
|
+
if (toggle) toggle.classList.toggle('open');
|
|
1357
|
+
};
|
|
1358
|
+
|
|
710
1359
|
window.copyJSON = function() {
|
|
711
|
-
if (!
|
|
712
|
-
var text = JSON.stringify(
|
|
1360
|
+
if (!currentDevice) return;
|
|
1361
|
+
var text = JSON.stringify(currentDevice.report, null, 2);
|
|
713
1362
|
navigator.clipboard.writeText(text).then(function() {
|
|
714
1363
|
showToast('Copied to clipboard');
|
|
715
1364
|
}).catch(function() {
|
|
@@ -725,6 +1374,132 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
725
1374
|
});
|
|
726
1375
|
};
|
|
727
1376
|
|
|
1377
|
+
window.copyEntryJSON = function(rowId) {
|
|
1378
|
+
// Find the entry data from current device logs
|
|
1379
|
+
if (!currentDevice) return;
|
|
1380
|
+
var el = document.getElementById('entry-' + rowId);
|
|
1381
|
+
if (!el) return;
|
|
1382
|
+
|
|
1383
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
1384
|
+
var options = readVisibleLogOptions();
|
|
1385
|
+
var startIndex = (options.page - 1) * PAGE_SIZE;
|
|
1386
|
+
var entries = collectLogEntries(logs, options.type, options.failedOnly)
|
|
1387
|
+
.slice(startIndex, startIndex + PAGE_SIZE);
|
|
1388
|
+
|
|
1389
|
+
var idx = parseInt(el.getAttribute('data-index'), 10);
|
|
1390
|
+
if (isNaN(idx) || idx < 0 || idx >= entries.length) return;
|
|
1391
|
+
var text = JSON.stringify(entries[idx].entry, null, 2);
|
|
1392
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
1393
|
+
showToast('Entry copied');
|
|
1394
|
+
}).catch(function() {
|
|
1395
|
+
var ta = document.createElement('textarea');
|
|
1396
|
+
ta.value = text;
|
|
1397
|
+
ta.style.position = 'fixed';
|
|
1398
|
+
ta.style.opacity = '0';
|
|
1399
|
+
document.body.appendChild(ta);
|
|
1400
|
+
ta.select();
|
|
1401
|
+
document.execCommand('copy');
|
|
1402
|
+
document.body.removeChild(ta);
|
|
1403
|
+
showToast('Entry copied');
|
|
1404
|
+
});
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
window.copySectionData = function(btn) {
|
|
1408
|
+
var data = btn.getAttribute('data-copy');
|
|
1409
|
+
if (!data) return;
|
|
1410
|
+
navigator.clipboard.writeText(data).then(function() {
|
|
1411
|
+
showToast('Section copied');
|
|
1412
|
+
}).catch(function() {
|
|
1413
|
+
var ta = document.createElement('textarea');
|
|
1414
|
+
ta.value = data;
|
|
1415
|
+
ta.style.position = 'fixed';
|
|
1416
|
+
ta.style.opacity = '0';
|
|
1417
|
+
document.body.appendChild(ta);
|
|
1418
|
+
ta.select();
|
|
1419
|
+
document.execCommand('copy');
|
|
1420
|
+
document.body.removeChild(ta);
|
|
1421
|
+
showToast('Section copied');
|
|
1422
|
+
});
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
// --- Keyboard shortcuts ---
|
|
1426
|
+
|
|
1427
|
+
document.addEventListener('keydown', function(e) {
|
|
1428
|
+
// Don't intercept when typing in inputs
|
|
1429
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
|
1430
|
+
if (e.key === 'Escape') {
|
|
1431
|
+
e.target.blur();
|
|
1432
|
+
if (e.target.id === 'searchInput' && searchTerm) {
|
|
1433
|
+
clearSearch();
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (e.key === '/') {
|
|
1440
|
+
e.preventDefault();
|
|
1441
|
+
var searchInput = document.getElementById('searchInput');
|
|
1442
|
+
if (searchInput) searchInput.focus();
|
|
1443
|
+
} else if (e.key === 'Escape') {
|
|
1444
|
+
if (currentDevice) {
|
|
1445
|
+
location.hash = '';
|
|
1446
|
+
}
|
|
1447
|
+
} else if (e.key === 'j' || e.key === 'k') {
|
|
1448
|
+
e.preventDefault();
|
|
1449
|
+
navigateEntries(e.key === 'j' ? 1 : -1);
|
|
1450
|
+
} else if (e.key === 'Enter') {
|
|
1451
|
+
e.preventDefault();
|
|
1452
|
+
toggleFocusedEntry();
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
function navigateEntries(direction) {
|
|
1457
|
+
var list = document.querySelector('.log-list');
|
|
1458
|
+
if (!list) return;
|
|
1459
|
+
var items = list.querySelectorAll('.log-entry');
|
|
1460
|
+
if (!items.length) return;
|
|
1461
|
+
|
|
1462
|
+
// Remove previous focus
|
|
1463
|
+
if (focusedIndex >= 0 && focusedIndex < items.length) {
|
|
1464
|
+
items[focusedIndex].classList.remove('focused');
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
focusedIndex += direction;
|
|
1468
|
+
if (focusedIndex < 0) focusedIndex = 0;
|
|
1469
|
+
if (focusedIndex >= items.length) focusedIndex = items.length - 1;
|
|
1470
|
+
|
|
1471
|
+
items[focusedIndex].classList.add('focused');
|
|
1472
|
+
items[focusedIndex].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function toggleFocusedEntry() {
|
|
1476
|
+
if (focusedIndex < 0) return;
|
|
1477
|
+
var list = document.querySelector('.log-list');
|
|
1478
|
+
if (!list) return;
|
|
1479
|
+
var items = list.querySelectorAll('.log-entry');
|
|
1480
|
+
if (focusedIndex >= items.length) return;
|
|
1481
|
+
items[focusedIndex].click();
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function openDeviceDetail(deviceId) {
|
|
1485
|
+
if (!deviceId) return;
|
|
1486
|
+
var nextHash = 'device/' + encodeURIComponent(deviceId);
|
|
1487
|
+
if (location.hash === '#' + nextHash) {
|
|
1488
|
+
route();
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
location.hash = nextHash;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
document.addEventListener('click', function(e) {
|
|
1495
|
+
var target = e.target;
|
|
1496
|
+
if (!target || !target.closest) return;
|
|
1497
|
+
var card = target.closest('.device-card[data-device-id]');
|
|
1498
|
+
if (!card) return;
|
|
1499
|
+
e.preventDefault();
|
|
1500
|
+
openDeviceDetail(card.getAttribute('data-device-id'));
|
|
1501
|
+
});
|
|
1502
|
+
|
|
728
1503
|
// --- Routing ---
|
|
729
1504
|
|
|
730
1505
|
window._currentFilterType = '';
|
|
@@ -732,8 +1507,8 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
732
1507
|
|
|
733
1508
|
function route() {
|
|
734
1509
|
var hash = location.hash.replace('#', '');
|
|
735
|
-
if (hash.startsWith('
|
|
736
|
-
renderDetail(decodeURIComponent(hash.substring(
|
|
1510
|
+
if (hash.startsWith('device/')) {
|
|
1511
|
+
renderDetail(decodeURIComponent(hash.substring(7)));
|
|
737
1512
|
} else {
|
|
738
1513
|
renderList();
|
|
739
1514
|
}
|
|
@@ -741,117 +1516,135 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
741
1516
|
|
|
742
1517
|
// --- Incremental DOM updates ---
|
|
743
1518
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
function renderSessionTags(logCount) {
|
|
753
|
-
var html = '';
|
|
754
|
-
Object.entries(logCount || {}).forEach(function(e) {
|
|
755
|
-
var type = String(e[0]);
|
|
756
|
-
html += '<span class="tag tag-' + toKeyPart(type) + '">' + escapeHtml(type.substring(0,3)) + ' ' + escapeHtml(String(e[1])) + '</span>';
|
|
757
|
-
});
|
|
758
|
-
return html;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function appendSessionCard(payload) {
|
|
762
|
-
var grid = document.querySelector('.session-grid');
|
|
763
|
-
if (!grid) { renderList(); return; }
|
|
764
|
-
|
|
765
|
-
var sid = payload.sessionId;
|
|
766
|
-
var existing = grid.querySelector('[data-sid="' + CSS.escape(sid) + '"]');
|
|
767
|
-
if (existing) {
|
|
768
|
-
var tags = existing.querySelector('.session-tags');
|
|
769
|
-
if (tags) tags.innerHTML = renderSessionTags(payload.logCount || {});
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
1519
|
+
function isFailedEntry(e) {
|
|
1520
|
+
return e && typeof e === 'object' && (
|
|
1521
|
+
Boolean(e.error) ||
|
|
1522
|
+
e.level === 'error' ||
|
|
1523
|
+
(e.response && (e.response.success === false || e.response.status >= 400))
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
772
1526
|
|
|
773
|
-
|
|
774
|
-
var
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
html
|
|
780
|
-
|
|
781
|
-
html += '<div class="session-arrow">›</div>';
|
|
782
|
-
card.innerHTML = html;
|
|
783
|
-
grid.prepend(card);
|
|
1527
|
+
function renderDeviceTags(logCount) {
|
|
1528
|
+
var html = '';
|
|
1529
|
+
Object.entries(logCount || {}).forEach(function(e) {
|
|
1530
|
+
var type = String(e[0]);
|
|
1531
|
+
html += '<span class="tag tag-' + toKeyPart(type) + '">' + escapeHtml(labelForType(type)) + ' ' + escapeHtml(String(e[1])) + '</span>';
|
|
1532
|
+
});
|
|
1533
|
+
return html;
|
|
1534
|
+
}
|
|
784
1535
|
|
|
785
|
-
|
|
786
|
-
var
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
titleEl.innerHTML = 'Sessions <span style="color:var(--text3);font-weight:400">(' + count + ')</span>';
|
|
1536
|
+
function findDeviceCard(deviceId) {
|
|
1537
|
+
var cards = document.querySelectorAll('.device-card[data-device-id]');
|
|
1538
|
+
for (var i = 0; i < cards.length; i += 1) {
|
|
1539
|
+
if (cards[i].getAttribute('data-device-id') === deviceId) return cards[i];
|
|
790
1540
|
}
|
|
1541
|
+
return null;
|
|
791
1542
|
}
|
|
792
1543
|
|
|
793
|
-
function
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1544
|
+
function updateVisibleIndexes(list) {
|
|
1545
|
+
Array.from(list.querySelectorAll('.log-entry')).forEach(function(entry, index) {
|
|
1546
|
+
entry.setAttribute('data-index', index);
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
function visibleDeltaItems(deltaLogs) {
|
|
1551
|
+
var options = readVisibleLogOptions();
|
|
1552
|
+
var items = [];
|
|
1553
|
+
Object.entries(deltaLogs || {}).forEach(function(logGroup) {
|
|
1554
|
+
var type = logGroup[0];
|
|
1555
|
+
var entries = logGroup[1];
|
|
1556
|
+
if (!Array.isArray(entries)) return;
|
|
1557
|
+
entries.forEach(function(entry) {
|
|
1558
|
+
if (options.type && type !== options.type) return;
|
|
1559
|
+
if (options.failedOnly && !isFailedEntry(entry)) return;
|
|
1560
|
+
if (searchTerm && !entryMatchesSearch(entry)) return;
|
|
1561
|
+
items.push({ type: type, entry: entry, order: items.length });
|
|
1562
|
+
});
|
|
1563
|
+
});
|
|
1564
|
+
return items.sort(function(a, b) {
|
|
1565
|
+
var byTime = readTimestamp(b.entry) - readTimestamp(a.entry);
|
|
1566
|
+
return byTime || b.order - a.order;
|
|
1567
|
+
});
|
|
802
1568
|
}
|
|
803
1569
|
|
|
804
1570
|
function appendDeltaLogs(deltaLogs) {
|
|
805
|
-
var
|
|
806
|
-
|
|
1571
|
+
var items = visibleDeltaItems(deltaLogs);
|
|
1572
|
+
updateCurrentPagination();
|
|
1573
|
+
if (!items.length) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
807
1576
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1577
|
+
if (currentPage !== 1) {
|
|
1578
|
+
pendingLiveCount += items.length;
|
|
1579
|
+
updateLiveNotice();
|
|
811
1580
|
return;
|
|
812
1581
|
}
|
|
813
1582
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
var limit = document.getElementById('limitInput') ? parseInt(document.getElementById('limitInput').value, 10) || 50 : 50;
|
|
817
|
-
var allNewEntries = [];
|
|
1583
|
+
pendingLiveCount = 0;
|
|
1584
|
+
updateLiveNotice();
|
|
818
1585
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (type && t !== type) return;
|
|
826
|
-
allNewEntries.push({ type: t, entry: e });
|
|
827
|
-
});
|
|
828
|
-
});
|
|
1586
|
+
var container = document.getElementById('logsContainer');
|
|
1587
|
+
var list = container ? container.querySelector('.log-list') : null;
|
|
1588
|
+
if (!list) {
|
|
1589
|
+
rerenderVisibleLogs();
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
829
1592
|
|
|
830
|
-
var
|
|
831
|
-
|
|
832
|
-
var
|
|
833
|
-
|
|
834
|
-
var div = document.createElement('div');
|
|
835
|
-
div.className = 'log-entry';
|
|
836
|
-
div.id = 'entry-' + rowId;
|
|
837
|
-
div.setAttribute('onclick', "toggleRow('" + rowId + "')");
|
|
838
|
-
div.innerHTML = buildLogEntryHtml(entry, rowId, item.type);
|
|
839
|
-
list.appendChild(div);
|
|
1593
|
+
var html = '';
|
|
1594
|
+
items.forEach(function(item) {
|
|
1595
|
+
var rowId = getLogEntryKey(item.entry, item.type, 'live-' + (liveSequence += 1));
|
|
1596
|
+
html += renderLogEntryHtml(item.entry, item.type, rowId, 0, false);
|
|
840
1597
|
});
|
|
1598
|
+
list.insertAdjacentHTML('afterbegin', html);
|
|
841
1599
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1600
|
+
while (list.querySelectorAll('.log-entry').length > PAGE_SIZE) {
|
|
1601
|
+
var last = list.lastElementChild;
|
|
1602
|
+
if (!last || last.classList.contains('expanded')) break;
|
|
1603
|
+
list.removeChild(last);
|
|
1604
|
+
}
|
|
1605
|
+
updateVisibleIndexes(list);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function appendDeviceCard(payload) {
|
|
1609
|
+
var grid = document.querySelector('.device-grid');
|
|
1610
|
+
if (!grid) { renderList(); return; }
|
|
1611
|
+
|
|
1612
|
+
var deviceId = payload.deviceId;
|
|
1613
|
+
if (!deviceId) return;
|
|
1614
|
+
var existing = findDeviceCard(deviceId);
|
|
1615
|
+
if (existing) {
|
|
1616
|
+
var tags = existing.querySelector('.device-tags');
|
|
1617
|
+
if (tags) tags.innerHTML = renderDeviceTags(payload.logCount || {});
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
var lc = payload.logCount || {};
|
|
1622
|
+
var deviceText = formatDevice(payload.device);
|
|
1623
|
+
var ipText = formatIp(payload.source);
|
|
1624
|
+
var card = document.createElement('div');
|
|
1625
|
+
card.className = 'device-card';
|
|
1626
|
+
card.setAttribute('data-device-id', deviceId);
|
|
1627
|
+
var html = '<div><div class="device-title">' + escapeHtml(deviceText) + '</div>';
|
|
1628
|
+
html += '<div class="device-subtitle">IP ' + escapeHtml(ipText) + '</div></div>';
|
|
1629
|
+
html += '<div class="device-meta-group">';
|
|
1630
|
+
html += '<div class="device-meta-line"><strong>Device</strong>' + escapeHtml(deviceId) + '</div>';
|
|
1631
|
+
html += '<div class="device-meta-line"><strong>Last seen</strong> just now</div>';
|
|
1632
|
+
html += '</div>';
|
|
1633
|
+
html += '<div class="device-tags">' + renderDeviceTags(lc) + '</div>';
|
|
1634
|
+
html += '<div class="device-arrow">›</div>';
|
|
1635
|
+
card.innerHTML = html;
|
|
1636
|
+
grid.prepend(card);
|
|
1637
|
+
|
|
1638
|
+
var titleEl = document.querySelector('.section-title');
|
|
1639
|
+
if (titleEl) {
|
|
1640
|
+
var count = grid.querySelectorAll('.device-card').length;
|
|
1641
|
+
titleEl.innerHTML = 'Devices <span style="color:var(--text3);font-weight:400">(' + count + ')</span>';
|
|
849
1642
|
}
|
|
850
1643
|
}
|
|
851
1644
|
|
|
852
1645
|
function updateTabCounts() {
|
|
853
|
-
if (!
|
|
854
|
-
var logs =
|
|
1646
|
+
if (!currentDevice) return;
|
|
1647
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
855
1648
|
var totalLogs = Object.values(logs).reduce(function(a, v) { return a + (Array.isArray(v) ? v.length : 0); }, 0);
|
|
856
1649
|
|
|
857
1650
|
document.querySelectorAll('.tab').forEach(function(tab) {
|
|
@@ -877,7 +1670,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
877
1670
|
|
|
878
1671
|
function connectSSE() {
|
|
879
1672
|
if (eventSource) { try { eventSource.close(); } catch {} }
|
|
880
|
-
|
|
1673
|
+
eventSource = new EventSource(withAuth('/events'));
|
|
881
1674
|
|
|
882
1675
|
eventSource.addEventListener('logs', function(e) {
|
|
883
1676
|
try {
|
|
@@ -886,17 +1679,15 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
886
1679
|
pulseDot.style.background = 'var(--cyan)';
|
|
887
1680
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
888
1681
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
appendSessionCard(payload);
|
|
1682
|
+
if (!currentDevice) {
|
|
1683
|
+
appendDeviceCard(payload);
|
|
892
1684
|
return;
|
|
893
1685
|
}
|
|
894
1686
|
|
|
895
|
-
|
|
896
|
-
if (payload.sessionId === currentSession.sessionId) {
|
|
1687
|
+
if (payload.deviceId === currentDevice.deviceId) {
|
|
897
1688
|
if (payload.type === 'delta' && payload.delta) {
|
|
898
1689
|
var deltaLogs = payload.delta.logs || {};
|
|
899
|
-
var report =
|
|
1690
|
+
var report = currentDevice.report || { version: 2, logs: {} };
|
|
900
1691
|
if (!report.logs) report.logs = {};
|
|
901
1692
|
|
|
902
1693
|
Object.entries(deltaLogs).forEach(function(entry) {
|
|
@@ -907,15 +1698,15 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
907
1698
|
report.logs[type] = report.logs[type].concat(entries);
|
|
908
1699
|
});
|
|
909
1700
|
|
|
910
|
-
|
|
911
|
-
if (payload.logCount)
|
|
1701
|
+
currentDevice.report = report;
|
|
1702
|
+
if (payload.logCount) currentDevice.logCount = payload.logCount;
|
|
912
1703
|
appendDeltaLogs(deltaLogs);
|
|
913
1704
|
updateTabCounts();
|
|
914
1705
|
} else if (payload.type === 'full') {
|
|
915
|
-
|
|
1706
|
+
refreshCurrentDevice();
|
|
916
1707
|
}
|
|
917
|
-
} else if (!location.hash.startsWith('
|
|
918
|
-
|
|
1708
|
+
} else if (!location.hash.startsWith('device/')) {
|
|
1709
|
+
appendDeviceCard(payload);
|
|
919
1710
|
}
|
|
920
1711
|
} catch {}
|
|
921
1712
|
});
|
|
@@ -930,6 +1721,18 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
930
1721
|
window.addEventListener('hashchange', route);
|
|
931
1722
|
connectSSE();
|
|
932
1723
|
route();
|
|
1724
|
+
|
|
1725
|
+
// Fetch LAN IPs from /health and display in header
|
|
1726
|
+
api('/health').then(function(data) {
|
|
1727
|
+
var ips = data && data.ips;
|
|
1728
|
+
if (Array.isArray(ips) && ips.length) {
|
|
1729
|
+
var ipEl = document.getElementById('ipHint');
|
|
1730
|
+
if (ipEl) {
|
|
1731
|
+
ipEl.textContent = 'LAN ' + ips.join(' / ');
|
|
1732
|
+
ipEl.style.display = '';
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}).catch(function() {});
|
|
933
1736
|
})();
|
|
934
1737
|
</script>
|
|
935
1738
|
</body>
|