react-native-debug-toolkit 3.0.0 → 3.1.2
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 +914 -188
- 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{
|
|
@@ -189,11 +198,66 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
189
198
|
}
|
|
190
199
|
.toggle.on::after{transform:translateX(14px)}
|
|
191
200
|
|
|
192
|
-
/*
|
|
201
|
+
/* Search */
|
|
202
|
+
.search-wrap{
|
|
203
|
+
flex:1;min-width:160px;position:relative;
|
|
204
|
+
}
|
|
205
|
+
.search-input{
|
|
206
|
+
width:100%;padding:5px 10px 5px 28px;
|
|
207
|
+
background:var(--surface);border:1px solid var(--border2);border-radius:4px;
|
|
208
|
+
color:var(--text);font-size:12px;font-family:var(--font-mono);
|
|
209
|
+
transition:border-color .15s;
|
|
210
|
+
}
|
|
211
|
+
.search-input::placeholder{color:var(--text3)}
|
|
212
|
+
.search-input:focus{outline:none;border-color:var(--cyan)}
|
|
213
|
+
.search-icon{
|
|
214
|
+
position:absolute;left:8px;top:50%;transform:translateY(-50%);
|
|
215
|
+
color:var(--text3);font-size:13px;pointer-events:none;
|
|
216
|
+
}
|
|
217
|
+
.search-clear{
|
|
218
|
+
position:absolute;right:6px;top:50%;transform:translateY(-50%);
|
|
219
|
+
background:none;border:none;color:var(--text3);cursor:pointer;
|
|
220
|
+
font-size:14px;padding:2px 4px;line-height:1;
|
|
221
|
+
display:none;
|
|
222
|
+
}
|
|
223
|
+
.search-clear.visible{display:block}
|
|
224
|
+
.search-clear:hover{color:var(--text)}
|
|
225
|
+
.kbd{
|
|
226
|
+
font-family:var(--font-mono);font-size:9px;color:var(--text3);
|
|
227
|
+
background:var(--surface2);border:1px solid var(--border2);
|
|
228
|
+
padding:1px 4px;border-radius:2px;letter-spacing:.02em;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Curl help - collapsible */
|
|
232
|
+
.curl-panel{
|
|
233
|
+
margin-bottom:18px;padding:12px 14px;background:var(--bg2);
|
|
234
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
235
|
+
}
|
|
236
|
+
.curl-header{
|
|
237
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
238
|
+
cursor:pointer;user-select:none;
|
|
239
|
+
}
|
|
240
|
+
.curl-title{
|
|
241
|
+
font-size:11px;font-family:var(--font-mono);font-weight:700;
|
|
242
|
+
color:var(--cyan);text-transform:uppercase;letter-spacing:.08em;
|
|
243
|
+
}
|
|
244
|
+
.curl-toggle{
|
|
245
|
+
font-size:10px;color:var(--text3);transition:transform .2s;
|
|
246
|
+
}
|
|
247
|
+
.curl-toggle.open{transform:rotate(90deg)}
|
|
248
|
+
.curl-body{display:none;margin-top:10px}
|
|
249
|
+
.curl-body.open{display:block}
|
|
250
|
+
.curl-list{display:flex;flex-direction:column;gap:6px}
|
|
251
|
+
.curl-list code{
|
|
252
|
+
display:block;padding:7px 9px;background:rgba(0,0,0,.18);
|
|
253
|
+
border:1px solid rgba(42,63,102,.65);border-radius:4px;
|
|
254
|
+
color:var(--text2);font-family:var(--font-mono);font-size:11px;
|
|
255
|
+
white-space:pre-wrap;word-break:break-word;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Log entries - redesigned */
|
|
193
259
|
.log-list{display:flex;flex-direction:column;gap:2px}
|
|
194
260
|
.log-entry{
|
|
195
|
-
display:grid;grid-template-columns:80px 1fr 90px 30px;
|
|
196
|
-
align-items:center;gap:0;
|
|
197
261
|
background:var(--surface);border:1px solid transparent;border-radius:var(--radius);
|
|
198
262
|
cursor:pointer;transition:all .15s;overflow:hidden;
|
|
199
263
|
animation:fadeSlideIn .3s ease-out both;
|
|
@@ -203,51 +267,174 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
203
267
|
background:var(--surface2);border-color:var(--border2);
|
|
204
268
|
box-shadow:0 2px 8px rgba(0,0,0,.2);
|
|
205
269
|
}
|
|
206
|
-
.log-entry.expanded{
|
|
270
|
+
.log-entry.expanded{
|
|
271
|
+
border-color:var(--cyan-mid);background:var(--surface2);
|
|
272
|
+
box-shadow:0 2px 12px rgba(0,0,0,.25);
|
|
273
|
+
}
|
|
274
|
+
.log-entry.focused{outline:1px solid var(--cyan);outline-offset:-1px}
|
|
275
|
+
|
|
276
|
+
.log-row{
|
|
277
|
+
display:grid;grid-template-columns:90px 1fr auto auto 28px;
|
|
278
|
+
align-items:center;gap:0;
|
|
279
|
+
padding:0 4px 0 0;min-height:46px;
|
|
280
|
+
}
|
|
207
281
|
.log-type{
|
|
208
282
|
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
209
283
|
text-transform:uppercase;letter-spacing:.06em;
|
|
210
|
-
padding:0
|
|
211
|
-
|
|
284
|
+
padding:0 12px;height:100%;display:flex;align-items:center;
|
|
285
|
+
justify-content:center;text-align:center;
|
|
212
286
|
}
|
|
213
287
|
.log-type-network{color:var(--cyan)}
|
|
214
288
|
.log-type-console{color:var(--green)}
|
|
215
|
-
.log-type-navigation{color
|
|
289
|
+
.log-type-navigation{color:var(--purple)}
|
|
216
290
|
.log-type-track{color:var(--orange)}
|
|
217
|
-
.log-type-zustand{color
|
|
291
|
+
.log-type-zustand{color:var(--pink)}
|
|
218
292
|
.log-type-unknown{color:var(--text3)}
|
|
293
|
+
|
|
294
|
+
.log-summary-col{
|
|
295
|
+
padding:8px 12px;min-width:0;overflow:hidden;
|
|
296
|
+
}
|
|
219
297
|
.log-summary{
|
|
220
|
-
padding:0 14px;height:42px;display:flex;align-items:center;
|
|
221
298
|
font-family:var(--font-mono);font-size:12px;color:var(--text);
|
|
222
|
-
|
|
299
|
+
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
|
|
300
|
+
overflow:hidden;line-height:1.5;word-break:break-all;
|
|
301
|
+
}
|
|
302
|
+
.log-timestamp{
|
|
303
|
+
font-family:var(--font-mono);font-size:10px;color:var(--text3);
|
|
304
|
+
margin-top:2px;
|
|
223
305
|
}
|
|
306
|
+
|
|
224
307
|
.log-status{
|
|
225
|
-
padding:0
|
|
308
|
+
padding:0 8px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
226
309
|
}
|
|
227
310
|
.badge{
|
|
228
311
|
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
229
|
-
padding:2px 8px;border-radius:3px;letter-spacing:.02em;
|
|
312
|
+
padding:2px 8px;border-radius:3px;letter-spacing:.02em;white-space:nowrap;
|
|
230
313
|
}
|
|
231
314
|
.badge-ok{background:var(--green-dim);color:var(--green)}
|
|
232
315
|
.badge-error{background:var(--red-dim);color:var(--red)}
|
|
233
316
|
.badge-warn{background:var(--amber-dim);color:var(--amber)}
|
|
234
317
|
.badge-info{background:var(--cyan-dim);color:var(--cyan)}
|
|
318
|
+
|
|
319
|
+
.log-copy{
|
|
320
|
+
padding:0 4px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
321
|
+
opacity:0;transition:opacity .15s;
|
|
322
|
+
}
|
|
323
|
+
.log-entry:hover .log-copy{opacity:1}
|
|
324
|
+
.copy-btn{
|
|
325
|
+
background:none;border:1px solid transparent;cursor:pointer;
|
|
326
|
+
color:var(--text3);font-size:13px;padding:2px 4px;border-radius:3px;
|
|
327
|
+
transition:all .15s;
|
|
328
|
+
}
|
|
329
|
+
.copy-btn:hover{color:var(--cyan);background:var(--cyan-dim);border-color:rgba(0,229,255,.15)}
|
|
330
|
+
|
|
235
331
|
.log-expand{
|
|
236
|
-
padding:0 8px;height:
|
|
237
|
-
color:var(--text3);font-size:10px;transition:color .15s;
|
|
332
|
+
padding:0 8px;height:100%;display:flex;align-items:center;justify-content:center;
|
|
333
|
+
color:var(--text3);font-size:10px;transition:transform .2s,color .15s;
|
|
238
334
|
}
|
|
239
335
|
.log-entry:hover .log-expand{color:var(--cyan)}
|
|
240
|
-
.log-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
336
|
+
.log-entry.expanded .log-expand{transform:rotate(90deg);color:var(--cyan)}
|
|
337
|
+
|
|
338
|
+
/* Expanded detail panel */
|
|
339
|
+
.log-detail{
|
|
340
|
+
display:none;border-top:1px solid var(--border);
|
|
341
|
+
animation:detailFadeIn .2s ease-out;
|
|
342
|
+
}
|
|
343
|
+
.log-entry.expanded .log-detail{display:block}
|
|
344
|
+
@keyframes detailFadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
|
|
345
|
+
|
|
346
|
+
.log-detail-inner{padding:16px 18px}
|
|
347
|
+
|
|
348
|
+
/* Detail sections */
|
|
349
|
+
.detail-sections{display:flex;flex-direction:column;gap:10px}
|
|
350
|
+
.detail-section{
|
|
351
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
352
|
+
background:rgba(8,12,22,.35);overflow:hidden;
|
|
353
|
+
}
|
|
354
|
+
.detail-section-header{
|
|
355
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
356
|
+
padding:7px 12px;border-bottom:1px solid var(--border);
|
|
357
|
+
background:rgba(0,229,255,.03);
|
|
358
|
+
}
|
|
359
|
+
.detail-section-title{
|
|
360
|
+
font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
|
|
361
|
+
color:var(--cyan);font-family:var(--font-mono);
|
|
362
|
+
}
|
|
363
|
+
.detail-section-copy{
|
|
364
|
+
background:none;border:none;cursor:pointer;
|
|
365
|
+
color:var(--text3);font-size:11px;padding:1px 4px;border-radius:2px;
|
|
366
|
+
transition:color .15s;
|
|
367
|
+
}
|
|
368
|
+
.detail-section-copy:hover{color:var(--cyan)}
|
|
369
|
+
.detail-section-body{padding:0}
|
|
370
|
+
|
|
371
|
+
/* Detail key-value table */
|
|
372
|
+
.detail-table{width:100%;border-collapse:collapse}
|
|
373
|
+
.detail-table tr{border-bottom:1px solid rgba(30,45,74,.5)}
|
|
374
|
+
.detail-table tr:last-child{border-bottom:none}
|
|
375
|
+
.detail-table th,.detail-table td{
|
|
376
|
+
padding:8px 12px;vertical-align:top;font-size:12px;
|
|
377
|
+
}
|
|
378
|
+
.detail-table th{
|
|
379
|
+
width:100px;color:var(--text3);font-family:var(--font-mono);font-weight:500;
|
|
380
|
+
text-align:left;white-space:nowrap;
|
|
381
|
+
}
|
|
382
|
+
.detail-table td{color:var(--text2);font-family:var(--font-mono);word-break:break-word;line-height:1.6}
|
|
383
|
+
|
|
384
|
+
/* JSON blocks */
|
|
385
|
+
.json-block{
|
|
386
|
+
margin:0;padding:10px 12px;font-family:var(--font-mono);font-size:11px;
|
|
387
|
+
line-height:1.7;color:var(--text2);white-space:pre-wrap;word-break:break-word;
|
|
388
|
+
max-height:320px;overflow:auto;background:rgba(0,0,0,.16);
|
|
245
389
|
}
|
|
246
|
-
.
|
|
247
|
-
.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
390
|
+
.json-block .json-key{color:var(--cyan)}
|
|
391
|
+
.json-block .json-string{color:var(--green)}
|
|
392
|
+
.json-block .json-number{color:var(--amber)}
|
|
393
|
+
.json-block .json-bool{color:var(--purple)}
|
|
394
|
+
.json-block .json-null{color:var(--text3)}
|
|
395
|
+
.json-compact{max-height:160px;font-size:10px;line-height:1.5;padding:8px 10px}
|
|
396
|
+
|
|
397
|
+
/* Value pills */
|
|
398
|
+
.value-list{display:flex;flex-direction:column;gap:6px;padding:10px 12px}
|
|
399
|
+
.value-pill{
|
|
400
|
+
display:block;padding:8px 10px;border:1px solid rgba(42,63,102,.6);
|
|
401
|
+
border-radius:4px;background:rgba(0,0,0,.1);font-family:var(--font-mono);
|
|
402
|
+
font-size:12px;color:var(--text2);word-break:break-word;line-height:1.6;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* Network detail hero */
|
|
406
|
+
.network-hero{
|
|
407
|
+
padding:12px 14px;border-bottom:1px solid rgba(30,45,74,.5);
|
|
408
|
+
display:flex;align-items:center;gap:10px;
|
|
409
|
+
}
|
|
410
|
+
.method-badge{
|
|
411
|
+
font-family:var(--font-mono);font-size:11px;font-weight:700;
|
|
412
|
+
padding:3px 10px;border-radius:3px;letter-spacing:.04em;
|
|
413
|
+
}
|
|
414
|
+
.method-get{background:var(--cyan-dim);color:var(--cyan)}
|
|
415
|
+
.method-post{background:var(--green-dim);color:var(--green)}
|
|
416
|
+
.method-put{background:var(--amber-dim);color:var(--amber)}
|
|
417
|
+
.method-patch{background:var(--amber-dim);color:var(--amber)}
|
|
418
|
+
.method-delete{background:var(--red-dim);color:var(--red)}
|
|
419
|
+
.network-url{
|
|
420
|
+
font-family:var(--font-mono);font-size:12px;color:var(--text);
|
|
421
|
+
word-break:break-all;line-height:1.5;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Navigation hero */
|
|
425
|
+
.nav-hero{
|
|
426
|
+
padding:12px 14px;border-bottom:1px solid rgba(30,45,74,.5);
|
|
427
|
+
display:flex;align-items:center;gap:8px;
|
|
428
|
+
font-family:var(--font-mono);font-size:12px;
|
|
429
|
+
}
|
|
430
|
+
.nav-from{color:var(--text3)}
|
|
431
|
+
.nav-arrow{color:var(--cyan);font-size:14px}
|
|
432
|
+
.nav-to{color:var(--text);font-weight:500}
|
|
433
|
+
|
|
434
|
+
/* Entry footer */
|
|
435
|
+
.entry-footer{
|
|
436
|
+
display:flex;align-items:center;gap:8px;
|
|
437
|
+
padding:10px 0 0;margin-top:10px;border-top:1px solid rgba(30,45,74,.5);
|
|
251
438
|
}
|
|
252
439
|
|
|
253
440
|
/* Actions bar */
|
|
@@ -302,13 +489,23 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
302
489
|
@keyframes livePulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
303
490
|
.live-badge-dot{width:6px;height:6px;border-radius:50%;background:var(--cyan)}
|
|
304
491
|
|
|
492
|
+
/* Search highlight */
|
|
493
|
+
mark{
|
|
494
|
+
background:rgba(0,229,255,.25);color:var(--text);
|
|
495
|
+
border-radius:2px;padding:0 1px;
|
|
496
|
+
}
|
|
497
|
+
|
|
305
498
|
/* Responsive */
|
|
306
499
|
@media(max-width:640px){
|
|
307
500
|
.container{padding:16px}
|
|
308
|
-
.
|
|
309
|
-
.log-
|
|
501
|
+
.device-card{grid-template-columns:1fr;gap:8px}
|
|
502
|
+
.log-row{grid-template-columns:72px 1fr auto 28px}
|
|
503
|
+
.log-copy{display:none}
|
|
504
|
+
.detail-table th{width:80px}
|
|
310
505
|
.detail-header{padding:14px 16px}
|
|
311
506
|
.tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
507
|
+
.toolbar{gap:8px}
|
|
508
|
+
.search-wrap{min-width:120px;flex-basis:100%}
|
|
312
509
|
}
|
|
313
510
|
</style>
|
|
314
511
|
</head>
|
|
@@ -320,6 +517,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
320
517
|
</div>
|
|
321
518
|
<div class="header-right">
|
|
322
519
|
<span class="header-meta" id="status"></span>
|
|
520
|
+
<span class="header-meta" id="ipHint" style="color:var(--text2);display:none"></span>
|
|
323
521
|
<button class="btn" onclick="refresh()">
|
|
324
522
|
<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
523
|
Refresh
|
|
@@ -336,28 +534,58 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
336
534
|
var app = document.getElementById('app');
|
|
337
535
|
var statusEl = document.getElementById('status');
|
|
338
536
|
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
|
-
|
|
537
|
+
var pulseDot = document.getElementById('pulseDot');
|
|
538
|
+
var currentDevice = null;
|
|
539
|
+
var expandedRows = {};
|
|
540
|
+
var authToken = null;
|
|
541
|
+
var searchTerm = '';
|
|
542
|
+
var focusedIndex = -1;
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
var params = new URLSearchParams(location.search);
|
|
546
|
+
authToken = params.get('token') || localStorage.getItem('debugToolkitToken');
|
|
547
|
+
if (authToken) localStorage.setItem('debugToolkitToken', authToken);
|
|
548
|
+
} catch {
|
|
549
|
+
authToken = null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function withAuth(path) {
|
|
553
|
+
if (!authToken) return path;
|
|
554
|
+
var join = path.indexOf('?') >= 0 ? '&' : '?';
|
|
555
|
+
return path + join + 'token=' + encodeURIComponent(authToken);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function api(path) {
|
|
559
|
+
return fetch(withAuth(path)).then(function(r) {
|
|
560
|
+
return r.json().then(function(body) {
|
|
561
|
+
if (!r.ok) throw new Error(body && body.error ? body.error : ('HTTP ' + r.status));
|
|
562
|
+
return body;
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function absoluteUrl(path) {
|
|
568
|
+
return location.origin + withAuth(path);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function curlCommand(path) {
|
|
572
|
+
return "curl '" + absoluteUrl(path).replace(/'/g, "'\\''") + "'";
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function renderCurlPanel(title, commands) {
|
|
576
|
+
var id = 'curl-' + Math.random().toString(36).slice(2,8);
|
|
577
|
+
var html = '<div class="curl-panel">';
|
|
578
|
+
html += '<div class="curl-header" onclick="toggleCurl(\'' + id + '\')">';
|
|
579
|
+
html += '<div class="curl-title">' + escapeHtml(title) + '</div>';
|
|
580
|
+
html += '<span class="curl-toggle" id="toggle-' + id + '">▶</span>';
|
|
581
|
+
html += '</div>';
|
|
582
|
+
html += '<div class="curl-body" id="' + id + '"><div class="curl-list">';
|
|
583
|
+
commands.forEach(function(command) {
|
|
584
|
+
html += '<code>' + escapeHtml(command) + '</code>';
|
|
585
|
+
});
|
|
586
|
+
html += '</div></div></div>';
|
|
587
|
+
return html;
|
|
588
|
+
}
|
|
361
589
|
|
|
362
590
|
function showToast(msg) {
|
|
363
591
|
toastEl.textContent = msg;
|
|
@@ -385,10 +613,52 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
385
613
|
if (!iso) return '';
|
|
386
614
|
try {
|
|
387
615
|
var d = new Date(iso);
|
|
388
|
-
return
|
|
616
|
+
var pad = function(n) { return String(n).padStart(2, '0'); };
|
|
617
|
+
return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + String(d.getMilliseconds()).padStart(3,'0').slice(0,2);
|
|
389
618
|
} catch { return iso; }
|
|
390
619
|
}
|
|
391
620
|
|
|
621
|
+
function readObject(value) {
|
|
622
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function readTimestamp(entry) {
|
|
626
|
+
if (!entry || typeof entry !== 'object') return 0;
|
|
627
|
+
var value = entry.timestamp || entry.time || entry.createdAt;
|
|
628
|
+
if (typeof value === 'number') return value;
|
|
629
|
+
if (typeof value === 'string') {
|
|
630
|
+
var parsed = Date.parse(value);
|
|
631
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
632
|
+
}
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function labelForType(type) {
|
|
637
|
+
var labels = {
|
|
638
|
+
network: 'Network',
|
|
639
|
+
console: 'Console',
|
|
640
|
+
navigation: 'Navigation',
|
|
641
|
+
track: 'Track',
|
|
642
|
+
zustand: 'State',
|
|
643
|
+
clipboard: 'Clipboard',
|
|
644
|
+
environment: 'Environment',
|
|
645
|
+
};
|
|
646
|
+
return labels[type] || (type ? type.charAt(0).toUpperCase() + type.slice(1) : 'Unknown');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function formatDevice(device) {
|
|
650
|
+
if (!device || typeof device !== 'object') return 'Unknown device';
|
|
651
|
+
var parts = [];
|
|
652
|
+
if (device.platform) parts.push(String(device.platform).toUpperCase());
|
|
653
|
+
if (device.model) parts.push(String(device.model));
|
|
654
|
+
if (device.osVersion) parts.push('OS ' + String(device.osVersion));
|
|
655
|
+
return parts.length ? parts.join(' / ') : 'Unknown device';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function formatIp(source) {
|
|
659
|
+
return source && source.ip ? String(source.ip) : 'unknown ip';
|
|
660
|
+
}
|
|
661
|
+
|
|
392
662
|
function statusBadge(entry) {
|
|
393
663
|
if (!entry || typeof entry !== 'object') return '<span class="badge badge-info">-</span>';
|
|
394
664
|
if (entry.error) return '<span class="badge badge-error">ERR</span>';
|
|
@@ -401,25 +671,33 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
401
671
|
}
|
|
402
672
|
if (entry.level === 'error') return '<span class="badge badge-error">ERR</span>';
|
|
403
673
|
if (entry.level === 'warn') return '<span class="badge badge-warn">WRN</span>';
|
|
404
|
-
return '
|
|
674
|
+
return '';
|
|
405
675
|
}
|
|
406
676
|
|
|
407
677
|
function summarize(entry) {
|
|
408
678
|
if (!entry || typeof entry !== 'object') return escapeHtml(String(entry));
|
|
679
|
+
if (entry.request && typeof entry.request === 'object') {
|
|
680
|
+
return escapeHtml((entry.request.method || 'GET') + ' ' + (entry.request.url || '-'));
|
|
681
|
+
}
|
|
409
682
|
if (entry.method && entry.url) return escapeHtml(entry.method + ' ' + entry.url);
|
|
410
|
-
if (entry.level && entry.data
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return escapeHtml(
|
|
683
|
+
if (entry.level && entry.data !== undefined) {
|
|
684
|
+
var data = Array.isArray(entry.data) ? entry.data.map(formatInlineValue).join(' ') : formatInlineValue(entry.data);
|
|
685
|
+
return escapeHtml(data.substring(0, 200));
|
|
686
|
+
}
|
|
687
|
+
if (entry.from || entry.to) return escapeHtml((entry.from || '-') + ' -> ' + (entry.to || '-'));
|
|
688
|
+
if (entry.eventName) return escapeHtml(String(entry.eventName));
|
|
689
|
+
if (entry.event) return escapeHtml(String(entry.event));
|
|
690
|
+
if (entry.action) return escapeHtml(String(entry.action));
|
|
691
|
+
return escapeHtml(JSON.stringify(entry).substring(0, 200));
|
|
415
692
|
}
|
|
416
693
|
|
|
417
694
|
function getLogType(entry) {
|
|
418
695
|
if (!entry || typeof entry !== 'object') return 'unknown';
|
|
419
|
-
if (entry.method && entry.url) return 'network';
|
|
696
|
+
if (entry.request || entry.response || (entry.method && entry.url)) return 'network';
|
|
420
697
|
if (entry.level && entry.data !== undefined) return 'console';
|
|
421
|
-
if (entry.path) return 'navigation';
|
|
422
|
-
if (entry.event) return 'track';
|
|
698
|
+
if (entry.from || entry.to || entry.path) return 'navigation';
|
|
699
|
+
if (entry.eventName || entry.event) return 'track';
|
|
700
|
+
if (entry.prevState !== undefined || entry.nextState !== undefined || entry.storeName) return 'zustand';
|
|
423
701
|
if (entry.action) return 'zustand';
|
|
424
702
|
return 'unknown';
|
|
425
703
|
}
|
|
@@ -452,37 +730,245 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
452
730
|
return logType + '-fallback-' + index + '-' + toKeyPart(stringifyForKey(entry));
|
|
453
731
|
}
|
|
454
732
|
|
|
733
|
+
function parseJsonString(value) {
|
|
734
|
+
if (typeof value !== 'string') return value;
|
|
735
|
+
var trimmed = value.trim();
|
|
736
|
+
if (!trimmed || !/^[\[{]/.test(trimmed)) return value;
|
|
737
|
+
try {
|
|
738
|
+
return JSON.parse(trimmed);
|
|
739
|
+
} catch {
|
|
740
|
+
return value;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function formatInlineValue(value) {
|
|
745
|
+
var parsed = parseJsonString(value);
|
|
746
|
+
if (parsed === null || parsed === undefined) return String(parsed);
|
|
747
|
+
if (typeof parsed === 'string') return parsed;
|
|
748
|
+
if (typeof parsed === 'number' || typeof parsed === 'boolean') return String(parsed);
|
|
749
|
+
try {
|
|
750
|
+
return JSON.stringify(parsed);
|
|
751
|
+
} catch {
|
|
752
|
+
return String(parsed);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function highlightJson(json) {
|
|
757
|
+
var str = typeof json === 'string' ? json : JSON.stringify(json, null, 2);
|
|
758
|
+
var escaped = escapeHtml(str);
|
|
759
|
+
return escaped
|
|
760
|
+
.replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
|
|
761
|
+
.replace(/: "((?:[^"\\]|\\.)*)"/g, ': <span class="json-string">"$1"</span>')
|
|
762
|
+
.replace(/: (\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
|
|
763
|
+
.replace(/: (true|false)/g, ': <span class="json-bool">$1</span>')
|
|
764
|
+
.replace(/: (null)/g, ': <span class="json-null">$1</span>');
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function renderValue(value) {
|
|
768
|
+
var parsed = parseJsonString(value);
|
|
769
|
+
if (parsed === null || parsed === undefined) {
|
|
770
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
771
|
+
}
|
|
772
|
+
if (typeof parsed === 'string' || typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
773
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
774
|
+
}
|
|
775
|
+
return '<pre class="json-block">' + highlightJson(parsed) + '</pre>';
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function renderRows(rows) {
|
|
779
|
+
var html = '<table class="detail-table"><tbody>';
|
|
780
|
+
rows.forEach(function(row) {
|
|
781
|
+
if (row[1] === undefined || row[1] === null || row[1] === '') return;
|
|
782
|
+
html += '<tr><th>' + escapeHtml(row[0]) + '</th><td>' + renderValue(row[1]) + '</td></tr>';
|
|
783
|
+
});
|
|
784
|
+
html += '</tbody></table>';
|
|
785
|
+
return html;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function renderSection(title, content, dataForCopy) {
|
|
789
|
+
var copyAttr = dataForCopy !== undefined
|
|
790
|
+
? ' data-copy="' + escapeHtml(typeof dataForCopy === 'string' ? dataForCopy : JSON.stringify(dataForCopy)) + '"'
|
|
791
|
+
: '';
|
|
792
|
+
return '<div class="detail-section"><div class="detail-section-header">' +
|
|
793
|
+
'<span class="detail-section-title">' + escapeHtml(title) + '</span>' +
|
|
794
|
+
'<button class="detail-section-copy" onclick="event.stopPropagation();copySectionData(this)" title="Copy section"' + copyAttr + '>⎘</button>' +
|
|
795
|
+
'</div><div class="detail-section-body">' + content + '</div></div>';
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function renderObjectSection(title, value) {
|
|
799
|
+
var object = readObject(value);
|
|
800
|
+
if (!object) return '';
|
|
801
|
+
return renderSection(title, renderValue(object), object);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function renderConsoleDetails(entry) {
|
|
805
|
+
var messages = Array.isArray(entry.data) ? entry.data : [entry.data];
|
|
806
|
+
return renderSection('Console', renderRows([
|
|
807
|
+
['Level', entry.level],
|
|
808
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
809
|
+
])) + renderSection('Messages',
|
|
810
|
+
'<div class="value-list">' + messages.map(renderValue).join('') + '</div>',
|
|
811
|
+
entry.data);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function renderNetworkDetails(entry) {
|
|
815
|
+
var request = readObject(entry.request) || entry;
|
|
816
|
+
var response = readObject(entry.response);
|
|
817
|
+
var method = (request.method || 'GET').toUpperCase();
|
|
818
|
+
var methodClass = 'method-' + method.toLowerCase();
|
|
819
|
+
|
|
820
|
+
var hero = '<div class="network-hero"><span class="method-badge ' + methodClass + '">' + escapeHtml(method) + '</span>' +
|
|
821
|
+
'<span class="network-url">' + escapeHtml(request.url || '-') + '</span></div>';
|
|
822
|
+
|
|
823
|
+
var reqRows = [
|
|
824
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
825
|
+
['Duration', entry.duration !== undefined ? entry.duration + 'ms' : ''],
|
|
826
|
+
];
|
|
827
|
+
if (request.headers) reqRows.push(['Headers', request.headers]);
|
|
828
|
+
if (request.body) reqRows.push(['Body', request.body]);
|
|
829
|
+
|
|
830
|
+
var html = renderSection('Request', hero + renderRows(reqRows), request);
|
|
831
|
+
|
|
832
|
+
if (response) {
|
|
833
|
+
var resRows = [
|
|
834
|
+
['Status', response.status !== undefined ? response.status + (response.statusText ? ' ' + response.statusText : '') : ''],
|
|
835
|
+
['Success', response.success !== undefined ? String(response.success) : ''],
|
|
836
|
+
];
|
|
837
|
+
if (response.headers) resRows.push(['Headers', response.headers]);
|
|
838
|
+
if (response.data) resRows.push(['Data', response.data]);
|
|
839
|
+
html += renderSection('Response', renderRows(resRows), response);
|
|
840
|
+
}
|
|
841
|
+
if (entry.error) {
|
|
842
|
+
html += renderSection('Error', renderValue(entry.error), entry.error);
|
|
843
|
+
}
|
|
844
|
+
return html;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function renderNavigationDetails(entry) {
|
|
848
|
+
var hero = '<div class="nav-hero">';
|
|
849
|
+
if (entry.from || entry.path) hero += '<span class="nav-from">' + escapeHtml(entry.from || entry.path || '-') + '</span>';
|
|
850
|
+
if (entry.to) hero += '<span class="nav-arrow">→</span><span class="nav-to">' + escapeHtml(entry.to) + '</span>';
|
|
851
|
+
hero += '</div>';
|
|
852
|
+
return renderSection('Navigation', hero + renderRows([
|
|
853
|
+
['Action', entry.action],
|
|
854
|
+
['Duration', entry.duration !== undefined ? entry.duration + 'ms' : ''],
|
|
855
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
856
|
+
]));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function renderTrackDetails(entry) {
|
|
860
|
+
return renderSection('Event', renderRows([
|
|
861
|
+
['Name', entry.eventName || entry.event],
|
|
862
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
863
|
+
])) + renderSection('Payload', renderValue(entry), entry);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function renderStateDetails(entry) {
|
|
867
|
+
return renderSection('State', renderRows([
|
|
868
|
+
['Store', entry.storeName],
|
|
869
|
+
['Action', entry.action],
|
|
870
|
+
['Time', entry.timestamp ? formatTime(new Date(entry.timestamp).toISOString()) : ''],
|
|
871
|
+
])) +
|
|
872
|
+
(entry.prevState !== undefined ? renderSection('Prev', renderValueCompact(entry.prevState), entry.prevState) : '') +
|
|
873
|
+
(entry.nextState !== undefined ? renderSection('Next', renderValueCompact(entry.nextState), entry.nextState) : '');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function renderValueCompact(value) {
|
|
877
|
+
var parsed = parseJsonString(value);
|
|
878
|
+
if (parsed === null || parsed === undefined) {
|
|
879
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
880
|
+
}
|
|
881
|
+
if (typeof parsed === 'string' || typeof parsed === 'number' || typeof parsed === 'boolean') {
|
|
882
|
+
return '<span class="value-pill">' + escapeHtml(String(parsed)) + '</span>';
|
|
883
|
+
}
|
|
884
|
+
return '<pre class="json-block json-compact">' + highlightJson(parsed) + '</pre>';
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function renderLogDetails(entry, type) {
|
|
888
|
+
var logType = type || getLogType(entry);
|
|
889
|
+
if (!entry || typeof entry !== 'object') {
|
|
890
|
+
return renderSection('Value', renderValue(entry));
|
|
891
|
+
}
|
|
892
|
+
if (logType === 'network') return renderNetworkDetails(entry);
|
|
893
|
+
if (logType === 'console') return renderConsoleDetails(entry);
|
|
894
|
+
if (logType === 'navigation') return renderNavigationDetails(entry);
|
|
895
|
+
if (logType === 'track') return renderTrackDetails(entry);
|
|
896
|
+
if (logType === 'zustand') return renderStateDetails(entry);
|
|
897
|
+
return renderObjectSection(labelForType(logType), entry) || renderSection('Raw', renderValue(entry));
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// --- Search ---
|
|
901
|
+
|
|
902
|
+
function matchSearch(text) {
|
|
903
|
+
if (!searchTerm) return text;
|
|
904
|
+
var escaped = escapeHtml(text);
|
|
905
|
+
var re = new RegExp('(' + escapeRegex(searchTerm) + ')', 'gi');
|
|
906
|
+
return escaped.replace(re, '<mark>$1</mark>');
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function escapeRegex(s) {
|
|
910
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function entryMatchesSearch(entry) {
|
|
914
|
+
if (!searchTerm) return true;
|
|
915
|
+
var term = searchTerm.toLowerCase();
|
|
916
|
+
var text = summarize(entry);
|
|
917
|
+
// Remove HTML for matching
|
|
918
|
+
var div = document.createElement('div');
|
|
919
|
+
div.innerHTML = text;
|
|
920
|
+
return div.textContent.toLowerCase().indexOf(term) >= 0;
|
|
921
|
+
}
|
|
922
|
+
|
|
455
923
|
// --- Views ---
|
|
456
924
|
|
|
457
925
|
function renderList() {
|
|
458
|
-
|
|
926
|
+
currentDevice = null;
|
|
459
927
|
expandedRows = {};
|
|
928
|
+
searchTerm = '';
|
|
929
|
+
focusedIndex = -1;
|
|
460
930
|
pulseDot.style.background = 'var(--text3)';
|
|
461
931
|
pulseDot.style.boxShadow = 'none';
|
|
462
932
|
statusEl.textContent = 'fetching...';
|
|
463
|
-
api('/
|
|
933
|
+
api('/devices').then(function(data) {
|
|
464
934
|
statusEl.textContent = '';
|
|
465
935
|
pulseDot.style.background = 'var(--cyan)';
|
|
466
936
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
467
|
-
var
|
|
468
|
-
if (!
|
|
937
|
+
var devices = data.devices || [];
|
|
938
|
+
if (!devices.length) {
|
|
469
939
|
app.innerHTML =
|
|
470
940
|
'<div class="empty">' +
|
|
471
941
|
'<div class="empty-icon">_</div>' +
|
|
472
|
-
'<p>No
|
|
942
|
+
'<p>No device logs received yet.</p>' +
|
|
473
943
|
'<p style="margin-top:12px;font-size:13px">POST a report to <code>/report</code> to see data here.</p>' +
|
|
474
|
-
'</div>'
|
|
944
|
+
'</div>' +
|
|
945
|
+
renderCurlPanel('Curl quick read', [
|
|
946
|
+
curlCommand('/health'),
|
|
947
|
+
curlCommand('/devices'),
|
|
948
|
+
curlCommand('/devices/latest'),
|
|
949
|
+
]);
|
|
475
950
|
return;
|
|
476
951
|
}
|
|
477
|
-
var html = '<div class="section-title">
|
|
478
|
-
html += '
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
952
|
+
var html = '<div class="section-title">Devices <span style="color:var(--text3);font-weight:400">(' + devices.length + ')</span></div>';
|
|
953
|
+
html += renderCurlPanel('Curl quick read', [
|
|
954
|
+
curlCommand('/health'),
|
|
955
|
+
curlCommand('/devices'),
|
|
956
|
+
curlCommand('/devices/latest'),
|
|
957
|
+
]);
|
|
958
|
+
html += '<div class="device-grid">';
|
|
959
|
+
devices.forEach(function(deviceLog, i) {
|
|
960
|
+
var lc = deviceLog.logCount || {};
|
|
961
|
+
var deviceText = formatDevice(deviceLog.device);
|
|
962
|
+
var ipText = formatIp(deviceLog.source);
|
|
963
|
+
html += '<div class="device-card" data-device-id="' + escapeHtml(deviceLog.deviceId) + '" style="animation-delay:' + (i * 40) + 'ms" onclick="location.hash=\'device/' + encodeURIComponent(deviceLog.deviceId) + '\'">';
|
|
964
|
+
html += '<div><div class="device-title">' + escapeHtml(deviceText) + '</div>';
|
|
965
|
+
html += '<div class="device-subtitle">IP ' + escapeHtml(ipText) + '</div></div>';
|
|
966
|
+
html += '<div class="device-meta-group">';
|
|
967
|
+
html += '<div class="device-meta-line"><strong>Device</strong>' + escapeHtml(deviceLog.deviceId) + '</div>';
|
|
968
|
+
html += '<div class="device-meta-line"><strong>Last seen</strong>' + formatTime(deviceLog.lastSeenAt || deviceLog.receivedAt) + '</div>';
|
|
969
|
+
html += '</div>';
|
|
970
|
+
html += '<div class="device-tags">' + renderDeviceTags(lc) + '</div>';
|
|
971
|
+
html += '<div class="device-arrow">›</div>';
|
|
486
972
|
html += '</div>';
|
|
487
973
|
});
|
|
488
974
|
html += '</div>';
|
|
@@ -495,14 +981,18 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
495
981
|
});
|
|
496
982
|
}
|
|
497
983
|
|
|
498
|
-
function renderDetail(
|
|
984
|
+
function renderDetail(deviceId) {
|
|
499
985
|
expandedRows = {};
|
|
986
|
+
searchTerm = '';
|
|
987
|
+
focusedIndex = -1;
|
|
988
|
+
window._currentFilterType = '';
|
|
989
|
+
window._failedOnly = false;
|
|
500
990
|
statusEl.textContent = 'loading...';
|
|
501
|
-
api('/
|
|
991
|
+
api('/devices/' + encodeURIComponent(deviceId)).then(function(data) {
|
|
502
992
|
statusEl.textContent = '';
|
|
503
993
|
pulseDot.style.background = 'var(--cyan)';
|
|
504
994
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
505
|
-
|
|
995
|
+
currentDevice = data;
|
|
506
996
|
var report = data.report || {};
|
|
507
997
|
var logs = report.logs || {};
|
|
508
998
|
var logTypes = Object.keys(logs);
|
|
@@ -512,17 +1002,20 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
512
1002
|
// Back link
|
|
513
1003
|
html += '<a href="#" class="back-link" onclick="location.hash=\'\';return false">';
|
|
514
1004
|
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
|
|
1005
|
+
html += 'All devices</a>';
|
|
516
1006
|
|
|
517
|
-
//
|
|
1007
|
+
// Device header card
|
|
518
1008
|
html += '<div class="detail-header">';
|
|
519
|
-
html += '<div class="detail-id">' + escapeHtml(data.
|
|
1009
|
+
html += '<div class="detail-id">' + escapeHtml(data.deviceId) + '</div>';
|
|
520
1010
|
html += '<div class="detail-meta">';
|
|
521
|
-
html += '<span class="detail-meta-item"><strong>
|
|
1011
|
+
html += '<span class="detail-meta-item"><strong>Last seen</strong> ' + formatTime(data.lastSeenAt || data.receivedAt) + '</span>';
|
|
522
1012
|
var totalLogs = Object.values(data.logCount || {}).reduce(function(a, b) { return a + b; }, 0);
|
|
523
1013
|
html += '<span class="detail-meta-item" data-type="Entries"><strong>Entries</strong> ' + totalLogs + '</span>';
|
|
1014
|
+
if (data.source && data.source.ip) {
|
|
1015
|
+
html += '<span class="detail-meta-item"><strong>IP</strong> ' + escapeHtml(data.source.ip) + '</span>';
|
|
1016
|
+
}
|
|
524
1017
|
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>';
|
|
1018
|
+
html += '<span class="detail-meta-item" data-type="' + e[0] + '"><strong>' + escapeHtml(labelForType(e[0])) + '</strong> ' + e[1] + '</span>';
|
|
526
1019
|
});
|
|
527
1020
|
html += '</div>';
|
|
528
1021
|
|
|
@@ -543,17 +1036,29 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
543
1036
|
|
|
544
1037
|
html += '</div>';
|
|
545
1038
|
|
|
1039
|
+
html += renderCurlPanel('Curl this device', [
|
|
1040
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId)),
|
|
1041
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?limit=100'),
|
|
1042
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?type=network&failedOnly=true&limit=50'),
|
|
1043
|
+
curlCommand('/devices/' + encodeURIComponent(deviceId) + '/logs?type=console&limit=100'),
|
|
1044
|
+
]);
|
|
1045
|
+
|
|
546
1046
|
// Tabs
|
|
547
1047
|
html += '<div class="tabs">';
|
|
548
1048
|
html += '<button class="tab active" data-type="" onclick="filterType(this,\'\')">All<span class="count">' + totalLogs + '</span></button>';
|
|
549
1049
|
logTypes.forEach(function(t) {
|
|
550
1050
|
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>';
|
|
1051
|
+
html += '<button class="tab" data-type="' + t + '" onclick="filterType(this,\'' + t + '\')">' + escapeHtml(labelForType(t)) + '<span class="count">' + count + '</span></button>';
|
|
552
1052
|
});
|
|
553
1053
|
html += '</div>';
|
|
554
1054
|
|
|
555
|
-
// Toolbar
|
|
1055
|
+
// Toolbar with search
|
|
556
1056
|
html += '<div class="toolbar">';
|
|
1057
|
+
html += '<div class="search-wrap">';
|
|
1058
|
+
html += '<span class="search-icon">⚲</span>';
|
|
1059
|
+
html += '<input class="search-input" id="searchInput" type="text" placeholder="Search logs..." autocomplete="off">';
|
|
1060
|
+
html += '<button class="search-clear" id="searchClear" onclick="clearSearch()">×</button>';
|
|
1061
|
+
html += '</div>';
|
|
557
1062
|
html += '<label>Failed only <div class="toggle" id="failedToggle" onclick="toggleFailed()"></div></label>';
|
|
558
1063
|
html += '<label>Limit <input type="number" id="limitInput" value="50" min="1" max="500"></label>';
|
|
559
1064
|
html += '</div>';
|
|
@@ -566,9 +1071,21 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
566
1071
|
html += '<button class="btn" onclick="copyJSON()">';
|
|
567
1072
|
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
1073
|
html += 'Copy JSON</button>';
|
|
1074
|
+
html += '<span style="flex:1"></span>';
|
|
1075
|
+
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
1076
|
html += '</div>';
|
|
570
1077
|
|
|
571
1078
|
app.innerHTML = html;
|
|
1079
|
+
|
|
1080
|
+
// Wire up search
|
|
1081
|
+
var searchInput = document.getElementById('searchInput');
|
|
1082
|
+
searchInput.addEventListener('input', function() {
|
|
1083
|
+
searchTerm = this.value.trim();
|
|
1084
|
+
var clearBtn = document.getElementById('searchClear');
|
|
1085
|
+
if (clearBtn) clearBtn.classList.toggle('visible', searchTerm.length > 0);
|
|
1086
|
+
applyFilters();
|
|
1087
|
+
});
|
|
1088
|
+
|
|
572
1089
|
document.getElementById('limitInput').addEventListener('change', applyFilters);
|
|
573
1090
|
renderLogs(logs, '', 50, false);
|
|
574
1091
|
}).catch(function(err) {
|
|
@@ -581,7 +1098,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
581
1098
|
var entries = [];
|
|
582
1099
|
if (type && logs[type]) {
|
|
583
1100
|
entries = Array.isArray(logs[type])
|
|
584
|
-
? logs[type].map(function(entry) { return { type: type, entry: entry }; })
|
|
1101
|
+
? logs[type].map(function(entry, index) { return { type: type, entry: entry, order: index }; })
|
|
585
1102
|
: [];
|
|
586
1103
|
} else {
|
|
587
1104
|
Object.entries(logs).forEach(function(logGroup) {
|
|
@@ -589,7 +1106,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
589
1106
|
var value = logGroup[1];
|
|
590
1107
|
if (Array.isArray(value)) {
|
|
591
1108
|
value.forEach(function(entry) {
|
|
592
|
-
entries.push({ type: logType, entry: entry });
|
|
1109
|
+
entries.push({ type: logType, entry: entry, order: entries.length });
|
|
593
1110
|
});
|
|
594
1111
|
}
|
|
595
1112
|
});
|
|
@@ -599,14 +1116,28 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
599
1116
|
entries = entries.filter(function(item) { return isFailedEntry(item.entry); });
|
|
600
1117
|
}
|
|
601
1118
|
|
|
602
|
-
|
|
1119
|
+
// Search filter
|
|
1120
|
+
if (searchTerm) {
|
|
1121
|
+
entries = entries.filter(function(item) { return entryMatchesSearch(item.entry); });
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
entries = entries
|
|
1125
|
+
.slice()
|
|
1126
|
+
.sort(function(a, b) {
|
|
1127
|
+
var byTime = readTimestamp(b.entry) - readTimestamp(a.entry);
|
|
1128
|
+
return byTime || b.order - a.order;
|
|
1129
|
+
})
|
|
1130
|
+
.slice(0, limit);
|
|
603
1131
|
|
|
604
1132
|
var container = document.getElementById('logsContainer');
|
|
605
1133
|
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">
|
|
1134
|
+
container.innerHTML = '<div class="empty" style="padding:40px"><div class="empty-icon" style="font-size:24px">0</div><p style="font-size:13px">' +
|
|
1135
|
+
(searchTerm ? 'No logs match "' + escapeHtml(searchTerm) + '"' : 'No logs match filters.') +
|
|
1136
|
+
'</p></div>';
|
|
607
1137
|
return;
|
|
608
1138
|
}
|
|
609
1139
|
|
|
1140
|
+
focusedIndex = -1;
|
|
610
1141
|
var html = '<div class="log-list">';
|
|
611
1142
|
entries.forEach(function(item, i) {
|
|
612
1143
|
var entry = item.entry;
|
|
@@ -614,12 +1145,31 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
614
1145
|
var lt = item.type || getLogType(entry);
|
|
615
1146
|
var typeClass = toKeyPart(lt);
|
|
616
1147
|
var isExpanded = expandedRows[rowId];
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
html += '<div class="log-
|
|
1148
|
+
var ts = readTimestamp(entry);
|
|
1149
|
+
|
|
1150
|
+
html += '<div class="log-entry' + (isExpanded ? ' expanded' : '') + '" id="entry-' + rowId + '" data-index="' + i + '">';
|
|
1151
|
+
html += '<div class="log-row" onclick="toggleRow(\'' + rowId + '\')">';
|
|
1152
|
+
html += '<div class="log-type log-type-' + typeClass + '">' + escapeHtml(labelForType(lt)) + '</div>';
|
|
1153
|
+
html += '<div class="log-summary-col">';
|
|
1154
|
+
html += '<div class="log-summary">' + matchSearch(summarize(entry)) + '</div>';
|
|
1155
|
+
if (ts) {
|
|
1156
|
+
html += '<div class="log-timestamp">' + formatTimeShort(new Date(ts).toISOString()) + '</div>';
|
|
1157
|
+
}
|
|
1158
|
+
html += '</div>';
|
|
620
1159
|
html += '<div class="log-status">' + statusBadge(entry) + '</div>';
|
|
621
|
-
html += '<div class="log-
|
|
622
|
-
html += '<div class="log-
|
|
1160
|
+
html += '<div class="log-copy" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')"><button class="copy-btn" title="Copy entry JSON">⎘</button></div>';
|
|
1161
|
+
html += '<div class="log-expand">' + (isExpanded ? '▶' : '▶') + '</div>';
|
|
1162
|
+
html += '</div>';
|
|
1163
|
+
|
|
1164
|
+
// Detail panel
|
|
1165
|
+
html += '<div class="log-detail' + (isExpanded ? '' : '') + '" id="detail-' + rowId + '">';
|
|
1166
|
+
html += '<div class="log-detail-inner"><div class="detail-sections">';
|
|
1167
|
+
html += renderLogDetails(entry, lt);
|
|
1168
|
+
html += '<div class="entry-footer">';
|
|
1169
|
+
html += '<button class="btn btn-sm" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')">⎘ Copy JSON</button>';
|
|
1170
|
+
html += '</div>';
|
|
1171
|
+
html += '</div></div></div>';
|
|
1172
|
+
|
|
623
1173
|
html += '</div>';
|
|
624
1174
|
});
|
|
625
1175
|
html += '</div>';
|
|
@@ -637,25 +1187,26 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
637
1187
|
}
|
|
638
1188
|
|
|
639
1189
|
function rerenderVisibleLogs() {
|
|
640
|
-
if (!
|
|
641
|
-
var logs =
|
|
1190
|
+
if (!currentDevice) return;
|
|
1191
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
642
1192
|
var options = readVisibleLogOptions();
|
|
643
1193
|
renderLogs(logs, options.type, options.limit, options.failedOnly);
|
|
644
1194
|
}
|
|
645
1195
|
|
|
646
|
-
function
|
|
647
|
-
if (!
|
|
1196
|
+
function refreshCurrentDevice() {
|
|
1197
|
+
if (!currentDevice) {
|
|
648
1198
|
renderList();
|
|
649
1199
|
return Promise.resolve();
|
|
650
1200
|
}
|
|
651
1201
|
|
|
652
1202
|
statusEl.textContent = 'refreshing...';
|
|
653
|
-
return api('/
|
|
1203
|
+
return api('/devices/' + encodeURIComponent(currentDevice.deviceId)).then(function(data) {
|
|
654
1204
|
statusEl.textContent = '';
|
|
655
1205
|
if (!data) return;
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1206
|
+
currentDevice.report = data.report;
|
|
1207
|
+
currentDevice.logCount = data.logCount;
|
|
1208
|
+
currentDevice.receivedAt = data.receivedAt;
|
|
1209
|
+
currentDevice.lastSeenAt = data.lastSeenAt;
|
|
659
1210
|
rerenderVisibleLogs();
|
|
660
1211
|
updateTabCounts();
|
|
661
1212
|
}).catch(function(err) {
|
|
@@ -665,7 +1216,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
665
1216
|
}
|
|
666
1217
|
|
|
667
1218
|
function refreshCurrentView() {
|
|
668
|
-
return
|
|
1219
|
+
return refreshCurrentDevice();
|
|
669
1220
|
}
|
|
670
1221
|
|
|
671
1222
|
window.refresh = function() { refreshCurrentView(); };
|
|
@@ -685,31 +1236,43 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
685
1236
|
};
|
|
686
1237
|
|
|
687
1238
|
window.applyFilters = function() {
|
|
688
|
-
if (!
|
|
1239
|
+
if (!currentDevice) return;
|
|
689
1240
|
expandedRows = {};
|
|
690
1241
|
rerenderVisibleLogs();
|
|
691
1242
|
};
|
|
692
1243
|
|
|
1244
|
+
window.clearSearch = function() {
|
|
1245
|
+
var input = document.getElementById('searchInput');
|
|
1246
|
+
if (input) input.value = '';
|
|
1247
|
+
searchTerm = '';
|
|
1248
|
+
var clearBtn = document.getElementById('searchClear');
|
|
1249
|
+
if (clearBtn) clearBtn.classList.remove('visible');
|
|
1250
|
+
applyFilters();
|
|
1251
|
+
};
|
|
1252
|
+
|
|
693
1253
|
window.toggleRow = function(rowId) {
|
|
694
1254
|
var entry = document.getElementById('entry-' + rowId);
|
|
695
|
-
var
|
|
696
|
-
|
|
697
|
-
if (!entry || !json) return;
|
|
1255
|
+
var detail = document.getElementById('detail-' + rowId);
|
|
1256
|
+
if (!entry || !detail) return;
|
|
698
1257
|
expandedRows[rowId] = !expandedRows[rowId];
|
|
699
1258
|
if (expandedRows[rowId]) {
|
|
700
1259
|
entry.classList.add('expanded');
|
|
701
|
-
json.classList.add('open');
|
|
702
|
-
if (expand) expand.innerHTML = '▼';
|
|
703
1260
|
} else {
|
|
704
1261
|
entry.classList.remove('expanded');
|
|
705
|
-
json.classList.remove('open');
|
|
706
|
-
if (expand) expand.innerHTML = '▶';
|
|
707
1262
|
}
|
|
708
1263
|
};
|
|
709
1264
|
|
|
1265
|
+
window.toggleCurl = function(id) {
|
|
1266
|
+
var body = document.getElementById(id);
|
|
1267
|
+
var toggle = document.getElementById('toggle-' + id);
|
|
1268
|
+
if (!body) return;
|
|
1269
|
+
body.classList.toggle('open');
|
|
1270
|
+
if (toggle) toggle.classList.toggle('open');
|
|
1271
|
+
};
|
|
1272
|
+
|
|
710
1273
|
window.copyJSON = function() {
|
|
711
|
-
if (!
|
|
712
|
-
var text = JSON.stringify(
|
|
1274
|
+
if (!currentDevice) return;
|
|
1275
|
+
var text = JSON.stringify(currentDevice.report, null, 2);
|
|
713
1276
|
navigator.clipboard.writeText(text).then(function() {
|
|
714
1277
|
showToast('Copied to clipboard');
|
|
715
1278
|
}).catch(function() {
|
|
@@ -725,6 +1288,135 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
725
1288
|
});
|
|
726
1289
|
};
|
|
727
1290
|
|
|
1291
|
+
window.copyEntryJSON = function(rowId) {
|
|
1292
|
+
// Find the entry data from current device logs
|
|
1293
|
+
if (!currentDevice) return;
|
|
1294
|
+
var el = document.getElementById('entry-' + rowId);
|
|
1295
|
+
if (!el) return;
|
|
1296
|
+
|
|
1297
|
+
// Re-derive the entry from current logs
|
|
1298
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
1299
|
+
var entries = [];
|
|
1300
|
+
Object.entries(logs).forEach(function(logGroup) {
|
|
1301
|
+
var logType = logGroup[0];
|
|
1302
|
+
var value = logGroup[1];
|
|
1303
|
+
if (Array.isArray(value)) {
|
|
1304
|
+
value.forEach(function(entry, index) {
|
|
1305
|
+
entries.push({ type: logType, entry: entry, order: entries.length });
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
var options = readVisibleLogOptions();
|
|
1311
|
+
if (options.type) {
|
|
1312
|
+
entries = entries.filter(function(item) { return item.type === options.type; });
|
|
1313
|
+
}
|
|
1314
|
+
if (options.failedOnly) {
|
|
1315
|
+
entries = entries.filter(function(item) { return isFailedEntry(item.entry); });
|
|
1316
|
+
}
|
|
1317
|
+
if (searchTerm) {
|
|
1318
|
+
entries = entries.filter(function(item) { return entryMatchesSearch(item.entry); });
|
|
1319
|
+
}
|
|
1320
|
+
entries = entries.slice().sort(function(a, b) {
|
|
1321
|
+
var byTime = readTimestamp(b.entry) - readTimestamp(a.entry);
|
|
1322
|
+
return byTime || b.order - a.order;
|
|
1323
|
+
}).slice(0, options.limit);
|
|
1324
|
+
|
|
1325
|
+
var idx = parseInt(el.getAttribute('data-index'), 10);
|
|
1326
|
+
if (isNaN(idx) || idx < 0 || idx >= entries.length) return;
|
|
1327
|
+
var text = JSON.stringify(entries[idx].entry, null, 2);
|
|
1328
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
1329
|
+
showToast('Entry copied');
|
|
1330
|
+
}).catch(function() {
|
|
1331
|
+
var ta = document.createElement('textarea');
|
|
1332
|
+
ta.value = text;
|
|
1333
|
+
ta.style.position = 'fixed';
|
|
1334
|
+
ta.style.opacity = '0';
|
|
1335
|
+
document.body.appendChild(ta);
|
|
1336
|
+
ta.select();
|
|
1337
|
+
document.execCommand('copy');
|
|
1338
|
+
document.body.removeChild(ta);
|
|
1339
|
+
showToast('Entry copied');
|
|
1340
|
+
});
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
window.copySectionData = function(btn) {
|
|
1344
|
+
var data = btn.getAttribute('data-copy');
|
|
1345
|
+
if (!data) return;
|
|
1346
|
+
navigator.clipboard.writeText(data).then(function() {
|
|
1347
|
+
showToast('Section copied');
|
|
1348
|
+
}).catch(function() {
|
|
1349
|
+
var ta = document.createElement('textarea');
|
|
1350
|
+
ta.value = data;
|
|
1351
|
+
ta.style.position = 'fixed';
|
|
1352
|
+
ta.style.opacity = '0';
|
|
1353
|
+
document.body.appendChild(ta);
|
|
1354
|
+
ta.select();
|
|
1355
|
+
document.execCommand('copy');
|
|
1356
|
+
document.body.removeChild(ta);
|
|
1357
|
+
showToast('Section copied');
|
|
1358
|
+
});
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
// --- Keyboard shortcuts ---
|
|
1362
|
+
|
|
1363
|
+
document.addEventListener('keydown', function(e) {
|
|
1364
|
+
// Don't intercept when typing in inputs
|
|
1365
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
|
1366
|
+
if (e.key === 'Escape') {
|
|
1367
|
+
e.target.blur();
|
|
1368
|
+
if (e.target.id === 'searchInput' && searchTerm) {
|
|
1369
|
+
clearSearch();
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (e.key === '/') {
|
|
1376
|
+
e.preventDefault();
|
|
1377
|
+
var searchInput = document.getElementById('searchInput');
|
|
1378
|
+
if (searchInput) searchInput.focus();
|
|
1379
|
+
} else if (e.key === 'Escape') {
|
|
1380
|
+
if (currentDevice) {
|
|
1381
|
+
location.hash = '';
|
|
1382
|
+
}
|
|
1383
|
+
} else if (e.key === 'j' || e.key === 'k') {
|
|
1384
|
+
e.preventDefault();
|
|
1385
|
+
navigateEntries(e.key === 'j' ? 1 : -1);
|
|
1386
|
+
} else if (e.key === 'Enter') {
|
|
1387
|
+
e.preventDefault();
|
|
1388
|
+
toggleFocusedEntry();
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
function navigateEntries(direction) {
|
|
1393
|
+
var list = document.querySelector('.log-list');
|
|
1394
|
+
if (!list) return;
|
|
1395
|
+
var items = list.querySelectorAll('.log-entry');
|
|
1396
|
+
if (!items.length) return;
|
|
1397
|
+
|
|
1398
|
+
// Remove previous focus
|
|
1399
|
+
if (focusedIndex >= 0 && focusedIndex < items.length) {
|
|
1400
|
+
items[focusedIndex].classList.remove('focused');
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
focusedIndex += direction;
|
|
1404
|
+
if (focusedIndex < 0) focusedIndex = 0;
|
|
1405
|
+
if (focusedIndex >= items.length) focusedIndex = items.length - 1;
|
|
1406
|
+
|
|
1407
|
+
items[focusedIndex].classList.add('focused');
|
|
1408
|
+
items[focusedIndex].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function toggleFocusedEntry() {
|
|
1412
|
+
if (focusedIndex < 0) return;
|
|
1413
|
+
var list = document.querySelector('.log-list');
|
|
1414
|
+
if (!list) return;
|
|
1415
|
+
var items = list.querySelectorAll('.log-entry');
|
|
1416
|
+
if (focusedIndex >= items.length) return;
|
|
1417
|
+
items[focusedIndex].click();
|
|
1418
|
+
}
|
|
1419
|
+
|
|
728
1420
|
// --- Routing ---
|
|
729
1421
|
|
|
730
1422
|
window._currentFilterType = '';
|
|
@@ -732,8 +1424,8 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
732
1424
|
|
|
733
1425
|
function route() {
|
|
734
1426
|
var hash = location.hash.replace('#', '');
|
|
735
|
-
if (hash.startsWith('
|
|
736
|
-
renderDetail(decodeURIComponent(hash.substring(
|
|
1427
|
+
if (hash.startsWith('device/')) {
|
|
1428
|
+
renderDetail(decodeURIComponent(hash.substring(7)));
|
|
737
1429
|
} else {
|
|
738
1430
|
renderList();
|
|
739
1431
|
}
|
|
@@ -741,63 +1433,84 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
741
1433
|
|
|
742
1434
|
// --- Incremental DOM updates ---
|
|
743
1435
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
var grid = document.querySelector('.
|
|
1436
|
+
function isFailedEntry(e) {
|
|
1437
|
+
return e && typeof e === 'object' && (
|
|
1438
|
+
Boolean(e.error) ||
|
|
1439
|
+
e.level === 'error' ||
|
|
1440
|
+
(e.response && (e.response.success === false || e.response.status >= 400))
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function renderDeviceTags(logCount) {
|
|
1445
|
+
var html = '';
|
|
1446
|
+
Object.entries(logCount || {}).forEach(function(e) {
|
|
1447
|
+
var type = String(e[0]);
|
|
1448
|
+
html += '<span class="tag tag-' + toKeyPart(type) + '">' + escapeHtml(labelForType(type)) + ' ' + escapeHtml(String(e[1])) + '</span>';
|
|
1449
|
+
});
|
|
1450
|
+
return html;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function appendDeviceCard(payload) {
|
|
1454
|
+
var grid = document.querySelector('.device-grid');
|
|
763
1455
|
if (!grid) { renderList(); return; }
|
|
764
1456
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1457
|
+
var deviceId = payload.deviceId;
|
|
1458
|
+
if (!deviceId) return;
|
|
1459
|
+
var existing = grid.querySelector('[data-device-id="' + CSS.escape(deviceId) + '"]');
|
|
1460
|
+
if (existing) {
|
|
1461
|
+
var tags = existing.querySelector('.device-tags');
|
|
1462
|
+
if (tags) tags.innerHTML = renderDeviceTags(payload.logCount || {});
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
772
1465
|
|
|
773
|
-
|
|
1466
|
+
var lc = payload.logCount || {};
|
|
1467
|
+
var deviceText = formatDevice(payload.device);
|
|
1468
|
+
var ipText = formatIp(payload.source);
|
|
774
1469
|
var card = document.createElement('div');
|
|
775
|
-
card.className = '
|
|
776
|
-
card.setAttribute('data-
|
|
777
|
-
card.setAttribute('onclick', "location.hash='
|
|
778
|
-
var html = '<div><div class="
|
|
779
|
-
html += '<div class="
|
|
780
|
-
|
|
781
|
-
html += '<div class="
|
|
1470
|
+
card.className = 'device-card';
|
|
1471
|
+
card.setAttribute('data-device-id', deviceId);
|
|
1472
|
+
card.setAttribute('onclick', "location.hash='device/" + encodeURIComponent(deviceId) + "'");
|
|
1473
|
+
var html = '<div><div class="device-title">' + escapeHtml(deviceText) + '</div>';
|
|
1474
|
+
html += '<div class="device-subtitle">IP ' + escapeHtml(ipText) + '</div></div>';
|
|
1475
|
+
html += '<div class="device-meta-group">';
|
|
1476
|
+
html += '<div class="device-meta-line"><strong>Device</strong>' + escapeHtml(deviceId) + '</div>';
|
|
1477
|
+
html += '<div class="device-meta-line"><strong>Last seen</strong> just now</div>';
|
|
1478
|
+
html += '</div>';
|
|
1479
|
+
html += '<div class="device-tags">' + renderDeviceTags(lc) + '</div>';
|
|
1480
|
+
html += '<div class="device-arrow">›</div>';
|
|
782
1481
|
card.innerHTML = html;
|
|
783
1482
|
grid.prepend(card);
|
|
784
1483
|
|
|
785
|
-
// Update session count
|
|
786
1484
|
var titleEl = document.querySelector('.section-title');
|
|
787
1485
|
if (titleEl) {
|
|
788
|
-
var count = grid.querySelectorAll('.
|
|
789
|
-
titleEl.innerHTML = '
|
|
1486
|
+
var count = grid.querySelectorAll('.device-card').length;
|
|
1487
|
+
titleEl.innerHTML = 'Devices <span style="color:var(--text3);font-weight:400">(' + count + ')</span>';
|
|
790
1488
|
}
|
|
791
1489
|
}
|
|
792
1490
|
|
|
793
|
-
function buildLogEntryHtml(entry, rowId, type) {
|
|
1491
|
+
function buildLogEntryHtml(entry, rowId, type, index) {
|
|
794
1492
|
var lt = type || getLogType(entry);
|
|
795
1493
|
var typeClass = toKeyPart(lt);
|
|
796
|
-
var
|
|
797
|
-
html
|
|
1494
|
+
var ts = readTimestamp(entry);
|
|
1495
|
+
var html = '<div class="log-row">';
|
|
1496
|
+
html += '<div class="log-type log-type-' + typeClass + '">' + escapeHtml(labelForType(lt)) + '</div>';
|
|
1497
|
+
html += '<div class="log-summary-col">';
|
|
1498
|
+
html += '<div class="log-summary">' + matchSearch(summarize(entry)) + '</div>';
|
|
1499
|
+
if (ts) {
|
|
1500
|
+
html += '<div class="log-timestamp">' + formatTimeShort(new Date(ts).toISOString()) + '</div>';
|
|
1501
|
+
}
|
|
1502
|
+
html += '</div>';
|
|
798
1503
|
html += '<div class="log-status">' + statusBadge(entry) + '</div>';
|
|
1504
|
+
html += '<div class="log-copy" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')"><button class="copy-btn" title="Copy entry JSON">⎘</button></div>';
|
|
799
1505
|
html += '<div class="log-expand">▶</div>';
|
|
800
|
-
html += '
|
|
1506
|
+
html += '</div>';
|
|
1507
|
+
html += '<div class="log-detail" id="detail-' + rowId + '">';
|
|
1508
|
+
html += '<div class="log-detail-inner"><div class="detail-sections">';
|
|
1509
|
+
html += renderLogDetails(entry, lt);
|
|
1510
|
+
html += '<div class="entry-footer">';
|
|
1511
|
+
html += '<button class="btn btn-sm" onclick="event.stopPropagation();copyEntryJSON(\'' + rowId + '\')">⎘ Copy JSON</button>';
|
|
1512
|
+
html += '</div>';
|
|
1513
|
+
html += '</div></div></div>';
|
|
801
1514
|
return html;
|
|
802
1515
|
}
|
|
803
1516
|
|
|
@@ -823,6 +1536,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
823
1536
|
entries.forEach(function(e) {
|
|
824
1537
|
if (failedOnly && !isFailedEntry(e)) return;
|
|
825
1538
|
if (type && t !== type) return;
|
|
1539
|
+
if (searchTerm && !entryMatchesSearch(e)) return;
|
|
826
1540
|
allNewEntries.push({ type: t, entry: e });
|
|
827
1541
|
});
|
|
828
1542
|
});
|
|
@@ -834,24 +1548,26 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
834
1548
|
var div = document.createElement('div');
|
|
835
1549
|
div.className = 'log-entry';
|
|
836
1550
|
div.id = 'entry-' + rowId;
|
|
837
|
-
div.setAttribute('
|
|
838
|
-
div.
|
|
839
|
-
|
|
1551
|
+
div.setAttribute('data-index', count - 1);
|
|
1552
|
+
div.setAttribute('onclick', '');
|
|
1553
|
+
div.querySelector('.log-row').setAttribute('onclick', "toggleRow('" + rowId + "')");
|
|
1554
|
+
div.innerHTML = buildLogEntryHtml(entry, rowId, item.type, count - 1);
|
|
1555
|
+
list.prepend(div);
|
|
840
1556
|
});
|
|
841
1557
|
|
|
842
|
-
// Trim from
|
|
1558
|
+
// Trim oldest rows from bottom if over limit, skip expanded entries.
|
|
843
1559
|
var entries = list.querySelectorAll('.log-entry');
|
|
844
1560
|
while (entries.length > limit) {
|
|
845
|
-
var
|
|
846
|
-
if (
|
|
847
|
-
list.removeChild(
|
|
1561
|
+
var last = entries[entries.length - 1];
|
|
1562
|
+
if (last && last.classList.contains('expanded')) break;
|
|
1563
|
+
list.removeChild(last);
|
|
848
1564
|
entries = list.querySelectorAll('.log-entry');
|
|
849
1565
|
}
|
|
850
1566
|
}
|
|
851
1567
|
|
|
852
1568
|
function updateTabCounts() {
|
|
853
|
-
if (!
|
|
854
|
-
var logs =
|
|
1569
|
+
if (!currentDevice) return;
|
|
1570
|
+
var logs = currentDevice.report ? currentDevice.report.logs : {};
|
|
855
1571
|
var totalLogs = Object.values(logs).reduce(function(a, v) { return a + (Array.isArray(v) ? v.length : 0); }, 0);
|
|
856
1572
|
|
|
857
1573
|
document.querySelectorAll('.tab').forEach(function(tab) {
|
|
@@ -877,7 +1593,7 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
877
1593
|
|
|
878
1594
|
function connectSSE() {
|
|
879
1595
|
if (eventSource) { try { eventSource.close(); } catch {} }
|
|
880
|
-
|
|
1596
|
+
eventSource = new EventSource(withAuth('/events'));
|
|
881
1597
|
|
|
882
1598
|
eventSource.addEventListener('logs', function(e) {
|
|
883
1599
|
try {
|
|
@@ -886,17 +1602,15 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
886
1602
|
pulseDot.style.background = 'var(--cyan)';
|
|
887
1603
|
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
888
1604
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
appendSessionCard(payload);
|
|
1605
|
+
if (!currentDevice) {
|
|
1606
|
+
appendDeviceCard(payload);
|
|
892
1607
|
return;
|
|
893
1608
|
}
|
|
894
1609
|
|
|
895
|
-
|
|
896
|
-
if (payload.sessionId === currentSession.sessionId) {
|
|
1610
|
+
if (payload.deviceId === currentDevice.deviceId) {
|
|
897
1611
|
if (payload.type === 'delta' && payload.delta) {
|
|
898
1612
|
var deltaLogs = payload.delta.logs || {};
|
|
899
|
-
var report =
|
|
1613
|
+
var report = currentDevice.report || { version: 2, logs: {} };
|
|
900
1614
|
if (!report.logs) report.logs = {};
|
|
901
1615
|
|
|
902
1616
|
Object.entries(deltaLogs).forEach(function(entry) {
|
|
@@ -907,15 +1621,15 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
907
1621
|
report.logs[type] = report.logs[type].concat(entries);
|
|
908
1622
|
});
|
|
909
1623
|
|
|
910
|
-
|
|
911
|
-
if (payload.logCount)
|
|
1624
|
+
currentDevice.report = report;
|
|
1625
|
+
if (payload.logCount) currentDevice.logCount = payload.logCount;
|
|
912
1626
|
appendDeltaLogs(deltaLogs);
|
|
913
1627
|
updateTabCounts();
|
|
914
1628
|
} else if (payload.type === 'full') {
|
|
915
|
-
|
|
1629
|
+
refreshCurrentDevice();
|
|
916
1630
|
}
|
|
917
|
-
} else if (!location.hash.startsWith('
|
|
918
|
-
|
|
1631
|
+
} else if (!location.hash.startsWith('device/')) {
|
|
1632
|
+
appendDeviceCard(payload);
|
|
919
1633
|
}
|
|
920
1634
|
} catch {}
|
|
921
1635
|
});
|
|
@@ -930,6 +1644,18 @@ header h1 span{color:var(--text3);font-weight:400}
|
|
|
930
1644
|
window.addEventListener('hashchange', route);
|
|
931
1645
|
connectSSE();
|
|
932
1646
|
route();
|
|
1647
|
+
|
|
1648
|
+
// Fetch LAN IPs from /health and display in header
|
|
1649
|
+
api('/health').then(function(data) {
|
|
1650
|
+
var ips = data && data.ips;
|
|
1651
|
+
if (Array.isArray(ips) && ips.length) {
|
|
1652
|
+
var ipEl = document.getElementById('ipHint');
|
|
1653
|
+
if (ipEl) {
|
|
1654
|
+
ipEl.textContent = 'LAN ' + ips.join(' / ');
|
|
1655
|
+
ipEl.style.display = '';
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}).catch(function() {});
|
|
933
1659
|
})();
|
|
934
1660
|
</script>
|
|
935
1661
|
</body>
|