flow-debugger 1.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/PORTFOLIO_README_SECTION.md +177 -0
- package/README.md +251 -0
- package/dashboard/app.js +339 -0
- package/dashboard/index.html +168 -0
- package/dashboard/style.css +846 -0
- package/dist/cjs/core/Analytics.js +174 -0
- package/dist/cjs/core/Analytics.js.map +1 -0
- package/dist/cjs/core/Classifier.js +66 -0
- package/dist/cjs/core/Classifier.js.map +1 -0
- package/dist/cjs/core/HealthMonitor.js +79 -0
- package/dist/cjs/core/HealthMonitor.js.map +1 -0
- package/dist/cjs/core/RootCause.js +89 -0
- package/dist/cjs/core/RootCause.js.map +1 -0
- package/dist/cjs/core/Sampler.js +34 -0
- package/dist/cjs/core/Sampler.js.map +1 -0
- package/dist/cjs/core/Timeline.js +90 -0
- package/dist/cjs/core/Timeline.js.map +1 -0
- package/dist/cjs/core/TraceEngine.js +222 -0
- package/dist/cjs/core/TraceEngine.js.map +1 -0
- package/dist/cjs/core/types.js +21 -0
- package/dist/cjs/core/types.js.map +1 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/integrations/axios.js +136 -0
- package/dist/cjs/integrations/axios.js.map +1 -0
- package/dist/cjs/integrations/fetch.js +153 -0
- package/dist/cjs/integrations/fetch.js.map +1 -0
- package/dist/cjs/integrations/mongo.js +111 -0
- package/dist/cjs/integrations/mongo.js.map +1 -0
- package/dist/cjs/integrations/mysql.js +212 -0
- package/dist/cjs/integrations/mysql.js.map +1 -0
- package/dist/cjs/integrations/postgres.js +182 -0
- package/dist/cjs/integrations/postgres.js.map +1 -0
- package/dist/cjs/integrations/redis.js +105 -0
- package/dist/cjs/integrations/redis.js.map +1 -0
- package/dist/cjs/middleware/express.js +255 -0
- package/dist/cjs/middleware/express.js.map +1 -0
- package/dist/esm/core/Analytics.js +170 -0
- package/dist/esm/core/Analytics.js.map +1 -0
- package/dist/esm/core/Classifier.js +61 -0
- package/dist/esm/core/Classifier.js.map +1 -0
- package/dist/esm/core/HealthMonitor.js +75 -0
- package/dist/esm/core/HealthMonitor.js.map +1 -0
- package/dist/esm/core/RootCause.js +86 -0
- package/dist/esm/core/RootCause.js.map +1 -0
- package/dist/esm/core/Sampler.js +30 -0
- package/dist/esm/core/Sampler.js.map +1 -0
- package/dist/esm/core/Timeline.js +86 -0
- package/dist/esm/core/Timeline.js.map +1 -0
- package/dist/esm/core/TraceEngine.js +217 -0
- package/dist/esm/core/TraceEngine.js.map +1 -0
- package/dist/esm/core/types.js +18 -0
- package/dist/esm/core/types.js.map +1 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/integrations/axios.js +133 -0
- package/dist/esm/integrations/axios.js.map +1 -0
- package/dist/esm/integrations/fetch.js +149 -0
- package/dist/esm/integrations/fetch.js.map +1 -0
- package/dist/esm/integrations/mongo.js +107 -0
- package/dist/esm/integrations/mongo.js.map +1 -0
- package/dist/esm/integrations/mysql.js +209 -0
- package/dist/esm/integrations/mysql.js.map +1 -0
- package/dist/esm/integrations/postgres.js +179 -0
- package/dist/esm/integrations/postgres.js.map +1 -0
- package/dist/esm/integrations/redis.js +102 -0
- package/dist/esm/integrations/redis.js.map +1 -0
- package/dist/esm/middleware/express.js +219 -0
- package/dist/esm/middleware/express.js.map +1 -0
- package/dist/types/core/Analytics.d.ts +35 -0
- package/dist/types/core/Analytics.d.ts.map +1 -0
- package/dist/types/core/Classifier.d.ts +21 -0
- package/dist/types/core/Classifier.d.ts.map +1 -0
- package/dist/types/core/HealthMonitor.d.ts +14 -0
- package/dist/types/core/HealthMonitor.d.ts.map +1 -0
- package/dist/types/core/RootCause.d.ts +12 -0
- package/dist/types/core/RootCause.d.ts.map +1 -0
- package/dist/types/core/Sampler.d.ts +13 -0
- package/dist/types/core/Sampler.d.ts.map +1 -0
- package/dist/types/core/Timeline.d.ts +22 -0
- package/dist/types/core/Timeline.d.ts.map +1 -0
- package/dist/types/core/TraceEngine.d.ts +47 -0
- package/dist/types/core/TraceEngine.d.ts.map +1 -0
- package/dist/types/core/types.d.ts +118 -0
- package/dist/types/core/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/integrations/axios.d.ts +22 -0
- package/dist/types/integrations/axios.d.ts.map +1 -0
- package/dist/types/integrations/fetch.d.ts +25 -0
- package/dist/types/integrations/fetch.d.ts.map +1 -0
- package/dist/types/integrations/mongo.d.ts +26 -0
- package/dist/types/integrations/mongo.d.ts.map +1 -0
- package/dist/types/integrations/mysql.d.ts +20 -0
- package/dist/types/integrations/mysql.d.ts.map +1 -0
- package/dist/types/integrations/postgres.d.ts +20 -0
- package/dist/types/integrations/postgres.d.ts.map +1 -0
- package/dist/types/integrations/redis.d.ts +20 -0
- package/dist/types/integrations/redis.d.ts.map +1 -0
- package/dist/types/middleware/express.d.ts +39 -0
- package/dist/types/middleware/express.d.ts.map +1 -0
- package/example/server.ts +234 -0
- package/jest.config.js +8 -0
- package/package.json +110 -0
- package/portfolio-repo/APIRESPONSE DASH.png +0 -0
- package/portfolio-repo/PAYLOAD.png +0 -0
- package/portfolio-repo/README.md +182 -0
- package/src/core/Analytics.ts +209 -0
- package/src/core/Classifier.ts +82 -0
- package/src/core/HealthMonitor.ts +92 -0
- package/src/core/RootCause.ts +105 -0
- package/src/core/Sampler.ts +35 -0
- package/src/core/Timeline.ts +108 -0
- package/src/core/TraceEngine.ts +266 -0
- package/src/core/types.ts +170 -0
- package/src/index.ts +42 -0
- package/src/integrations/axios.ts +164 -0
- package/src/integrations/fetch.ts +172 -0
- package/src/integrations/mongo.ts +130 -0
- package/src/integrations/mysql.ts +239 -0
- package/src/integrations/postgres.ts +217 -0
- package/src/integrations/redis.ts +122 -0
- package/src/middleware/express.ts +264 -0
- package/tests/Analytics.test.ts +136 -0
- package/tests/Classifier.test.ts +57 -0
- package/tests/RootCause.test.ts +69 -0
- package/tests/TraceEngine.test.ts +110 -0
- package/tsconfig.cjs.json +9 -0
- package/tsconfig.esm.json +9 -0
- package/tsconfig.json +31 -0
- package/tsconfig.types.json +8 -0
package/dashboard/app.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// flow-debugger — Dashboard App
|
|
3
|
+
// Fetches analytics from /__debugger API and renders live UI
|
|
4
|
+
// ─────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
const API_URL = '/__debugger';
|
|
7
|
+
let autoRefresh = true;
|
|
8
|
+
let refreshInterval = null;
|
|
9
|
+
|
|
10
|
+
// ─── Init ─────────────────────────────────────────────────
|
|
11
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
12
|
+
refreshData();
|
|
13
|
+
startAutoRefresh();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function startAutoRefresh() {
|
|
17
|
+
if (refreshInterval) clearInterval(refreshInterval);
|
|
18
|
+
refreshInterval = setInterval(refreshData, 3000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toggleAutoRefresh() {
|
|
22
|
+
autoRefresh = !autoRefresh;
|
|
23
|
+
const btn = document.getElementById('autoRefreshBtn');
|
|
24
|
+
if (autoRefresh) {
|
|
25
|
+
btn.textContent = 'Auto: ON';
|
|
26
|
+
startAutoRefresh();
|
|
27
|
+
} else {
|
|
28
|
+
btn.textContent = 'Auto: OFF';
|
|
29
|
+
if (refreshInterval) clearInterval(refreshInterval);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function refreshData() {
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(API_URL);
|
|
36
|
+
if (!res.ok) throw new Error('API error');
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
renderDashboard(data);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
document.getElementById('statusBadge').textContent = '● OFFLINE';
|
|
41
|
+
document.getElementById('statusBadge').className = 'badge badge-offline';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Render ───────────────────────────────────────────────
|
|
46
|
+
function renderDashboard(data) {
|
|
47
|
+
// Status
|
|
48
|
+
document.getElementById('statusBadge').textContent = '● LIVE';
|
|
49
|
+
document.getElementById('statusBadge').className = 'badge badge-live';
|
|
50
|
+
|
|
51
|
+
// Uptime
|
|
52
|
+
const uptimeMs = data.uptime || 0;
|
|
53
|
+
document.getElementById('uptime').textContent = `Uptime: ${formatDuration(uptimeMs)}`;
|
|
54
|
+
|
|
55
|
+
// Stats
|
|
56
|
+
document.getElementById('totalRequests').textContent = formatNumber(data.totalRequests || 0);
|
|
57
|
+
document.getElementById('totalErrors').textContent = formatNumber(data.totalErrors || 0);
|
|
58
|
+
document.getElementById('totalSlow').textContent = formatNumber(data.totalSlow || 0);
|
|
59
|
+
|
|
60
|
+
const total = data.totalRequests || 0;
|
|
61
|
+
const errors = data.totalErrors || 0;
|
|
62
|
+
const rate = total > 0 ? Math.round(((total - errors) / total) * 100) : 100;
|
|
63
|
+
document.getElementById('successRate').textContent = rate + '%';
|
|
64
|
+
|
|
65
|
+
// Panels
|
|
66
|
+
renderHealthPanel(data.serviceHealth || []);
|
|
67
|
+
renderFailuresPanel(data.topFailures || []);
|
|
68
|
+
renderEndpointsTable(data.endpoints || []);
|
|
69
|
+
renderSlowQueries(data.recentTraces || []);
|
|
70
|
+
renderRecentTraces(data.recentTraces || []);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Health Panel ─────────────────────────────────────────
|
|
74
|
+
function renderHealthPanel(health) {
|
|
75
|
+
const panel = document.getElementById('healthPanel');
|
|
76
|
+
if (!health.length) {
|
|
77
|
+
panel.innerHTML = '<div class="empty-state">No services detected yet</div>';
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
panel.innerHTML = `<div class="health-grid">${health.map(h => `
|
|
82
|
+
<div class="health-pill">
|
|
83
|
+
<div class="health-dot ${h.status}"></div>
|
|
84
|
+
<div class="health-info">
|
|
85
|
+
<span class="health-name">${escapeHtml(h.name)}</span>
|
|
86
|
+
<span class="health-rate">${h.successRate}% success · ${h.totalChecks} checks</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
`).join('')}</div>`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Failures Panel ───────────────────────────────────────
|
|
93
|
+
function renderFailuresPanel(failures) {
|
|
94
|
+
const panel = document.getElementById('failuresPanel');
|
|
95
|
+
if (!failures.length) {
|
|
96
|
+
panel.innerHTML = '<div class="empty-state">No failures recorded</div>';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const maxPct = Math.max(...failures.map(f => f.percentage), 1);
|
|
101
|
+
|
|
102
|
+
panel.innerHTML = `<div class="failure-bar-group">${failures.map(f => `
|
|
103
|
+
<div class="failure-item">
|
|
104
|
+
<span class="failure-label">${escapeHtml(f.service)}</span>
|
|
105
|
+
<div class="failure-bar-bg">
|
|
106
|
+
<div class="failure-bar" style="width: ${(f.percentage / maxPct) * 100}%"></div>
|
|
107
|
+
</div>
|
|
108
|
+
<span class="failure-pct">${f.percentage}%</span>
|
|
109
|
+
</div>
|
|
110
|
+
`).join('')}</div>`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Endpoints Table ──────────────────────────────────────
|
|
114
|
+
function renderEndpointsTable(endpoints) {
|
|
115
|
+
const tbody = document.getElementById('endpointsTable');
|
|
116
|
+
if (!endpoints.length) {
|
|
117
|
+
tbody.innerHTML = '<tr><td colspan="9" class="empty-state">No endpoints tracked yet</td></tr>';
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
tbody.innerHTML = endpoints.map(ep => `
|
|
122
|
+
<tr>
|
|
123
|
+
<td><span class="method-badge method-${ep.method}">${ep.method}</span></td>
|
|
124
|
+
<td><span class="endpoint-path">${escapeHtml(ep.path)}</span></td>
|
|
125
|
+
<td class="td-mono">${formatNumber(ep.totalRequests)}</td>
|
|
126
|
+
<td class="td-mono" style="color: ${ep.errorCount > 0 ? 'var(--red)' : 'inherit'}">${ep.errorCount}</td>
|
|
127
|
+
<td class="td-mono" style="color: ${ep.slowCount > 0 ? 'var(--yellow)' : 'inherit'}">${ep.slowCount}</td>
|
|
128
|
+
<td class="td-mono">${Math.round(ep.avgDuration)}</td>
|
|
129
|
+
<td class="td-mono">${Math.round(ep.p95Duration)}</td>
|
|
130
|
+
<td class="td-mono">${Math.round(ep.maxDuration)}</td>
|
|
131
|
+
<td>${(ep.commonIssues || []).slice(0, 2).map(i => `<span class="issue-tag">${escapeHtml(i)}</span>`).join('') || '—'}</td>
|
|
132
|
+
</tr>
|
|
133
|
+
`).join('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Slow Queries ─────────────────────────────────────────
|
|
137
|
+
function renderSlowQueries(traces) {
|
|
138
|
+
const container = document.getElementById('slowQueries');
|
|
139
|
+
const slowSteps = [];
|
|
140
|
+
|
|
141
|
+
for (const trace of traces) {
|
|
142
|
+
for (const step of (trace.steps || [])) {
|
|
143
|
+
if (step.classification === 'WARN' && step.duration > 300 && step.metadata?.sql) {
|
|
144
|
+
slowSteps.push({
|
|
145
|
+
sql: step.metadata.sql,
|
|
146
|
+
duration: step.duration,
|
|
147
|
+
service: step.service,
|
|
148
|
+
endpoint: trace.endpoint,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!slowSteps.length) {
|
|
155
|
+
container.innerHTML = '<div class="empty-state">No slow queries detected</div>';
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
container.innerHTML = `<div class="slow-query-list">${slowSteps.slice(0, 10).map(sq => `
|
|
160
|
+
<div class="slow-query-item">
|
|
161
|
+
<span class="slow-query-icon">⚠</span>
|
|
162
|
+
<span class="slow-query-sql">${escapeHtml(sq.sql)}</span>
|
|
163
|
+
<span class="slow-query-dur">${Math.round(sq.duration)}ms</span>
|
|
164
|
+
</div>
|
|
165
|
+
`).join('')}</div>`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Recent Traces ────────────────────────────────────────
|
|
169
|
+
function renderRecentTraces(traces) {
|
|
170
|
+
const container = document.getElementById('recentTraces');
|
|
171
|
+
if (!traces.length) {
|
|
172
|
+
container.innerHTML = '<div class="empty-state">No traces recorded yet</div>';
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
container.innerHTML = `<div class="trace-list">${traces.slice(0, 15).map((trace, idx) => `
|
|
177
|
+
<div class="trace-item">
|
|
178
|
+
<div class="trace-header" onclick="toggleTrace(${idx})">
|
|
179
|
+
<div class="trace-header-left">
|
|
180
|
+
<span class="method-badge method-${trace.method}">${trace.method}</span>
|
|
181
|
+
<span class="trace-endpoint">${escapeHtml(trace.endpoint)}</span>
|
|
182
|
+
<span class="trace-id">${trace.traceId}</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="trace-header-right">
|
|
185
|
+
<span class="classification-badge classification-${trace.classification}">${trace.classification}</span>
|
|
186
|
+
<span class="trace-dur">${Math.round(trace.totalDuration)}ms</span>
|
|
187
|
+
<span style="color: var(--text-dim); font-size: 12px">▼</span>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="trace-steps" id="traceSteps-${idx}">
|
|
191
|
+
<div class="step-timeline">
|
|
192
|
+
${(trace.steps || []).map(step => {
|
|
193
|
+
const cls = step.status === 'error' ? 'step-error' :
|
|
194
|
+
step.status === 'timeout' ? 'step-timeout' :
|
|
195
|
+
step.classification === 'WARN' ? 'step-slow' : '';
|
|
196
|
+
const icon = step.status === 'success' ? '✔' : step.status === 'timeout' ? '⏱' : '❌';
|
|
197
|
+
return `
|
|
198
|
+
<div class="step-item ${cls}">
|
|
199
|
+
<span class="step-offset">[${Math.round(step.startTime)}ms]</span>
|
|
200
|
+
<span class="step-name">${escapeHtml(step.name)}</span>
|
|
201
|
+
<span class="step-status-icon">${icon}</span>
|
|
202
|
+
<span class="step-dur">${Math.round(step.duration)}ms</span>
|
|
203
|
+
${step.service !== 'internal' ? `<span class="step-service-tag">${step.service}</span>` : ''}
|
|
204
|
+
</div>
|
|
205
|
+
${step.error ? `<div class="step-item" style="padding-left: 92px; color: var(--red); font-size: 11px">└─ ${escapeHtml(step.error)}</div>` : ''}
|
|
206
|
+
${step.errorFile ? `<div class="step-item" style="padding-left: 92px; color: var(--purple); font-size: 10px; font-family: monospace"> 📄 ${escapeHtml(step.errorFile)}${step.errorLine ? `:${step.errorLine}` : ''}</div>` : ''}
|
|
207
|
+
`;
|
|
208
|
+
}).join('')}
|
|
209
|
+
</div>
|
|
210
|
+
${trace.rootCause ? `
|
|
211
|
+
<div class="root-cause-box">
|
|
212
|
+
<div class="rc-label">🔍 Root Cause</div>
|
|
213
|
+
<div class="rc-detail">${escapeHtml(trace.rootCause.cause)} · Service: ${trace.rootCause.service} · Confidence: ${trace.rootCause.confidence}</div>
|
|
214
|
+
</div>
|
|
215
|
+
` : ''}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
`).join('')}</div>`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function toggleTrace(idx) {
|
|
222
|
+
const el = document.getElementById(`traceSteps-${idx}`);
|
|
223
|
+
if (el) el.classList.toggle('open');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Utilities ────────────────────────────────────────────
|
|
227
|
+
function escapeHtml(str) {
|
|
228
|
+
if (!str) return '';
|
|
229
|
+
return String(str)
|
|
230
|
+
.replace(/&/g, '&')
|
|
231
|
+
.replace(/</g, '<')
|
|
232
|
+
.replace(/>/g, '>')
|
|
233
|
+
.replace(/"/g, '"');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function formatNumber(n) {
|
|
237
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
|
238
|
+
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
|
|
239
|
+
return String(n);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function performSearch() {
|
|
243
|
+
const query = document.getElementById('searchInput').value.trim();
|
|
244
|
+
const env = document.getElementById('envFilter').value;
|
|
245
|
+
const resultsDiv = document.getElementById('searchResults');
|
|
246
|
+
|
|
247
|
+
if (!query) {
|
|
248
|
+
resultsDiv.innerHTML = '<div class="empty-state">Enter a search term</div>';
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const url = `/__debugger/search?q=${encodeURIComponent(query)}${env ? `&env=${env}` : ''}`;
|
|
254
|
+
const res = await fetch(url);
|
|
255
|
+
const data = await res.json();
|
|
256
|
+
|
|
257
|
+
if (data.error) {
|
|
258
|
+
resultsDiv.innerHTML = `<div class="empty-state">${data.error}</div>`;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!data.results || data.results.length === 0) {
|
|
263
|
+
resultsDiv.innerHTML = '<div class="empty-state">No results found</div>';
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Render results using same trace rendering logic
|
|
268
|
+
resultsDiv.innerHTML = `
|
|
269
|
+
<div style="margin-bottom: 12px; color: var(--text-dim); font-size: 13px;">
|
|
270
|
+
Found ${data.count} result${data.count !== 1 ? 's' : ''} for "${escapeHtml(query)}"
|
|
271
|
+
</div>
|
|
272
|
+
<div class="trace-list">${data.results.map((trace, idx) => {
|
|
273
|
+
const hasPayload = trace.payloadSize && trace.payloadSize > 0;
|
|
274
|
+
const payloadMB = hasPayload ? (trace.payloadSize / (1024 * 1024)).toFixed(2) : null;
|
|
275
|
+
return `
|
|
276
|
+
<div class="trace-item">
|
|
277
|
+
<div class="trace-header" onclick="toggleSearchTrace(${idx})">
|
|
278
|
+
<div class="trace-header-left">
|
|
279
|
+
<span class="method-badge method-${trace.method}">${trace.method}</span>
|
|
280
|
+
<span class="trace-endpoint">${escapeHtml(trace.endpoint)}</span>
|
|
281
|
+
<span class="trace-id">${trace.traceId}</span>
|
|
282
|
+
${trace.environment ? `<span class="badge" style="background: rgba(124,58,237,0.2); color: var(--purple); font-size: 10px; padding: 2px 6px;">${trace.environment}</span>` : ''}
|
|
283
|
+
${hasPayload && payloadMB > 1 ? `<span class="badge" style="background: rgba(251,191,36,0.2); color: var(--yellow); font-size: 10px; padding: 2px 6px;">📦 ${payloadMB}MB</span>` : ''}
|
|
284
|
+
</div>
|
|
285
|
+
<div class="trace-header-right">
|
|
286
|
+
<span class="classification-badge classification-${trace.classification}">${trace.classification}</span>
|
|
287
|
+
<span class="trace-dur">${Math.round(trace.totalDuration)}ms</span>
|
|
288
|
+
<span style="color: var(--text-dim); font-size: 12px">▼</span>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="trace-steps" id="searchTraceSteps-${idx}">
|
|
292
|
+
<div class="step-timeline">
|
|
293
|
+
${(trace.steps || []).map(step => {
|
|
294
|
+
const cls = step.status === 'error' ? 'step-error' :
|
|
295
|
+
step.status === 'timeout' ? 'step-timeout' :
|
|
296
|
+
step.classification === 'WARN' ? 'step-slow' : '';
|
|
297
|
+
const icon = step.status === 'success' ? '✔' : step.status === 'timeout' ? '⏱' : '❌';
|
|
298
|
+
return `
|
|
299
|
+
<div class="step-item ${cls}">
|
|
300
|
+
<span class="step-offset">[${Math.round(step.startTime)}ms]</span>
|
|
301
|
+
<span class="step-name">${escapeHtml(step.name)}</span>
|
|
302
|
+
<span class="step-status-icon">${icon}</span>
|
|
303
|
+
<span class="step-dur">${Math.round(step.duration)}ms</span>
|
|
304
|
+
${step.service !== 'internal' ? `<span class="step-service-tag">${step.service}</span>` : ''}
|
|
305
|
+
</div>
|
|
306
|
+
${step.error ? `<div class="step-item" style="padding-left: 92px; color: var(--red); font-size: 11px">└─ ${escapeHtml(step.error)}</div>` : ''}
|
|
307
|
+
${step.errorFile ? `<div class="step-item" style="padding-left: 92px; color: var(--purple); font-size: 10px; font-family: monospace"> 📄 ${escapeHtml(step.errorFile)}${step.errorLine ? `:${step.errorLine}` : ''}</div>` : ''}
|
|
308
|
+
`;
|
|
309
|
+
}).join('')}
|
|
310
|
+
</div>
|
|
311
|
+
${trace.rootCause ? `
|
|
312
|
+
<div class="root-cause-box">
|
|
313
|
+
<div class="rc-label">🔍 Root Cause</div>
|
|
314
|
+
<div class="rc-detail">${escapeHtml(trace.rootCause.cause)} · Service: ${trace.rootCause.service} · Confidence: ${trace.rootCause.confidence}</div>
|
|
315
|
+
</div>
|
|
316
|
+
` : ''}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
`;
|
|
320
|
+
}).join('')}</div>
|
|
321
|
+
`;
|
|
322
|
+
} catch (err) {
|
|
323
|
+
resultsDiv.innerHTML = '<div class="empty-state">Search failed</div>';
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function toggleSearchTrace(idx) {
|
|
328
|
+
const el = document.getElementById(`searchTraceSteps-${idx}`);
|
|
329
|
+
if (el) el.classList.toggle('open');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function formatDuration(ms) {
|
|
333
|
+
const secs = Math.floor(ms / 1000);
|
|
334
|
+
if (secs < 60) return secs + 's';
|
|
335
|
+
const mins = Math.floor(secs / 60);
|
|
336
|
+
if (mins < 60) return mins + 'm ' + (secs % 60) + 's';
|
|
337
|
+
const hrs = Math.floor(mins / 60);
|
|
338
|
+
return hrs + 'h ' + (mins % 60) + 'm';
|
|
339
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Flow Debugger — Dashboard</title>
|
|
8
|
+
<link rel="stylesheet" href="/__debugger/dashboard/style.css">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
12
|
+
rel="stylesheet">
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
<body>
|
|
16
|
+
<!-- Header -->
|
|
17
|
+
<header class="header">
|
|
18
|
+
<div class="header-left">
|
|
19
|
+
<div class="logo">
|
|
20
|
+
<span class="logo-icon">🔍</span>
|
|
21
|
+
<h1>Flow Debugger</h1>
|
|
22
|
+
</div>
|
|
23
|
+
<span class="badge badge-live" id="statusBadge">● LIVE</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="header-right">
|
|
26
|
+
<span class="uptime" id="uptime">Uptime: --</span>
|
|
27
|
+
<button class="btn btn-ghost" onclick="refreshData()">↻ Refresh</button>
|
|
28
|
+
<button class="btn btn-ghost" id="autoRefreshBtn" onclick="toggleAutoRefresh()">Auto: ON</button>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<!-- Stats Row -->
|
|
33
|
+
<section class="stats-row" id="statsRow">
|
|
34
|
+
<div class="stat-card">
|
|
35
|
+
<div class="stat-icon stat-icon-blue">📊</div>
|
|
36
|
+
<div class="stat-content">
|
|
37
|
+
<span class="stat-label">Total Requests</span>
|
|
38
|
+
<span class="stat-value" id="totalRequests">0</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="stat-card">
|
|
42
|
+
<div class="stat-icon stat-icon-red">❌</div>
|
|
43
|
+
<div class="stat-content">
|
|
44
|
+
<span class="stat-label">Errors</span>
|
|
45
|
+
<span class="stat-value stat-error" id="totalErrors">0</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="stat-card">
|
|
49
|
+
<div class="stat-icon stat-icon-yellow">⚠</div>
|
|
50
|
+
<div class="stat-content">
|
|
51
|
+
<span class="stat-label">Slow Requests</span>
|
|
52
|
+
<span class="stat-value stat-warn" id="totalSlow">0</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="stat-card">
|
|
56
|
+
<div class="stat-icon stat-icon-green">✔</div>
|
|
57
|
+
<div class="stat-content">
|
|
58
|
+
<span class="stat-label">Success Rate</span>
|
|
59
|
+
<span class="stat-value stat-success" id="successRate">100%</span>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<!-- Main Grid -->
|
|
65
|
+
<section class="main-grid">
|
|
66
|
+
<!-- Service Health Panel -->
|
|
67
|
+
<div class="card">
|
|
68
|
+
<div class="card-header">
|
|
69
|
+
<h2>🏥 Service Health</h2>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="card-body" id="healthPanel">
|
|
72
|
+
<div class="empty-state">No services detected yet</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<!-- Top Failures Panel -->
|
|
77
|
+
<div class="card">
|
|
78
|
+
<div class="card-header">
|
|
79
|
+
<h2>🔥 Top Failures by Service</h2>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="card-body" id="failuresPanel">
|
|
82
|
+
<div class="empty-state">No failures recorded</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<!-- Endpoints Table -->
|
|
88
|
+
<section class="card full-width">
|
|
89
|
+
<div class="card-header">
|
|
90
|
+
<h2>📡 Endpoint Analytics</h2>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="card-body">
|
|
93
|
+
<div class="table-container">
|
|
94
|
+
<table class="data-table">
|
|
95
|
+
<thead>
|
|
96
|
+
<tr>
|
|
97
|
+
<th>Method</th>
|
|
98
|
+
<th>Endpoint</th>
|
|
99
|
+
<th>Requests</th>
|
|
100
|
+
<th>Errors</th>
|
|
101
|
+
<th>Slow</th>
|
|
102
|
+
<th>Avg (ms)</th>
|
|
103
|
+
<th>P95 (ms)</th>
|
|
104
|
+
<th>Max (ms)</th>
|
|
105
|
+
<th>Common Issues</th>
|
|
106
|
+
</tr>
|
|
107
|
+
</thead>
|
|
108
|
+
<tbody id="endpointsTable">
|
|
109
|
+
<tr>
|
|
110
|
+
<td colspan="9" class="empty-state">No endpoints tracked yet</td>
|
|
111
|
+
</tr>
|
|
112
|
+
</tbody>
|
|
113
|
+
</table>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<!-- Slow Query Log -->
|
|
119
|
+
<section class="card full-width">
|
|
120
|
+
<div class="card-header">
|
|
121
|
+
<h2>🐌 Slow Queries Detected</h2>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="card-body" id="slowQueries">
|
|
124
|
+
<div class="empty-state">No slow queries detected</div>
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
|
|
128
|
+
<!-- Search Section -->
|
|
129
|
+
<section class="card full-width">
|
|
130
|
+
<div class="card-header">
|
|
131
|
+
<h2>🔍 Search Traces</h2>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="card-body">
|
|
134
|
+
<div style="display: flex; gap: 12px; margin-bottom: 16px;">
|
|
135
|
+
<input type="text" id="searchInput" placeholder="Search by traceId, endpoint, or error message..."
|
|
136
|
+
style="flex: 1; padding: 10px 14px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #e0e0e0; font-size: 14px;"
|
|
137
|
+
onkeypress="if(event.key==='Enter') performSearch()" />
|
|
138
|
+
<select id="envFilter"
|
|
139
|
+
style="padding: 10px 14px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #e0e0e0; font-size: 14px;">
|
|
140
|
+
<option value="">All Environments</option>
|
|
141
|
+
<option value="development">Development</option>
|
|
142
|
+
<option value="staging">Staging</option>
|
|
143
|
+
<option value="production">Production</option>
|
|
144
|
+
</select>
|
|
145
|
+
<button class="btn btn-primary" onclick="performSearch()">Search</button>
|
|
146
|
+
</div>
|
|
147
|
+
<div id="searchResults"></div>
|
|
148
|
+
</div>
|
|
149
|
+
</section>
|
|
150
|
+
|
|
151
|
+
<!-- Recent Traces -->
|
|
152
|
+
<section class="card full-width">
|
|
153
|
+
<div class="card-header">
|
|
154
|
+
<h2>📜 Recent Request Traces</h2>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="card-body" id="recentTraces">
|
|
157
|
+
<div class="empty-state">No traces recorded yet</div>
|
|
158
|
+
</div>
|
|
159
|
+
</section>
|
|
160
|
+
|
|
161
|
+
<footer class="footer">
|
|
162
|
+
<p>Flow Debugger v1.0.0 — Production-safe request tracing & analytics</p>
|
|
163
|
+
</footer>
|
|
164
|
+
|
|
165
|
+
<script src="/__debugger/dashboard/app.js"></script>
|
|
166
|
+
</body>
|
|
167
|
+
|
|
168
|
+
</html>
|