react-native-debug-toolkit 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/debug-toolkit.js +114 -0
- package/lib/commonjs/features/network/index.js +28 -2
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +14 -6
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/commonjs/index.js +59 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/panel/DebugPanel.js +25 -0
- package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
- package/lib/commonjs/ui/panel/FloatPanelView.js +15 -62
- package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js +529 -0
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js +71 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/commonjs/utils/autoDetectDaemon.js +141 -0
- package/lib/commonjs/utils/autoDetectDaemon.js.map +1 -0
- package/lib/commonjs/utils/createPersistedObservableStore.js +23 -3
- package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/commonjs/utils/daemonConnection.js +81 -0
- package/lib/commonjs/utils/daemonConnection.js.map +1 -0
- package/lib/commonjs/utils/daemonSettings.js +110 -0
- package/lib/commonjs/utils/daemonSettings.js.map +1 -0
- package/lib/commonjs/utils/reportToDaemon.js +112 -0
- package/lib/commonjs/utils/reportToDaemon.js.map +1 -0
- package/lib/commonjs/utils/sessionReport.js +132 -0
- package/lib/commonjs/utils/sessionReport.js.map +1 -0
- package/lib/commonjs/utils/streamToDaemon.js +334 -0
- package/lib/commonjs/utils/streamToDaemon.js.map +1 -0
- package/lib/module/features/network/index.js +25 -1
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +14 -6
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/panel/DebugPanel.js +26 -1
- package/lib/module/ui/panel/DebugPanel.js.map +1 -1
- package/lib/module/ui/panel/FloatPanelView.js +16 -63
- package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/module/ui/panel/StreamingSettingsModal.js +524 -0
- package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/module/ui/panel/useTabAnimation.js +67 -0
- package/lib/module/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/module/utils/autoDetectDaemon.js +136 -0
- package/lib/module/utils/autoDetectDaemon.js.map +1 -0
- package/lib/module/utils/createPersistedObservableStore.js +23 -3
- package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/module/utils/daemonConnection.js +77 -0
- package/lib/module/utils/daemonConnection.js.map +1 -0
- package/lib/module/utils/daemonSettings.js +102 -0
- package/lib/module/utils/daemonSettings.js.map +1 -0
- package/lib/module/utils/reportToDaemon.js +105 -0
- package/lib/module/utils/reportToDaemon.js.map +1 -0
- package/lib/module/utils/sessionReport.js +128 -0
- package/lib/module/utils/sessionReport.js.map +1 -0
- package/lib/module/utils/streamToDaemon.js +328 -0
- package/lib/module/utils/streamToDaemon.js.map +1 -0
- package/lib/typescript/src/features/network/index.d.ts +2 -0
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/networkInterceptor.d.ts +1 -1
- package/lib/typescript/src/features/network/networkInterceptor.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts +8 -0
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts +14 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts.map +1 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts +15 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +2 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/daemonConnection.d.ts +18 -0
- package/lib/typescript/src/utils/daemonConnection.d.ts.map +1 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts +19 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts.map +1 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts +34 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/sessionReport.d.ts +18 -0
- package/lib/typescript/src/utils/sessionReport.d.ts.map +1 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts +23 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts.map +1 -0
- package/node/daemon/src/cli.js +75 -0
- package/node/daemon/src/console/console.html +936 -0
- package/node/daemon/src/console/index.js +47 -0
- package/node/daemon/src/constants.js +32 -0
- package/node/daemon/src/index.js +11 -0
- package/node/daemon/src/server.js +365 -0
- package/node/daemon/src/store.js +110 -0
- package/node/mcp/src/cli.js +31 -0
- package/node/mcp/src/constants.js +13 -0
- package/node/mcp/src/daemonClient.js +132 -0
- package/node/mcp/src/httpClient.js +49 -0
- package/node/mcp/src/index.js +15 -0
- package/node/mcp/src/logs.js +95 -0
- package/node/mcp/src/server.js +144 -0
- package/node/mcp/src/tools.js +84 -0
- package/package.json +7 -2
- package/src/features/network/index.ts +30 -3
- package/src/features/network/networkInterceptor.ts +19 -6
- package/src/index.ts +14 -0
- package/src/ui/panel/DebugPanel.tsx +23 -1
- package/src/ui/panel/FloatPanelView.tsx +10 -68
- package/src/ui/panel/StreamingSettingsModal.tsx +566 -0
- package/src/ui/panel/useTabAnimation.ts +77 -0
- package/src/utils/autoDetectDaemon.ts +175 -0
- package/src/utils/createPersistedObservableStore.ts +16 -3
- package/src/utils/daemonConnection.ts +133 -0
- package/src/utils/daemonSettings.ts +134 -0
- package/src/utils/reportToDaemon.ts +172 -0
- package/src/utils/sessionReport.ts +203 -0
- package/src/utils/streamToDaemon.ts +419 -0
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Debug Toolkit Console</title>
|
|
7
|
+
<style>
|
|
8
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
9
|
+
:root{
|
|
10
|
+
--bg:#080c16;--bg2:#0d1220;--surface:#111827;--surface2:#1a2236;--surface3:#222d44;
|
|
11
|
+
--border:#1e2d4a;--border2:#2a3f66;
|
|
12
|
+
--text:#e2e8f0;--text2:#8899b4;--text3:#5a6e8a;
|
|
13
|
+
--cyan:#00e5ff;--cyan-dim:rgba(0,229,255,.12);--cyan-mid:rgba(0,229,255,.25);
|
|
14
|
+
--green:#00e676;--green-dim:rgba(0,230,118,.12);
|
|
15
|
+
--red:#ff1744;--red-dim:rgba(255,23,68,.12);
|
|
16
|
+
--amber:#ffab00;--amber-dim:rgba(255,171,0,.12);
|
|
17
|
+
--orange:#ff6e40;--orange-dim:rgba(255,110,64,.12);
|
|
18
|
+
--font-mono:'SF Mono',Monaco,Consolas,monospace;
|
|
19
|
+
--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
|
20
|
+
--radius:6px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
html{scrollbar-width:thin;scrollbar-color:var(--border2) transparent}
|
|
24
|
+
::-webkit-scrollbar{width:6px;height:6px}
|
|
25
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
26
|
+
::-webkit-scrollbar-thumb{background:var(--border2);border-radius:3px}
|
|
27
|
+
|
|
28
|
+
body{
|
|
29
|
+
font-family:var(--font-sans);background:var(--bg);color:var(--text);
|
|
30
|
+
line-height:1.6;min-height:100vh;
|
|
31
|
+
background-image:
|
|
32
|
+
repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,229,255,.012) 2px,rgba(0,229,255,.012) 4px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Scanline overlay */
|
|
36
|
+
body::after{
|
|
37
|
+
content:'';position:fixed;inset:0;pointer-events:none;z-index:9999;
|
|
38
|
+
background:repeating-linear-gradient(0deg,transparent 0px,transparent 1px,rgba(0,0,0,.03) 1px,rgba(0,0,0,.03) 2px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
a{color:var(--cyan);text-decoration:none;transition:color .15s}
|
|
42
|
+
a:hover{color:#fff}
|
|
43
|
+
|
|
44
|
+
/* Header */
|
|
45
|
+
header{
|
|
46
|
+
display:flex;align-items:center;justify-content:space-between;
|
|
47
|
+
padding:0 24px;height:56px;
|
|
48
|
+
background:linear-gradient(180deg,var(--bg2) 0%,rgba(13,18,32,.95) 100%);
|
|
49
|
+
border-bottom:1px solid var(--border);
|
|
50
|
+
position:sticky;top:0;z-index:100;
|
|
51
|
+
backdrop-filter:blur(12px);
|
|
52
|
+
}
|
|
53
|
+
.header-left{display:flex;align-items:center;gap:14px}
|
|
54
|
+
.pulse{
|
|
55
|
+
width:8px;height:8px;border-radius:50%;background:var(--cyan);flex-shrink:0;
|
|
56
|
+
box-shadow:0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3);
|
|
57
|
+
animation:pulse 2s ease-in-out infinite;
|
|
58
|
+
}
|
|
59
|
+
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.85)}}
|
|
60
|
+
header h1{font-size:15px;font-weight:600;letter-spacing:-.01em;color:var(--text)}
|
|
61
|
+
header h1 span{color:var(--text3);font-weight:400}
|
|
62
|
+
.header-right{display:flex;align-items:center;gap:10px}
|
|
63
|
+
.header-meta{font-size:11px;font-family:var(--font-mono);color:var(--text3)}
|
|
64
|
+
|
|
65
|
+
/* Buttons */
|
|
66
|
+
.btn{
|
|
67
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
68
|
+
padding:6px 14px;font-size:12px;font-weight:500;
|
|
69
|
+
border:1px solid var(--border2);border-radius:var(--radius);
|
|
70
|
+
background:var(--surface2);color:var(--text2);cursor:pointer;
|
|
71
|
+
font-family:var(--font-sans);transition:all .15s;letter-spacing:.01em;
|
|
72
|
+
}
|
|
73
|
+
.btn:hover{background:var(--surface3);color:var(--text);border-color:var(--cyan-mid)}
|
|
74
|
+
.btn:active{transform:scale(.97)}
|
|
75
|
+
.btn-icon{padding:6px 10px;font-size:14px}
|
|
76
|
+
|
|
77
|
+
/* Container */
|
|
78
|
+
.container{max-width:1160px;margin:0 auto;padding:28px 24px}
|
|
79
|
+
|
|
80
|
+
/* Empty state */
|
|
81
|
+
.empty{text-align:center;padding:80px 20px;color:var(--text3)}
|
|
82
|
+
.empty-icon{font-size:40px;margin-bottom:16px;opacity:.3;font-family:var(--font-mono)}
|
|
83
|
+
.empty p{font-size:14px;line-height:1.8}
|
|
84
|
+
.empty code{
|
|
85
|
+
font-family:var(--font-mono);font-size:12px;
|
|
86
|
+
background:var(--surface);padding:2px 8px;border-radius:3px;
|
|
87
|
+
border:1px solid var(--border);color:var(--cyan);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Session list */
|
|
91
|
+
.section-title{
|
|
92
|
+
font-size:13px;font-weight:600;color:var(--text2);
|
|
93
|
+
text-transform:uppercase;letter-spacing:.08em;
|
|
94
|
+
margin-bottom:16px;display:flex;align-items:center;gap:8px;
|
|
95
|
+
}
|
|
96
|
+
.section-title::after{content:'';flex:1;height:1px;background:var(--border)}
|
|
97
|
+
|
|
98
|
+
.session-grid{display:flex;flex-direction:column;gap:6px}
|
|
99
|
+
.session-card{
|
|
100
|
+
display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:16px;
|
|
101
|
+
padding:14px 18px;background:var(--surface);
|
|
102
|
+
border:1px solid var(--border);border-radius:var(--radius);
|
|
103
|
+
cursor:pointer;transition:all .15s;
|
|
104
|
+
}
|
|
105
|
+
.session-card:hover{
|
|
106
|
+
background:var(--surface2);border-color:var(--border2);
|
|
107
|
+
transform:translateX(3px);
|
|
108
|
+
box-shadow:0 0 0 1px var(--cyan-dim),0 4px 16px rgba(0,0,0,.3);
|
|
109
|
+
}
|
|
110
|
+
.session-id{font-family:var(--font-mono);font-size:12px;color:var(--text);font-weight:500}
|
|
111
|
+
.session-time{font-size:11px;color:var(--text3);font-family:var(--font-mono);margin-top:2px}
|
|
112
|
+
.session-tags{display:flex;gap:6px;flex-wrap:wrap}
|
|
113
|
+
.tag{
|
|
114
|
+
font-family:var(--font-mono);font-size:10px;font-weight:500;
|
|
115
|
+
padding:2px 8px;border-radius:3px;
|
|
116
|
+
background:var(--cyan-dim);color:var(--cyan);
|
|
117
|
+
border:1px solid rgba(0,229,255,.1);
|
|
118
|
+
}
|
|
119
|
+
.tag-network{background:var(--cyan-dim);color:var(--cyan);border-color:rgba(0,229,255,.1)}
|
|
120
|
+
.tag-console{background:var(--green-dim);color:var(--green);border-color:rgba(0,230,118,.1)}
|
|
121
|
+
.tag-error{background:var(--red-dim);color:var(--red);border-color:rgba(255,23,68,.1)}
|
|
122
|
+
.tag-warn{background:var(--amber-dim);color:var(--amber);border-color:rgba(255,171,0,.1)}
|
|
123
|
+
.tag-track{background:var(--orange-dim);color:var(--orange);border-color:rgba(255,110,64,.1)}
|
|
124
|
+
.session-arrow{color:var(--text3);font-size:16px;transition:color .15s}
|
|
125
|
+
.session-card:hover .session-arrow{color:var(--cyan)}
|
|
126
|
+
|
|
127
|
+
/* Detail view */
|
|
128
|
+
.back-link{
|
|
129
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
130
|
+
font-size:12px;color:var(--text3);margin-bottom:20px;
|
|
131
|
+
padding:6px 0;transition:color .15s;
|
|
132
|
+
}
|
|
133
|
+
.back-link:hover{color:var(--cyan)}
|
|
134
|
+
.back-link svg{width:14px;height:14px}
|
|
135
|
+
|
|
136
|
+
.detail-header{
|
|
137
|
+
background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
|
138
|
+
padding:20px 24px;margin-bottom:20px;
|
|
139
|
+
border-left:3px solid var(--cyan);
|
|
140
|
+
}
|
|
141
|
+
.detail-id{font-family:var(--font-mono);font-size:13px;color:var(--cyan);font-weight:500}
|
|
142
|
+
.detail-meta{
|
|
143
|
+
display:flex;gap:20px;margin-top:10px;flex-wrap:wrap;
|
|
144
|
+
}
|
|
145
|
+
.detail-meta-item{font-size:11px;color:var(--text3);font-family:var(--font-mono)}
|
|
146
|
+
.detail-meta-item strong{color:var(--text2);font-weight:500}
|
|
147
|
+
|
|
148
|
+
/* Tabs */
|
|
149
|
+
.tabs{display:flex;gap:2px;margin-bottom:18px;padding:3px;background:var(--bg2);border-radius:var(--radius);border:1px solid var(--border);overflow-x:auto}
|
|
150
|
+
.tab{
|
|
151
|
+
padding:7px 16px;font-size:12px;font-weight:500;
|
|
152
|
+
border:none;border-radius:4px;background:transparent;
|
|
153
|
+
color:var(--text3);cursor:pointer;font-family:var(--font-sans);
|
|
154
|
+
transition:all .15s;white-space:nowrap;letter-spacing:.01em;
|
|
155
|
+
}
|
|
156
|
+
.tab:hover{color:var(--text2);background:var(--surface)}
|
|
157
|
+
.tab.active{
|
|
158
|
+
background:var(--cyan);color:var(--bg);font-weight:600;
|
|
159
|
+
box-shadow:0 0 12px rgba(0,229,255,.25);
|
|
160
|
+
}
|
|
161
|
+
.tab .count{font-weight:400;opacity:.7;margin-left:3px;font-size:11px}
|
|
162
|
+
|
|
163
|
+
/* Toolbar */
|
|
164
|
+
.toolbar{
|
|
165
|
+
display:flex;align-items:center;gap:14px;margin-bottom:18px;
|
|
166
|
+
padding:10px 16px;background:var(--bg2);border-radius:var(--radius);
|
|
167
|
+
border:1px solid var(--border);flex-wrap:wrap;
|
|
168
|
+
}
|
|
169
|
+
.toolbar label{
|
|
170
|
+
font-size:12px;color:var(--text2);display:flex;align-items:center;gap:6px;
|
|
171
|
+
font-family:var(--font-mono);cursor:pointer;
|
|
172
|
+
}
|
|
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
|
+
.toggle{
|
|
180
|
+
position:relative;width:32px;height:18px;
|
|
181
|
+
background:var(--surface3);border-radius:9px;cursor:pointer;
|
|
182
|
+
transition:background .15s;border:1px solid var(--border2);
|
|
183
|
+
}
|
|
184
|
+
.toggle.on{background:var(--red);border-color:var(--red)}
|
|
185
|
+
.toggle::after{
|
|
186
|
+
content:'';position:absolute;top:2px;left:2px;
|
|
187
|
+
width:12px;height:12px;border-radius:50%;background:#fff;
|
|
188
|
+
transition:transform .15s;
|
|
189
|
+
}
|
|
190
|
+
.toggle.on::after{transform:translateX(14px)}
|
|
191
|
+
|
|
192
|
+
/* Log entries */
|
|
193
|
+
.log-list{display:flex;flex-direction:column;gap:2px}
|
|
194
|
+
.log-entry{
|
|
195
|
+
display:grid;grid-template-columns:80px 1fr 90px 30px;
|
|
196
|
+
align-items:center;gap:0;
|
|
197
|
+
background:var(--surface);border:1px solid transparent;border-radius:var(--radius);
|
|
198
|
+
cursor:pointer;transition:all .15s;overflow:hidden;
|
|
199
|
+
animation:fadeSlideIn .3s ease-out both;
|
|
200
|
+
}
|
|
201
|
+
@keyframes fadeSlideIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
|
202
|
+
.log-entry:hover{
|
|
203
|
+
background:var(--surface2);border-color:var(--border2);
|
|
204
|
+
box-shadow:0 2px 8px rgba(0,0,0,.2);
|
|
205
|
+
}
|
|
206
|
+
.log-entry.expanded{border-color:var(--cyan-mid);background:var(--surface2)}
|
|
207
|
+
.log-type{
|
|
208
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
209
|
+
text-transform:uppercase;letter-spacing:.06em;
|
|
210
|
+
padding:0 14px;height:42px;display:flex;align-items:center;
|
|
211
|
+
border-right:1px solid var(--border);
|
|
212
|
+
}
|
|
213
|
+
.log-type-network{color:var(--cyan)}
|
|
214
|
+
.log-type-console{color:var(--green)}
|
|
215
|
+
.log-type-navigation{color:#7c4dff}
|
|
216
|
+
.log-type-track{color:var(--orange)}
|
|
217
|
+
.log-type-zustand{color:#e040fb}
|
|
218
|
+
.log-type-unknown{color:var(--text3)}
|
|
219
|
+
.log-summary{
|
|
220
|
+
padding:0 14px;height:42px;display:flex;align-items:center;
|
|
221
|
+
font-family:var(--font-mono);font-size:12px;color:var(--text);
|
|
222
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
223
|
+
}
|
|
224
|
+
.log-status{
|
|
225
|
+
padding:0 10px;height:42px;display:flex;align-items:center;justify-content:center;
|
|
226
|
+
}
|
|
227
|
+
.badge{
|
|
228
|
+
font-family:var(--font-mono);font-size:10px;font-weight:600;
|
|
229
|
+
padding:2px 8px;border-radius:3px;letter-spacing:.02em;
|
|
230
|
+
}
|
|
231
|
+
.badge-ok{background:var(--green-dim);color:var(--green)}
|
|
232
|
+
.badge-error{background:var(--red-dim);color:var(--red)}
|
|
233
|
+
.badge-warn{background:var(--amber-dim);color:var(--amber)}
|
|
234
|
+
.badge-info{background:var(--cyan-dim);color:var(--cyan)}
|
|
235
|
+
.log-expand{
|
|
236
|
+
padding:0 8px;height:42px;display:flex;align-items:center;justify-content:center;
|
|
237
|
+
color:var(--text3);font-size:10px;transition:color .15s;
|
|
238
|
+
}
|
|
239
|
+
.log-entry:hover .log-expand{color:var(--cyan)}
|
|
240
|
+
.log-json{
|
|
241
|
+
grid-column:1/-1;
|
|
242
|
+
border-top:1px solid var(--border);
|
|
243
|
+
overflow:hidden;
|
|
244
|
+
max-height:0;transition:max-height .3s ease-out;
|
|
245
|
+
}
|
|
246
|
+
.log-json.open{max-height:400px}
|
|
247
|
+
.log-json pre{
|
|
248
|
+
padding:14px 18px;font-family:var(--font-mono);font-size:11px;
|
|
249
|
+
line-height:1.6;color:var(--text2);white-space:pre-wrap;word-break:break-all;
|
|
250
|
+
overflow:auto;max-height:400px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Actions bar */
|
|
254
|
+
.actions{
|
|
255
|
+
display:flex;gap:8px;margin-top:20px;padding-top:16px;
|
|
256
|
+
border-top:1px solid var(--border);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Toast */
|
|
260
|
+
.toast{
|
|
261
|
+
position:fixed;bottom:24px;right:24px;
|
|
262
|
+
padding:10px 20px;
|
|
263
|
+
background:var(--cyan);color:var(--bg);
|
|
264
|
+
border-radius:var(--radius);font-size:13px;font-weight:600;
|
|
265
|
+
font-family:var(--font-sans);
|
|
266
|
+
box-shadow:0 4px 20px rgba(0,229,255,.3);
|
|
267
|
+
transform:translateY(80px);opacity:0;transition:all .3s cubic-bezier(.2,.9,.3,1);
|
|
268
|
+
z-index:1000;
|
|
269
|
+
}
|
|
270
|
+
.toast.show{transform:translateY(0);opacity:1}
|
|
271
|
+
|
|
272
|
+
/* Stagger animations */
|
|
273
|
+
.log-entry:nth-child(1){animation-delay:.0s}
|
|
274
|
+
.log-entry:nth-child(2){animation-delay:.02s}
|
|
275
|
+
.log-entry:nth-child(3){animation-delay:.04s}
|
|
276
|
+
.log-entry:nth-child(4){animation-delay:.06s}
|
|
277
|
+
.log-entry:nth-child(5){animation-delay:.08s}
|
|
278
|
+
.log-entry:nth-child(n+6){animation-delay:.1s}
|
|
279
|
+
|
|
280
|
+
/* Device info */
|
|
281
|
+
.device-info{
|
|
282
|
+
display:flex;gap:16px;align-items:center;flex-wrap:wrap;
|
|
283
|
+
margin-top:10px;padding-top:10px;border-top:1px solid var(--border);
|
|
284
|
+
}
|
|
285
|
+
.device-badge{
|
|
286
|
+
display:inline-flex;align-items:center;gap:4px;
|
|
287
|
+
font-family:var(--font-mono);font-size:11px;
|
|
288
|
+
padding:3px 10px;border-radius:4px;
|
|
289
|
+
background:var(--surface2);color:var(--text2);
|
|
290
|
+
border:1px solid var(--border);
|
|
291
|
+
}
|
|
292
|
+
.device-badge.platform-ios{border-color:rgba(255,255,255,.2);color:#fff}
|
|
293
|
+
.device-badge.platform-android{border-color:rgba(0,230,118,.2);color:var(--green)}
|
|
294
|
+
.live-badge{
|
|
295
|
+
display:inline-flex;align-items:center;gap:6px;
|
|
296
|
+
font-family:var(--font-mono);font-size:11px;
|
|
297
|
+
padding:3px 10px;border-radius:4px;
|
|
298
|
+
background:var(--cyan-dim);color:var(--cyan);
|
|
299
|
+
border:1px solid rgba(0,229,255,.15);
|
|
300
|
+
animation:livePulse 2s ease-in-out infinite;
|
|
301
|
+
}
|
|
302
|
+
@keyframes livePulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
303
|
+
.live-badge-dot{width:6px;height:6px;border-radius:50%;background:var(--cyan)}
|
|
304
|
+
|
|
305
|
+
/* Responsive */
|
|
306
|
+
@media(max-width:640px){
|
|
307
|
+
.container{padding:16px}
|
|
308
|
+
.session-card{grid-template-columns:1fr;gap:8px}
|
|
309
|
+
.log-entry{grid-template-columns:60px 1fr 70px 24px}
|
|
310
|
+
.detail-header{padding:14px 16px}
|
|
311
|
+
.tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
312
|
+
}
|
|
313
|
+
</style>
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
<header>
|
|
317
|
+
<div class="header-left">
|
|
318
|
+
<div class="pulse" id="pulseDot"></div>
|
|
319
|
+
<h1>Debug Toolkit <span>Console</span></h1>
|
|
320
|
+
</div>
|
|
321
|
+
<div class="header-right">
|
|
322
|
+
<span class="header-meta" id="status"></span>
|
|
323
|
+
<button class="btn" onclick="refresh()">
|
|
324
|
+
<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
|
+
Refresh
|
|
326
|
+
</button>
|
|
327
|
+
</div>
|
|
328
|
+
</header>
|
|
329
|
+
<div class="container" id="app"></div>
|
|
330
|
+
<div class="toast" id="toast"></div>
|
|
331
|
+
|
|
332
|
+
<script>
|
|
333
|
+
(function() {
|
|
334
|
+
'use strict';
|
|
335
|
+
|
|
336
|
+
var app = document.getElementById('app');
|
|
337
|
+
var statusEl = document.getElementById('status');
|
|
338
|
+
var toastEl = document.getElementById('toast');
|
|
339
|
+
var pulseDot = document.getElementById('pulseDot');
|
|
340
|
+
var currentSession = null;
|
|
341
|
+
var expandedRows = {};
|
|
342
|
+
var authToken = null;
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
var params = new URLSearchParams(location.search);
|
|
346
|
+
authToken = params.get('token') || sessionStorage.getItem('debugToolkitToken');
|
|
347
|
+
if (authToken) sessionStorage.setItem('debugToolkitToken', authToken);
|
|
348
|
+
} catch {
|
|
349
|
+
authToken = null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function withAuth(path) {
|
|
353
|
+
if (!authToken) return path;
|
|
354
|
+
var join = path.indexOf('?') >= 0 ? '&' : '?';
|
|
355
|
+
return path + join + 'token=' + encodeURIComponent(authToken);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function api(path) {
|
|
359
|
+
return fetch(withAuth(path)).then(function(r) { return r.json(); });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function showToast(msg) {
|
|
363
|
+
toastEl.textContent = msg;
|
|
364
|
+
toastEl.classList.add('show');
|
|
365
|
+
setTimeout(function() { toastEl.classList.remove('show'); }, 2000);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function escapeHtml(s) {
|
|
369
|
+
var d = document.createElement('div');
|
|
370
|
+
d.textContent = s;
|
|
371
|
+
return d.innerHTML;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function formatTime(iso) {
|
|
375
|
+
if (!iso) return '-';
|
|
376
|
+
try {
|
|
377
|
+
var d = new Date(iso);
|
|
378
|
+
var pad = function(n) { return String(n).padStart(2, '0'); };
|
|
379
|
+
return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate()) +
|
|
380
|
+
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
|
381
|
+
} catch { return iso; }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function formatTimeShort(iso) {
|
|
385
|
+
if (!iso) return '';
|
|
386
|
+
try {
|
|
387
|
+
var d = new Date(iso);
|
|
388
|
+
return d.toLocaleTimeString();
|
|
389
|
+
} catch { return iso; }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function statusBadge(entry) {
|
|
393
|
+
if (!entry || typeof entry !== 'object') return '<span class="badge badge-info">-</span>';
|
|
394
|
+
if (entry.error) return '<span class="badge badge-error">ERR</span>';
|
|
395
|
+
if (entry.response) {
|
|
396
|
+
var s = entry.response.status;
|
|
397
|
+
if (s >= 500) return '<span class="badge badge-error">' + s + '</span>';
|
|
398
|
+
if (s >= 400) return '<span class="badge badge-warn">' + s + '</span>';
|
|
399
|
+
if (s >= 200 && s < 300) return '<span class="badge badge-ok">' + s + '</span>';
|
|
400
|
+
return '<span class="badge badge-info">' + s + '</span>';
|
|
401
|
+
}
|
|
402
|
+
if (entry.level === 'error') return '<span class="badge badge-error">ERR</span>';
|
|
403
|
+
if (entry.level === 'warn') return '<span class="badge badge-warn">WRN</span>';
|
|
404
|
+
return '<span class="badge badge-ok">OK</span>';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function summarize(entry) {
|
|
408
|
+
if (!entry || typeof entry !== 'object') return escapeHtml(String(entry));
|
|
409
|
+
if (entry.method && entry.url) return escapeHtml(entry.method + ' ' + entry.url);
|
|
410
|
+
if (entry.level && entry.data) return escapeHtml((Array.isArray(entry.data) ? entry.data.join(' ') : String(entry.data)).substring(0, 120));
|
|
411
|
+
if (entry.path) return escapeHtml(entry.path);
|
|
412
|
+
if (entry.event) return escapeHtml(entry.event);
|
|
413
|
+
if (entry.action) return escapeHtml(entry.action);
|
|
414
|
+
return escapeHtml(JSON.stringify(entry).substring(0, 120));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function getLogType(entry) {
|
|
418
|
+
if (!entry || typeof entry !== 'object') return 'unknown';
|
|
419
|
+
if (entry.method && entry.url) return 'network';
|
|
420
|
+
if (entry.level && entry.data !== undefined) return 'console';
|
|
421
|
+
if (entry.path) return 'navigation';
|
|
422
|
+
if (entry.event) return 'track';
|
|
423
|
+
if (entry.action) return 'zustand';
|
|
424
|
+
return 'unknown';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function toKeyPart(value) {
|
|
428
|
+
return String(value).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function stringifyForKey(value) {
|
|
432
|
+
try {
|
|
433
|
+
return JSON.stringify(value).slice(0, 80);
|
|
434
|
+
} catch {
|
|
435
|
+
return String(value);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getLogEntryKey(entry, type, index) {
|
|
440
|
+
var logType = toKeyPart(type || getLogType(entry));
|
|
441
|
+
if (entry && typeof entry === 'object') {
|
|
442
|
+
if (entry.id !== undefined && entry.id !== null) {
|
|
443
|
+
return logType + '-id-' + toKeyPart(entry.id);
|
|
444
|
+
}
|
|
445
|
+
if (entry.timestamp !== undefined && entry.timestamp !== null) {
|
|
446
|
+
return logType + '-ts-' + toKeyPart(entry.timestamp) + '-' + index;
|
|
447
|
+
}
|
|
448
|
+
if (entry.request && typeof entry.request === 'object' && entry.request.url) {
|
|
449
|
+
return logType + '-req-' + toKeyPart((entry.request.method || '') + '-' + entry.request.url + '-' + index);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return logType + '-fallback-' + index + '-' + toKeyPart(stringifyForKey(entry));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// --- Views ---
|
|
456
|
+
|
|
457
|
+
function renderList() {
|
|
458
|
+
currentSession = null;
|
|
459
|
+
expandedRows = {};
|
|
460
|
+
pulseDot.style.background = 'var(--text3)';
|
|
461
|
+
pulseDot.style.boxShadow = 'none';
|
|
462
|
+
statusEl.textContent = 'fetching...';
|
|
463
|
+
api('/sessions').then(function(data) {
|
|
464
|
+
statusEl.textContent = '';
|
|
465
|
+
pulseDot.style.background = 'var(--cyan)';
|
|
466
|
+
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
467
|
+
var sessions = data.sessions || [];
|
|
468
|
+
if (!sessions.length) {
|
|
469
|
+
app.innerHTML =
|
|
470
|
+
'<div class="empty">' +
|
|
471
|
+
'<div class="empty-icon">_</div>' +
|
|
472
|
+
'<p>No sessions received yet.</p>' +
|
|
473
|
+
'<p style="margin-top:12px;font-size:13px">POST a report to <code>/report</code> to see data here.</p>' +
|
|
474
|
+
'</div>';
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
var html = '<div class="section-title">Sessions <span style="color:var(--text3);font-weight:400">(' + sessions.length + ')</span></div>';
|
|
478
|
+
html += '<div class="session-grid">';
|
|
479
|
+
sessions.forEach(function(s, i) {
|
|
480
|
+
var lc = s.logCount || {};
|
|
481
|
+
html += '<div class="session-card" data-sid="' + escapeHtml(s.sessionId) + '" style="animation-delay:' + (i * 40) + 'ms" onclick="location.hash=\'session/' + encodeURIComponent(s.sessionId) + '\'">';
|
|
482
|
+
html += '<div><div class="session-id">' + escapeHtml(s.sessionId) + '</div>';
|
|
483
|
+
html += '<div class="session-time">' + formatTime(s.receivedAt) + '</div></div>';
|
|
484
|
+
html += '<div class="session-tags">' + renderSessionTags(lc) + '</div>';
|
|
485
|
+
html += '<div class="session-arrow">›</div>';
|
|
486
|
+
html += '</div>';
|
|
487
|
+
});
|
|
488
|
+
html += '</div>';
|
|
489
|
+
app.innerHTML = html;
|
|
490
|
+
}).catch(function(err) {
|
|
491
|
+
statusEl.textContent = '';
|
|
492
|
+
pulseDot.style.background = 'var(--red)';
|
|
493
|
+
pulseDot.style.boxShadow = '0 0 8px var(--red)';
|
|
494
|
+
app.innerHTML = '<div class="empty" style="color:var(--red)">Connection failed: ' + escapeHtml(err.message) + '</div>';
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function renderDetail(sessionId) {
|
|
499
|
+
expandedRows = {};
|
|
500
|
+
statusEl.textContent = 'loading...';
|
|
501
|
+
api('/sessions/' + encodeURIComponent(sessionId)).then(function(data) {
|
|
502
|
+
statusEl.textContent = '';
|
|
503
|
+
pulseDot.style.background = 'var(--cyan)';
|
|
504
|
+
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
505
|
+
currentSession = data;
|
|
506
|
+
var report = data.report || {};
|
|
507
|
+
var logs = report.logs || {};
|
|
508
|
+
var logTypes = Object.keys(logs);
|
|
509
|
+
|
|
510
|
+
var html = '';
|
|
511
|
+
|
|
512
|
+
// Back link
|
|
513
|
+
html += '<a href="#" class="back-link" onclick="location.hash=\'\';return false">';
|
|
514
|
+
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 sessions</a>';
|
|
516
|
+
|
|
517
|
+
// Session header card
|
|
518
|
+
html += '<div class="detail-header">';
|
|
519
|
+
html += '<div class="detail-id">' + escapeHtml(data.sessionId) + '</div>';
|
|
520
|
+
html += '<div class="detail-meta">';
|
|
521
|
+
html += '<span class="detail-meta-item"><strong>Received</strong> ' + formatTime(data.receivedAt) + '</span>';
|
|
522
|
+
var totalLogs = Object.values(data.logCount || {}).reduce(function(a, b) { return a + b; }, 0);
|
|
523
|
+
html += '<span class="detail-meta-item" data-type="Entries"><strong>Entries</strong> ' + totalLogs + '</span>';
|
|
524
|
+
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>';
|
|
526
|
+
});
|
|
527
|
+
html += '</div>';
|
|
528
|
+
|
|
529
|
+
// Device info
|
|
530
|
+
var device = report.device;
|
|
531
|
+
if (device && typeof device === 'object') {
|
|
532
|
+
html += '<div class="device-info">';
|
|
533
|
+
var pClass = device.platform === 'ios' ? 'platform-ios' : device.platform === 'android' ? 'platform-android' : '';
|
|
534
|
+
html += '<span class="device-badge ' + pClass + '">' + escapeHtml((device.platform || 'unknown').toUpperCase()) + '</span>';
|
|
535
|
+
if (device.model) html += '<span class="device-badge">' + escapeHtml(device.model) + '</span>';
|
|
536
|
+
if (device.osVersion) html += '<span class="device-badge">OS ' + escapeHtml(device.osVersion) + '</span>';
|
|
537
|
+
if (device.appVersion) html += '<span class="device-badge">v' + escapeHtml(device.appVersion) + '</span>';
|
|
538
|
+
if (sseConnected) html += '<span class="live-badge"><span class="live-badge-dot"></span>LIVE</span>';
|
|
539
|
+
html += '</div>';
|
|
540
|
+
} else if (sseConnected) {
|
|
541
|
+
html += '<div class="device-info"><span class="live-badge"><span class="live-badge-dot"></span>LIVE</span></div>';
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
html += '</div>';
|
|
545
|
+
|
|
546
|
+
// Tabs
|
|
547
|
+
html += '<div class="tabs">';
|
|
548
|
+
html += '<button class="tab active" data-type="" onclick="filterType(this,\'\')">All<span class="count">' + totalLogs + '</span></button>';
|
|
549
|
+
logTypes.forEach(function(t) {
|
|
550
|
+
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>';
|
|
552
|
+
});
|
|
553
|
+
html += '</div>';
|
|
554
|
+
|
|
555
|
+
// Toolbar
|
|
556
|
+
html += '<div class="toolbar">';
|
|
557
|
+
html += '<label>Failed only <div class="toggle" id="failedToggle" onclick="toggleFailed()"></div></label>';
|
|
558
|
+
html += '<label>Limit <input type="number" id="limitInput" value="50" min="1" max="500"></label>';
|
|
559
|
+
html += '</div>';
|
|
560
|
+
|
|
561
|
+
// Log container
|
|
562
|
+
html += '<div id="logsContainer"></div>';
|
|
563
|
+
|
|
564
|
+
// Actions
|
|
565
|
+
html += '<div class="actions">';
|
|
566
|
+
html += '<button class="btn" onclick="copyJSON()">';
|
|
567
|
+
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
|
+
html += 'Copy JSON</button>';
|
|
569
|
+
html += '</div>';
|
|
570
|
+
|
|
571
|
+
app.innerHTML = html;
|
|
572
|
+
document.getElementById('limitInput').addEventListener('change', applyFilters);
|
|
573
|
+
renderLogs(logs, '', 50, false);
|
|
574
|
+
}).catch(function(err) {
|
|
575
|
+
statusEl.textContent = '';
|
|
576
|
+
app.innerHTML = '<div class="empty" style="color:var(--red)">Failed to load: ' + escapeHtml(err.message) + '</div>';
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function renderLogs(logs, type, limit, failedOnly) {
|
|
581
|
+
var entries = [];
|
|
582
|
+
if (type && logs[type]) {
|
|
583
|
+
entries = Array.isArray(logs[type])
|
|
584
|
+
? logs[type].map(function(entry) { return { type: type, entry: entry }; })
|
|
585
|
+
: [];
|
|
586
|
+
} else {
|
|
587
|
+
Object.entries(logs).forEach(function(logGroup) {
|
|
588
|
+
var logType = logGroup[0];
|
|
589
|
+
var value = logGroup[1];
|
|
590
|
+
if (Array.isArray(value)) {
|
|
591
|
+
value.forEach(function(entry) {
|
|
592
|
+
entries.push({ type: logType, entry: entry });
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (failedOnly) {
|
|
599
|
+
entries = entries.filter(function(item) { return isFailedEntry(item.entry); });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
entries = entries.slice(-limit);
|
|
603
|
+
|
|
604
|
+
var container = document.getElementById('logsContainer');
|
|
605
|
+
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">No logs match filters.</p></div>';
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
var html = '<div class="log-list">';
|
|
611
|
+
entries.forEach(function(item, i) {
|
|
612
|
+
var entry = item.entry;
|
|
613
|
+
var rowId = getLogEntryKey(entry, item.type, i);
|
|
614
|
+
var lt = item.type || getLogType(entry);
|
|
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>';
|
|
624
|
+
});
|
|
625
|
+
html += '</div>';
|
|
626
|
+
container.innerHTML = html;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// --- Global handlers ---
|
|
630
|
+
|
|
631
|
+
function readVisibleLogOptions() {
|
|
632
|
+
return {
|
|
633
|
+
type: window._currentFilterType || '',
|
|
634
|
+
failedOnly: window._failedOnly || false,
|
|
635
|
+
limit: document.getElementById('limitInput') ? parseInt(document.getElementById('limitInput').value, 10) || 50 : 50,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function rerenderVisibleLogs() {
|
|
640
|
+
if (!currentSession) return;
|
|
641
|
+
var logs = currentSession.report ? currentSession.report.logs : {};
|
|
642
|
+
var options = readVisibleLogOptions();
|
|
643
|
+
renderLogs(logs, options.type, options.limit, options.failedOnly);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function refreshCurrentSession() {
|
|
647
|
+
if (!currentSession) {
|
|
648
|
+
renderList();
|
|
649
|
+
return Promise.resolve();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
statusEl.textContent = 'refreshing...';
|
|
653
|
+
return api('/sessions/' + encodeURIComponent(currentSession.sessionId)).then(function(data) {
|
|
654
|
+
statusEl.textContent = '';
|
|
655
|
+
if (!data) return;
|
|
656
|
+
currentSession.report = data.report;
|
|
657
|
+
currentSession.logCount = data.logCount;
|
|
658
|
+
currentSession.receivedAt = data.receivedAt;
|
|
659
|
+
rerenderVisibleLogs();
|
|
660
|
+
updateTabCounts();
|
|
661
|
+
}).catch(function(err) {
|
|
662
|
+
statusEl.textContent = '';
|
|
663
|
+
showToast('Refresh failed: ' + err.message);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function refreshCurrentView() {
|
|
668
|
+
return refreshCurrentSession();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
window.refresh = function() { refreshCurrentView(); };
|
|
672
|
+
|
|
673
|
+
window.filterType = function(btn, type) {
|
|
674
|
+
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
|
675
|
+
btn.classList.add('active');
|
|
676
|
+
window._currentFilterType = type;
|
|
677
|
+
applyFilters();
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
window.toggleFailed = function() {
|
|
681
|
+
var el = document.getElementById('failedToggle');
|
|
682
|
+
window._failedOnly = !window._failedOnly;
|
|
683
|
+
if (window._failedOnly) { el.classList.add('on'); } else { el.classList.remove('on'); }
|
|
684
|
+
applyFilters();
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
window.applyFilters = function() {
|
|
688
|
+
if (!currentSession) return;
|
|
689
|
+
expandedRows = {};
|
|
690
|
+
rerenderVisibleLogs();
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
window.toggleRow = function(rowId) {
|
|
694
|
+
var entry = document.getElementById('entry-' + rowId);
|
|
695
|
+
var json = document.getElementById('json-' + rowId);
|
|
696
|
+
var expand = entry ? entry.querySelector('.log-expand') : null;
|
|
697
|
+
if (!entry || !json) return;
|
|
698
|
+
expandedRows[rowId] = !expandedRows[rowId];
|
|
699
|
+
if (expandedRows[rowId]) {
|
|
700
|
+
entry.classList.add('expanded');
|
|
701
|
+
json.classList.add('open');
|
|
702
|
+
if (expand) expand.innerHTML = '▼';
|
|
703
|
+
} else {
|
|
704
|
+
entry.classList.remove('expanded');
|
|
705
|
+
json.classList.remove('open');
|
|
706
|
+
if (expand) expand.innerHTML = '▶';
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
window.copyJSON = function() {
|
|
711
|
+
if (!currentSession) return;
|
|
712
|
+
var text = JSON.stringify(currentSession.report, null, 2);
|
|
713
|
+
navigator.clipboard.writeText(text).then(function() {
|
|
714
|
+
showToast('Copied to clipboard');
|
|
715
|
+
}).catch(function() {
|
|
716
|
+
var ta = document.createElement('textarea');
|
|
717
|
+
ta.value = text;
|
|
718
|
+
ta.style.position = 'fixed';
|
|
719
|
+
ta.style.opacity = '0';
|
|
720
|
+
document.body.appendChild(ta);
|
|
721
|
+
ta.select();
|
|
722
|
+
document.execCommand('copy');
|
|
723
|
+
document.body.removeChild(ta);
|
|
724
|
+
showToast('Copied to clipboard');
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// --- Routing ---
|
|
729
|
+
|
|
730
|
+
window._currentFilterType = '';
|
|
731
|
+
window._failedOnly = false;
|
|
732
|
+
|
|
733
|
+
function route() {
|
|
734
|
+
var hash = location.hash.replace('#', '');
|
|
735
|
+
if (hash.startsWith('session/')) {
|
|
736
|
+
renderDetail(decodeURIComponent(hash.substring(8)));
|
|
737
|
+
} else {
|
|
738
|
+
renderList();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// --- Incremental DOM updates ---
|
|
743
|
+
|
|
744
|
+
function isFailedEntry(e) {
|
|
745
|
+
return e && typeof e === 'object' && (
|
|
746
|
+
Boolean(e.error) ||
|
|
747
|
+
e.level === 'error' ||
|
|
748
|
+
(e.response && (e.response.success === false || e.response.status >= 400))
|
|
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
|
+
}
|
|
772
|
+
|
|
773
|
+
var lc = payload.logCount || {};
|
|
774
|
+
var card = document.createElement('div');
|
|
775
|
+
card.className = 'session-card';
|
|
776
|
+
card.setAttribute('data-sid', sid);
|
|
777
|
+
card.setAttribute('onclick', "location.hash='session/" + encodeURIComponent(sid) + "'");
|
|
778
|
+
var html = '<div><div class="session-id">' + escapeHtml(sid) + '</div>';
|
|
779
|
+
html += '<div class="session-time">just now</div></div>';
|
|
780
|
+
html += '<div class="session-tags">' + renderSessionTags(lc) + '</div>';
|
|
781
|
+
html += '<div class="session-arrow">›</div>';
|
|
782
|
+
card.innerHTML = html;
|
|
783
|
+
grid.prepend(card);
|
|
784
|
+
|
|
785
|
+
// Update session count
|
|
786
|
+
var titleEl = document.querySelector('.section-title');
|
|
787
|
+
if (titleEl) {
|
|
788
|
+
var count = grid.querySelectorAll('.session-card').length;
|
|
789
|
+
titleEl.innerHTML = 'Sessions <span style="color:var(--text3);font-weight:400">(' + count + ')</span>';
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function buildLogEntryHtml(entry, rowId, type) {
|
|
794
|
+
var lt = type || getLogType(entry);
|
|
795
|
+
var typeClass = toKeyPart(lt);
|
|
796
|
+
var html = '<div class="log-type log-type-' + typeClass + '">' + escapeHtml(lt.substring(0,4)) + '</div>';
|
|
797
|
+
html += '<div class="log-summary">' + summarize(entry) + '</div>';
|
|
798
|
+
html += '<div class="log-status">' + statusBadge(entry) + '</div>';
|
|
799
|
+
html += '<div class="log-expand">▶</div>';
|
|
800
|
+
html += '<div class="log-json" id="json-' + rowId + '"><pre>' + escapeHtml(JSON.stringify(entry, null, 2)) + '</pre></div>';
|
|
801
|
+
return html;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function appendDeltaLogs(deltaLogs) {
|
|
805
|
+
var container = document.getElementById('logsContainer');
|
|
806
|
+
if (!container) return;
|
|
807
|
+
|
|
808
|
+
var list = container.querySelector('.log-list');
|
|
809
|
+
if (!list) {
|
|
810
|
+
applyFilters();
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
var type = window._currentFilterType || '';
|
|
815
|
+
var failedOnly = window._failedOnly || false;
|
|
816
|
+
var limit = document.getElementById('limitInput') ? parseInt(document.getElementById('limitInput').value, 10) || 50 : 50;
|
|
817
|
+
var allNewEntries = [];
|
|
818
|
+
|
|
819
|
+
Object.entries(deltaLogs).forEach(function(entry) {
|
|
820
|
+
var t = entry[0];
|
|
821
|
+
var entries = entry[1];
|
|
822
|
+
if (!Array.isArray(entries)) return;
|
|
823
|
+
entries.forEach(function(e) {
|
|
824
|
+
if (failedOnly && !isFailedEntry(e)) return;
|
|
825
|
+
if (type && t !== type) return;
|
|
826
|
+
allNewEntries.push({ type: t, entry: e });
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
var count = list.querySelectorAll('.log-entry').length;
|
|
831
|
+
allNewEntries.forEach(function(item) {
|
|
832
|
+
var entry = item.entry;
|
|
833
|
+
var rowId = getLogEntryKey(entry, item.type, count++);
|
|
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);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
// Trim from top if over limit, skip expanded entries
|
|
843
|
+
var entries = list.querySelectorAll('.log-entry');
|
|
844
|
+
while (entries.length > limit) {
|
|
845
|
+
var first = entries[0];
|
|
846
|
+
if (first && first.classList.contains('expanded')) break;
|
|
847
|
+
list.removeChild(first);
|
|
848
|
+
entries = list.querySelectorAll('.log-entry');
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function updateTabCounts() {
|
|
853
|
+
if (!currentSession) return;
|
|
854
|
+
var logs = currentSession.report ? currentSession.report.logs : {};
|
|
855
|
+
var totalLogs = Object.values(logs).reduce(function(a, v) { return a + (Array.isArray(v) ? v.length : 0); }, 0);
|
|
856
|
+
|
|
857
|
+
document.querySelectorAll('.tab').forEach(function(tab) {
|
|
858
|
+
var t = tab.getAttribute('data-type') || '';
|
|
859
|
+
var countEl = tab.querySelector('.count');
|
|
860
|
+
var c = t ? (logs[t] ? logs[t].length : 0) : totalLogs;
|
|
861
|
+
if (countEl) countEl.textContent = c;
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
document.querySelectorAll('.detail-meta-item[data-type]').forEach(function(el) {
|
|
865
|
+
var t = el.getAttribute('data-type');
|
|
866
|
+
var strong = el.querySelector('strong');
|
|
867
|
+
if (!strong) return;
|
|
868
|
+
var c = t === 'Entries' ? totalLogs : (logs[t] ? logs[t].length : 0);
|
|
869
|
+
el.innerHTML = '<strong>' + strong.textContent + '</strong> ' + c;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// --- SSE ---
|
|
874
|
+
|
|
875
|
+
var sseConnected = false;
|
|
876
|
+
var eventSource = null;
|
|
877
|
+
|
|
878
|
+
function connectSSE() {
|
|
879
|
+
if (eventSource) { try { eventSource.close(); } catch {} }
|
|
880
|
+
eventSource = new EventSource(withAuth('/events'));
|
|
881
|
+
|
|
882
|
+
eventSource.addEventListener('logs', function(e) {
|
|
883
|
+
try {
|
|
884
|
+
var payload = JSON.parse(e.data);
|
|
885
|
+
sseConnected = true;
|
|
886
|
+
pulseDot.style.background = 'var(--cyan)';
|
|
887
|
+
pulseDot.style.boxShadow = '0 0 8px var(--cyan),0 0 20px rgba(0,229,255,.3)';
|
|
888
|
+
|
|
889
|
+
// Session list page — append new session card
|
|
890
|
+
if (!currentSession) {
|
|
891
|
+
appendSessionCard(payload);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Detail page — merge delta if same session
|
|
896
|
+
if (payload.sessionId === currentSession.sessionId) {
|
|
897
|
+
if (payload.type === 'delta' && payload.delta) {
|
|
898
|
+
var deltaLogs = payload.delta.logs || {};
|
|
899
|
+
var report = currentSession.report || { version: 2, logs: {} };
|
|
900
|
+
if (!report.logs) report.logs = {};
|
|
901
|
+
|
|
902
|
+
Object.entries(deltaLogs).forEach(function(entry) {
|
|
903
|
+
var type = entry[0];
|
|
904
|
+
var entries = entry[1];
|
|
905
|
+
if (!Array.isArray(entries)) return;
|
|
906
|
+
if (!report.logs[type]) report.logs[type] = [];
|
|
907
|
+
report.logs[type] = report.logs[type].concat(entries);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
currentSession.report = report;
|
|
911
|
+
if (payload.logCount) currentSession.logCount = payload.logCount;
|
|
912
|
+
appendDeltaLogs(deltaLogs);
|
|
913
|
+
updateTabCounts();
|
|
914
|
+
} else if (payload.type === 'full') {
|
|
915
|
+
refreshCurrentSession();
|
|
916
|
+
}
|
|
917
|
+
} else if (!location.hash.startsWith('session/')) {
|
|
918
|
+
appendSessionCard(payload);
|
|
919
|
+
}
|
|
920
|
+
} catch {}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
eventSource.onerror = function() {
|
|
924
|
+
sseConnected = false;
|
|
925
|
+
pulseDot.style.background = 'var(--amber)';
|
|
926
|
+
pulseDot.style.boxShadow = '0 0 8px var(--amber)';
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
window.addEventListener('hashchange', route);
|
|
931
|
+
connectSSE();
|
|
932
|
+
route();
|
|
933
|
+
})();
|
|
934
|
+
</script>
|
|
935
|
+
</body>
|
|
936
|
+
</html>
|