@beignet/devtools 0.0.1
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/CHANGELOG.md +5 -0
- package/README.md +464 -0
- package/dist/access.d.ts +21 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +20 -0
- package/dist/access.js.map +1 -0
- package/dist/audit.d.ts +10 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +49 -0
- package/dist/audit.js.map +1 -0
- package/dist/events.d.ts +143 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +20 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.d.ts +74 -0
- package/dist/instrumentation.d.ts.map +1 -0
- package/dist/instrumentation.js +293 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/persistence.d.ts +30 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +100 -0
- package/dist/persistence.js.map +1 -0
- package/dist/provider-instrumentation.d.ts +9 -0
- package/dist/provider-instrumentation.d.ts.map +1 -0
- package/dist/provider-instrumentation.js +25 -0
- package/dist/provider-instrumentation.js.map +1 -0
- package/dist/provider.d.ts +79 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +293 -0
- package/dist/provider.js.map +1 -0
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +20 -0
- package/dist/redaction.js.map +1 -0
- package/dist/routes.d.ts +113 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +247 -0
- package/dist/routes.js.map +1 -0
- package/dist/trace-context.d.ts +29 -0
- package/dist/trace-context.d.ts.map +1 -0
- package/dist/trace-context.js +74 -0
- package/dist/trace-context.js.map +1 -0
- package/dist/ui.d.ts +14 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +795 -0
- package/dist/ui.js.map +1 -0
- package/dist/watchers.d.ts +22 -0
- package/dist/watchers.d.ts.map +1 -0
- package/dist/watchers.js +171 -0
- package/dist/watchers.js.map +1 -0
- package/package.json +66 -0
- package/src/access.ts +52 -0
- package/src/audit.ts +71 -0
- package/src/events.ts +193 -0
- package/src/index.ts +136 -0
- package/src/instrumentation.ts +451 -0
- package/src/persistence.ts +163 -0
- package/src/provider-instrumentation.ts +50 -0
- package/src/provider.ts +375 -0
- package/src/redaction.ts +26 -0
- package/src/routes.ts +317 -0
- package/src/trace-context.ts +115 -0
- package/src/ui.ts +807 -0
- package/src/watchers.ts +235 -0
package/src/ui.ts
ADDED
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DevtoolsRouteAccessOptions,
|
|
3
|
+
devtoolsNotFoundResponse,
|
|
4
|
+
isDevtoolsRouteEnabled,
|
|
5
|
+
} from "./access";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Devtools UI handler
|
|
9
|
+
*
|
|
10
|
+
* Serves a self-contained HTML dashboard for viewing devtools events.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle requests for the devtools dashboard UI.
|
|
15
|
+
*
|
|
16
|
+
* @param basePath - The URL path prefix where devtools routes are mounted (e.g. "/api/devtools")
|
|
17
|
+
* @returns HTML response with the devtools dashboard
|
|
18
|
+
*/
|
|
19
|
+
export function handleDevtoolsUIRequest(
|
|
20
|
+
basePath: string,
|
|
21
|
+
options: DevtoolsRouteAccessOptions = {},
|
|
22
|
+
): Response {
|
|
23
|
+
if (!isDevtoolsRouteEnabled(options)) {
|
|
24
|
+
return devtoolsNotFoundResponse();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const html = buildDashboardHTML(basePath);
|
|
28
|
+
|
|
29
|
+
return new Response(html, {
|
|
30
|
+
status: 200,
|
|
31
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildDashboardHTML(basePath: string): string {
|
|
36
|
+
const sanitizedBasePath = basePath.replace(/\/+$/, "");
|
|
37
|
+
|
|
38
|
+
return `<!DOCTYPE html>
|
|
39
|
+
<html lang="en">
|
|
40
|
+
<head>
|
|
41
|
+
<meta charset="utf-8" />
|
|
42
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
43
|
+
<title>Beignet Devtools</title>
|
|
44
|
+
<style>
|
|
45
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
46
|
+
:root{
|
|
47
|
+
--bg:#ffffff;--surface:#f8fafc;--surface-2:#f1f5f9;--surface-3:#e2e8f0;--border:rgba(15,23,42,.1);
|
|
48
|
+
--text:#0f172a;--text-muted:#475569;--text-dim:#94a3b8;
|
|
49
|
+
--accent:#4f46e5;--accent-strong:#4338ca;
|
|
50
|
+
--red:#e11d48;--orange:#d97706;--green:#059669;--blue:#2563eb;--purple:#7c3aed;--gray:#64748b;
|
|
51
|
+
--shadow:0 20px 50px rgba(15,23,42,.06);
|
|
52
|
+
--font-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
|
|
53
|
+
--font-sans:"Inter",ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
|
|
54
|
+
}
|
|
55
|
+
body{font-family:var(--font-sans);background:var(--bg);color:var(--text);line-height:1.5;min-height:100vh;font-size:14px}
|
|
56
|
+
.shell{max-width:1160px;margin:0 auto;padding:28px 28px 48px}
|
|
57
|
+
header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;padding:0 0 20px;border-bottom:1px solid var(--border);margin-bottom:18px}
|
|
58
|
+
.brand{display:flex;align-items:center;gap:10px;color:var(--text);font-size:12px;font-weight:650;letter-spacing:.01em}
|
|
59
|
+
.mark{width:22px;height:22px;border:1px solid var(--border);border-radius:6px;background:linear-gradient(180deg,#fff,var(--surface));display:grid;place-items:center;color:var(--accent-strong);font-family:var(--font-mono);font-size:10px;box-shadow:0 1px 2px rgba(15,23,42,.04)}
|
|
60
|
+
h1{font-size:26px;line-height:1.15;font-weight:720;letter-spacing:-.01em;margin-top:18px}
|
|
61
|
+
h1 span{color:var(--accent-strong);font-weight:720}
|
|
62
|
+
.subtitle{color:var(--text-muted);font-size:13px;margin-top:8px;max-width:520px}
|
|
63
|
+
.controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
|
|
64
|
+
button{font-family:var(--font-sans);font-size:12px;font-weight:560;padding:7px 10px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:border-color .15s,background .15s,color .15s,box-shadow .15s}
|
|
65
|
+
button:hover{border-color:rgba(79,70,229,.28);background:var(--surface);color:var(--text)}
|
|
66
|
+
button.active{border-color:rgba(79,70,229,.3);color:var(--accent-strong);background:#eef2ff;box-shadow:inset 0 0 0 1px rgba(79,70,229,.04)}
|
|
67
|
+
button.danger{color:var(--red)}
|
|
68
|
+
button.danger:hover{border-color:rgba(225,29,72,.24);background:#fff1f2}
|
|
69
|
+
.status{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:7px;padding:7px 10px;border:1px solid var(--border);border-radius:6px;background:var(--surface)}
|
|
70
|
+
.dot{width:6px;height:6px;border-radius:50%;background:var(--green);display:inline-block}
|
|
71
|
+
.dot.paused{background:var(--text-dim)}
|
|
72
|
+
.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:16px}
|
|
73
|
+
.stat{border:1px solid var(--border);background:var(--surface);border-radius:8px;padding:14px 14px 12px}
|
|
74
|
+
.stat-label{color:var(--text-muted);font-size:11px;text-transform:uppercase;letter-spacing:.12em}
|
|
75
|
+
.stat-value{font-family:var(--font-mono);font-size:22px;color:var(--text);margin-top:5px;letter-spacing:0}
|
|
76
|
+
.panel{border:1px solid var(--border);border-radius:8px;background:var(--bg);box-shadow:var(--shadow);overflow:hidden}
|
|
77
|
+
.panel-top{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px;border-bottom:1px solid var(--border);background:var(--surface)}
|
|
78
|
+
.tabs{display:flex;gap:6px;flex-wrap:wrap}
|
|
79
|
+
.tab-count{font-family:var(--font-mono);font-size:10px;color:var(--text-dim);margin-left:4px}
|
|
80
|
+
.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
|
|
81
|
+
.toolbar input,.toolbar select{font-family:var(--font-mono);font-size:12px;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text);outline:none;height:34px}
|
|
82
|
+
.toolbar input{width:300px}
|
|
83
|
+
.toolbar select{min-width:118px}
|
|
84
|
+
.toolbar input:focus,.toolbar select:focus{border-color:rgba(79,70,229,.45);box-shadow:0 0 0 3px rgba(79,70,229,.08)}
|
|
85
|
+
.toolbar input::placeholder{color:var(--text-dim)}
|
|
86
|
+
.event-list{display:flex;flex-direction:column}
|
|
87
|
+
.event{background:var(--bg);padding:12px 14px;cursor:pointer;transition:background .1s,border-color .1s;border-bottom:1px solid var(--border)}
|
|
88
|
+
.event:last-child{border-bottom:0}
|
|
89
|
+
.event:hover{background:var(--surface)}
|
|
90
|
+
.event.open{background:#f8fafc}
|
|
91
|
+
.event-header{display:flex;align-items:center;gap:8px;font-size:13px}
|
|
92
|
+
.event-time{color:var(--text-dim);font-family:var(--font-mono);font-size:11px;min-width:82px}
|
|
93
|
+
.event-summary{color:var(--text-muted);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
94
|
+
.event-detail{display:none;margin-top:10px;padding-top:10px;border-top:1px solid var(--border);font-family:var(--font-mono);font-size:11px;color:var(--text-muted);line-height:1.7;white-space:pre-wrap;word-break:break-word}
|
|
95
|
+
.event.open .event-detail{display:block}
|
|
96
|
+
.request-detail{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);gap:12px}
|
|
97
|
+
.detail-section{background:var(--bg);border:1px solid var(--border);border-radius:7px;padding:10px}
|
|
98
|
+
.detail-title{color:var(--text);font-family:var(--font-sans);font-size:12px;font-weight:650;margin-bottom:6px}
|
|
99
|
+
.correlated{display:flex;flex-direction:column;gap:6px}
|
|
100
|
+
.correlated-row{display:flex;gap:7px;align-items:flex-start;color:var(--text-muted)}
|
|
101
|
+
.focus-panel{display:flex;flex-direction:column}
|
|
102
|
+
.focus-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:14px;border-bottom:1px solid var(--border);background:var(--bg)}
|
|
103
|
+
.focus-title{font-size:15px;line-height:1.2;font-weight:720;color:var(--text)}
|
|
104
|
+
.focus-subtitle{margin-top:3px;color:var(--text-muted);font-size:12px}
|
|
105
|
+
.focus-metrics{display:grid;grid-template-columns:repeat(4,minmax(92px,1fr));gap:8px;min-width:440px}
|
|
106
|
+
.focus-metric{border:1px solid var(--border);border-radius:7px;background:var(--surface);padding:8px 9px}
|
|
107
|
+
.focus-metric-label{font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}
|
|
108
|
+
.focus-metric-value{font-family:var(--font-mono);font-size:15px;color:var(--text);margin-top:3px}
|
|
109
|
+
.event-table{display:flex;flex-direction:column}
|
|
110
|
+
.table-row{display:grid;grid-template-columns:86px minmax(140px,.9fr) minmax(220px,1.4fr) 96px 112px;gap:10px;align-items:start;padding:11px 14px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .1s}
|
|
111
|
+
.table-row:last-child{border-bottom:0}
|
|
112
|
+
.table-row:hover{background:var(--surface)}
|
|
113
|
+
.table-row.open{background:#f8fafc}
|
|
114
|
+
.table-cell{min-width:0;color:var(--text-muted);font-size:12px}
|
|
115
|
+
.table-cell strong{display:block;color:var(--text);font-size:13px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
116
|
+
.table-cell code{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);background:var(--surface-2);border:1px solid var(--border);border-radius:4px;padding:1px 4px}
|
|
117
|
+
.table-summary{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
118
|
+
.table-detail{display:none;grid-column:1/-1;margin-top:2px;padding-top:10px;border-top:1px solid var(--border);font-family:var(--font-mono);font-size:11px;color:var(--text-muted);line-height:1.7;white-space:pre-wrap;word-break:break-word}
|
|
119
|
+
.table-row.open .table-detail{display:block}
|
|
120
|
+
.empty{text-align:center;padding:58px 16px;color:var(--text-dim);font-size:13px;background:var(--bg)}
|
|
121
|
+
.badge{display:inline-block;font-size:10px;font-weight:650;padding:2px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.06em;line-height:1.4;border:1px solid transparent}
|
|
122
|
+
.badge-request{background:#eff6ff;color:var(--blue);border-color:#dbeafe}
|
|
123
|
+
.badge-error{background:#fff1f2;color:var(--red);border-color:#ffe4e6}
|
|
124
|
+
.badge-usecase{background:#f5f3ff;color:var(--purple);border-color:#ede9fe}
|
|
125
|
+
.badge-eventBus{background:#ecfdf5;color:var(--green);border-color:#d1fae5}
|
|
126
|
+
.badge-job{background:#fff7ed;color:var(--orange);border-color:#fed7aa}
|
|
127
|
+
.badge-schedule{background:#f0f9ff;color:#0369a1;border-color:#bae6fd}
|
|
128
|
+
.badge-provider{background:#f8fafc;color:var(--gray);border-color:var(--border)}
|
|
129
|
+
.badge-custom{background:#f0fdfa;color:#0f766e;border-color:#ccfbf1}
|
|
130
|
+
.badge-auth{background:#eef2ff;color:var(--accent-strong);border-color:#c7d2fe}
|
|
131
|
+
.badge-audit{background:#fef2f2;color:#b91c1c;border-color:#fecaca}
|
|
132
|
+
.badge-cache{background:#f7fee7;color:#4d7c0f;border-color:#d9f99d}
|
|
133
|
+
.badge-db{background:#ecfeff;color:#0e7490;border-color:#cffafe}
|
|
134
|
+
.badge-mail{background:#fdf2f8;color:#be185d;border-color:#fbcfe8}
|
|
135
|
+
.badge-rateLimit{background:#fffbeb;color:#b45309;border-color:#fde68a}
|
|
136
|
+
.badge-storage{background:#f0fdf4;color:#15803d;border-color:#bbf7d0}
|
|
137
|
+
.request-id{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);padding:2px 5px;background:var(--surface-2);border:1px solid var(--border);border-radius:4px}
|
|
138
|
+
.trace-id{font-family:var(--font-mono);font-size:10px;color:var(--accent-strong);padding:2px 5px;background:#eef2ff;border:1px solid rgba(79,70,229,.16);border-radius:4px}
|
|
139
|
+
@media(max-width:760px){
|
|
140
|
+
.shell{padding:18px}
|
|
141
|
+
header{flex-direction:column;align-items:stretch}
|
|
142
|
+
.controls{justify-content:flex-start}
|
|
143
|
+
.stats{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
144
|
+
.panel-top{align-items:stretch;flex-direction:column}
|
|
145
|
+
.toolbar input,.toolbar select{width:100%}
|
|
146
|
+
.toolbar{align-items:stretch;flex-direction:column}
|
|
147
|
+
.event-header{align-items:flex-start;flex-wrap:wrap}
|
|
148
|
+
.event-time{min-width:0}
|
|
149
|
+
.event-summary{white-space:normal}
|
|
150
|
+
.request-detail{grid-template-columns:1fr}
|
|
151
|
+
.focus-head{flex-direction:column}
|
|
152
|
+
.focus-metrics{grid-template-columns:repeat(2,minmax(0,1fr));min-width:0;width:100%}
|
|
153
|
+
.table-row{grid-template-columns:1fr;gap:6px}
|
|
154
|
+
.table-detail{grid-column:auto}
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
157
|
+
</head>
|
|
158
|
+
<body>
|
|
159
|
+
<div class="shell">
|
|
160
|
+
<header>
|
|
161
|
+
<div>
|
|
162
|
+
<div class="brand"><span class="mark">B</span><span>Beignet</span></div>
|
|
163
|
+
<h1>Beignet <span>Devtools</span></h1>
|
|
164
|
+
<div class="subtitle">A local timeline for requests, use cases, errors, jobs, schedules, and provider activity.</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="controls">
|
|
167
|
+
<span class="status"><span class="dot" id="statusDot"></span><span id="statusText">Connecting</span></span>
|
|
168
|
+
<button id="togglePause">Pause</button>
|
|
169
|
+
<button class="danger" id="clearBtn">Clear</button>
|
|
170
|
+
</div>
|
|
171
|
+
</header>
|
|
172
|
+
<div class="stats">
|
|
173
|
+
<div class="stat"><div class="stat-label">Events</div><div class="stat-value" id="statTotal">0</div></div>
|
|
174
|
+
<div class="stat"><div class="stat-label">Requests</div><div class="stat-value" id="statRequests">0</div></div>
|
|
175
|
+
<div class="stat"><div class="stat-label">Errors</div><div class="stat-value" id="statErrors">0</div></div>
|
|
176
|
+
<div class="stat"><div class="stat-label">Use cases</div><div class="stat-value" id="statUseCases">0</div></div>
|
|
177
|
+
</div>
|
|
178
|
+
<section class="panel" aria-label="Devtools event timeline">
|
|
179
|
+
<div class="panel-top">
|
|
180
|
+
<nav class="tabs" id="tabs" aria-label="Event type filters"></nav>
|
|
181
|
+
<div class="toolbar">
|
|
182
|
+
<input type="search" id="searchInput" placeholder="Search events, paths, messages, details" />
|
|
183
|
+
<select id="methodFilter" aria-label="HTTP method filter">
|
|
184
|
+
<option value="">All methods</option>
|
|
185
|
+
<option value="GET">GET</option>
|
|
186
|
+
<option value="POST">POST</option>
|
|
187
|
+
<option value="PUT">PUT</option>
|
|
188
|
+
<option value="PATCH">PATCH</option>
|
|
189
|
+
<option value="DELETE">DELETE</option>
|
|
190
|
+
<option value="HEAD">HEAD</option>
|
|
191
|
+
<option value="OPTIONS">OPTIONS</option>
|
|
192
|
+
</select>
|
|
193
|
+
<select id="statusFilter" aria-label="HTTP status filter">
|
|
194
|
+
<option value="">All statuses</option>
|
|
195
|
+
<option value="2xx">2xx</option>
|
|
196
|
+
<option value="3xx">3xx</option>
|
|
197
|
+
<option value="4xx">4xx</option>
|
|
198
|
+
<option value="5xx">5xx</option>
|
|
199
|
+
</select>
|
|
200
|
+
<select id="watcherFilter" aria-label="Watcher filter">
|
|
201
|
+
<option value="">All watchers</option>
|
|
202
|
+
</select>
|
|
203
|
+
<button id="resetFilters" type="button">Reset</button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<main id="content" class="event-list"></main>
|
|
207
|
+
</section>
|
|
208
|
+
</div>
|
|
209
|
+
<script>
|
|
210
|
+
(function(){
|
|
211
|
+
const BASE="${sanitizedBasePath}";
|
|
212
|
+
const TABS=[
|
|
213
|
+
{id:"timeline",label:"Timeline"},
|
|
214
|
+
{id:"requests",label:"Requests",type:"request",watcher:"requests"},
|
|
215
|
+
{id:"usecases",label:"Use cases",type:"usecase",watcher:"useCases"},
|
|
216
|
+
{id:"errors",label:"Errors",type:"error",watcher:"errors"},
|
|
217
|
+
{id:"events",label:"Events",type:"eventBus",watcher:"eventBus"},
|
|
218
|
+
{id:"jobs",label:"Jobs",type:"job",watcher:"jobs"},
|
|
219
|
+
{id:"schedules",label:"Schedules",type:"schedule",watcher:"schedules"},
|
|
220
|
+
{id:"providers",label:"Providers",type:"provider",watcher:"providers"},
|
|
221
|
+
{id:"db",label:"Database",type:"custom",watcher:"db",watcherName:"db"},
|
|
222
|
+
{id:"cache",label:"Cache",type:"custom",watcher:"cache",watcherName:"cache"},
|
|
223
|
+
{id:"storage",label:"Storage",type:"custom",watcher:"storage",watcherName:"storage"},
|
|
224
|
+
{id:"mail",label:"Mail",type:"custom",watcher:"mail",watcherName:"mail"},
|
|
225
|
+
{id:"auth",label:"Auth",type:"custom",watcher:"auth",watcherName:"auth"},
|
|
226
|
+
{id:"audit",label:"Audit",type:"custom",watcher:"audit",watcherName:"audit"},
|
|
227
|
+
{id:"rateLimit",label:"Rate limits",type:"custom",watcher:"rateLimit",watcherName:"rateLimit"},
|
|
228
|
+
{id:"custom",label:"Custom",type:"custom",watcher:"custom"}
|
|
229
|
+
];
|
|
230
|
+
const FOCUS_PANEL_TABS={events:true,jobs:true,schedules:true,db:true,cache:true,storage:true,mail:true,auth:true,audit:true,rateLimit:true};
|
|
231
|
+
const BUILT_IN_WATCHERS={requests:true,errors:true,useCases:true,eventBus:true,jobs:true,schedules:true,providers:true,db:true,cache:true,storage:true,mail:true,auth:true,audit:true,rateLimit:true,custom:true};
|
|
232
|
+
let events=[];
|
|
233
|
+
let watchers=[];
|
|
234
|
+
let activeTab=localStorage.getItem("beignet-devtools-tab")||"timeline";
|
|
235
|
+
let paused=localStorage.getItem("beignet-devtools-paused")==="true";
|
|
236
|
+
let timer=null;
|
|
237
|
+
let source=null;
|
|
238
|
+
|
|
239
|
+
const $tabs=document.getElementById("tabs");
|
|
240
|
+
const $content=document.getElementById("content");
|
|
241
|
+
const $toggle=document.getElementById("togglePause");
|
|
242
|
+
const $clear=document.getElementById("clearBtn");
|
|
243
|
+
const $search=document.getElementById("searchInput");
|
|
244
|
+
const $methodFilter=document.getElementById("methodFilter");
|
|
245
|
+
const $statusFilter=document.getElementById("statusFilter");
|
|
246
|
+
const $watcherFilter=document.getElementById("watcherFilter");
|
|
247
|
+
const $resetFilters=document.getElementById("resetFilters");
|
|
248
|
+
const $dot=document.getElementById("statusDot");
|
|
249
|
+
const $statusText=document.getElementById("statusText");
|
|
250
|
+
const $statTotal=document.getElementById("statTotal");
|
|
251
|
+
const $statRequests=document.getElementById("statRequests");
|
|
252
|
+
const $statErrors=document.getElementById("statErrors");
|
|
253
|
+
const $statUseCases=document.getElementById("statUseCases");
|
|
254
|
+
|
|
255
|
+
function esc(value){
|
|
256
|
+
if(value===undefined||value===null)return"";
|
|
257
|
+
return String(value).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function json(value){
|
|
261
|
+
return esc(JSON.stringify(value,null,2));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function jsonSearch(value){
|
|
265
|
+
if(value===undefined)return"";
|
|
266
|
+
try{return JSON.stringify(value)}
|
|
267
|
+
catch{return String(value)}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function tabFor(id){
|
|
271
|
+
return visibleTabs().find(function(tab){return tab.id===id})||TABS[0];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function watcherEnabled(name){
|
|
275
|
+
const watcher=watchers.find(function(item){return item.name===name});
|
|
276
|
+
return watcher?watcher.enabled:true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function visibleTabs(){
|
|
280
|
+
return TABS.filter(function(tab){return !tab.watcher||watcherEnabled(tab.watcher)}).concat(customWatcherTabs());
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function customWatcherTabs(){
|
|
284
|
+
return watchers.filter(function(watcher){
|
|
285
|
+
return !BUILT_IN_WATCHERS[watcher.name]&&watcher.enabled&&watcher.eventTypes&&watcher.eventTypes.indexOf("custom")>=0;
|
|
286
|
+
}).map(function(watcher){
|
|
287
|
+
return {id:"watcher:"+watcher.name,label:watcher.label,type:"custom",watcherName:watcher.name};
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function summarize(e){
|
|
292
|
+
switch(e.type){
|
|
293
|
+
case "request": return (e.method||"")+" "+(e.path||"")+(e.status?" -> "+e.status:"")+(e.durationMs!==undefined?" ("+e.durationMs+"ms)":"");
|
|
294
|
+
case "error": return e.message||"Unknown error";
|
|
295
|
+
case "usecase": return e.name+" ("+e.phase+")"+(e.durationMs!==undefined?" "+e.durationMs+"ms":"");
|
|
296
|
+
case "eventBus": return e.eventName||"Domain event";
|
|
297
|
+
case "job": return e.jobName+" -> "+e.status;
|
|
298
|
+
case "schedule": return e.scheduleName+" -> "+e.status;
|
|
299
|
+
case "provider": return e.providerName+" -> "+e.action;
|
|
300
|
+
case "custom": return (e.label||e.name||"Custom event")+(e.summary?" - "+e.summary:"");
|
|
301
|
+
default: return "";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function tabMatchesEvent(tab,event){
|
|
306
|
+
if(tab.type&&event.type!==tab.type)return false;
|
|
307
|
+
if(tab.watcherName&&event.watcher!==tab.watcherName)return false;
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function inferredWatcher(event){
|
|
312
|
+
if(event.watcher)return event.watcher;
|
|
313
|
+
switch(event.type){
|
|
314
|
+
case "request": return "requests";
|
|
315
|
+
case "error": return "errors";
|
|
316
|
+
case "usecase": return "useCases";
|
|
317
|
+
case "eventBus": return "eventBus";
|
|
318
|
+
case "job": return "jobs";
|
|
319
|
+
case "schedule": return "schedules";
|
|
320
|
+
case "provider": return "providers";
|
|
321
|
+
case "custom": return "custom";
|
|
322
|
+
default: return "";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function eventBadgeLabel(event){
|
|
327
|
+
if(event.type==="custom"){
|
|
328
|
+
switch(inferredWatcher(event)){
|
|
329
|
+
case "auth": return "auth";
|
|
330
|
+
case "audit": return "audit";
|
|
331
|
+
case "cache": return "cache";
|
|
332
|
+
case "db": return "database";
|
|
333
|
+
case "mail": return "mail";
|
|
334
|
+
case "rateLimit": return "rate limit";
|
|
335
|
+
case "storage": return "storage";
|
|
336
|
+
default: return "custom";
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return event.type;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function eventBadgeClass(event){
|
|
343
|
+
if(event.type==="custom"){
|
|
344
|
+
switch(inferredWatcher(event)){
|
|
345
|
+
case "auth": return "auth";
|
|
346
|
+
case "audit": return "audit";
|
|
347
|
+
case "cache": return "cache";
|
|
348
|
+
case "db": return "db";
|
|
349
|
+
case "mail": return "mail";
|
|
350
|
+
case "rateLimit": return "rateLimit";
|
|
351
|
+
case "storage": return "storage";
|
|
352
|
+
default: return "custom";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return event.type;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function eventBadge(event){
|
|
359
|
+
return '<span class="badge badge-'+esc(eventBadgeClass(event))+'">'+esc(eventBadgeLabel(event))+'</span>';
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function statusGroup(status){
|
|
363
|
+
const value=Number(status);
|
|
364
|
+
if(!Number.isFinite(value))return"";
|
|
365
|
+
if(value>=200&&value<300)return"2xx";
|
|
366
|
+
if(value>=300&&value<400)return"3xx";
|
|
367
|
+
if(value>=400&&value<500)return"4xx";
|
|
368
|
+
if(value>=500&&value<600)return"5xx";
|
|
369
|
+
return"";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function eventSearchText(event){
|
|
373
|
+
return [
|
|
374
|
+
event.id,
|
|
375
|
+
event.type,
|
|
376
|
+
event.requestId,
|
|
377
|
+
event.traceId,
|
|
378
|
+
event.watcher,
|
|
379
|
+
inferredWatcher(event),
|
|
380
|
+
summarize(event),
|
|
381
|
+
event.method,
|
|
382
|
+
event.path,
|
|
383
|
+
event.status,
|
|
384
|
+
event.contractName,
|
|
385
|
+
event.useCaseName,
|
|
386
|
+
event.message,
|
|
387
|
+
event.name,
|
|
388
|
+
event.label,
|
|
389
|
+
event.summary,
|
|
390
|
+
event.eventName,
|
|
391
|
+
event.jobName,
|
|
392
|
+
event.providerName,
|
|
393
|
+
event.action,
|
|
394
|
+
jsonSearch(event.details)
|
|
395
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function count(tab){
|
|
399
|
+
if(!tab||tab.id==="timeline")return events.length;
|
|
400
|
+
return events.filter(function(event){return tabMatchesEvent(tab,event)}).length;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function filteredEvents(){
|
|
404
|
+
const tab=tabFor(activeTab);
|
|
405
|
+
const query=$search.value.trim().toLowerCase();
|
|
406
|
+
const method=$methodFilter.value;
|
|
407
|
+
const status=$statusFilter.value;
|
|
408
|
+
const watcher=$watcherFilter.value;
|
|
409
|
+
return events.filter(function(event){
|
|
410
|
+
if(!tabMatchesEvent(tab,event))return false;
|
|
411
|
+
if(query&&eventSearchText(event).indexOf(query)<0)return false;
|
|
412
|
+
if(method&&event.method!==method)return false;
|
|
413
|
+
if(status&&statusGroup(event.status)!==status)return false;
|
|
414
|
+
if(watcher&&inferredWatcher(event)!==watcher)return false;
|
|
415
|
+
return true;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function correlatedEvents(request){
|
|
420
|
+
return events.filter(function(event){
|
|
421
|
+
if(event.id===request.id)return false;
|
|
422
|
+
if(request.traceId&&event.traceId===request.traceId)return true;
|
|
423
|
+
return Boolean(request.requestId)&&event.requestId===request.requestId;
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function updateStats(){
|
|
428
|
+
$statTotal.textContent=String(events.length);
|
|
429
|
+
$statRequests.textContent=String(events.filter(function(event){return event.type==="request"}).length);
|
|
430
|
+
$statErrors.textContent=String(events.filter(function(event){return event.type==="error"}).length);
|
|
431
|
+
$statUseCases.textContent=String(events.filter(function(event){return event.type==="usecase"}).length);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function renderTabs(){
|
|
435
|
+
const tabs=visibleTabs();
|
|
436
|
+
if(!tabs.some(function(tab){return tab.id===activeTab})){
|
|
437
|
+
activeTab="timeline";
|
|
438
|
+
localStorage.setItem("beignet-devtools-tab",activeTab);
|
|
439
|
+
}
|
|
440
|
+
$tabs.innerHTML=tabs.map(function(tab){
|
|
441
|
+
const active=tab.id===activeTab?" active":"";
|
|
442
|
+
return '<button class="'+active+'" data-tab="'+esc(tab.id)+'">'+esc(tab.label)+'<span class="tab-count">'+count(tab)+'</span></button>';
|
|
443
|
+
}).join("");
|
|
444
|
+
document.querySelectorAll("[data-tab]").forEach(function(btn){
|
|
445
|
+
btn.addEventListener("click",function(){
|
|
446
|
+
activeTab=btn.dataset.tab;
|
|
447
|
+
localStorage.setItem("beignet-devtools-tab",activeTab);
|
|
448
|
+
render();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function renderWatcherFilter(){
|
|
454
|
+
const selected=$watcherFilter.value;
|
|
455
|
+
const names=new Map();
|
|
456
|
+
watchers.forEach(function(watcher){
|
|
457
|
+
if(watcher.enabled)names.set(watcher.name,watcher.label||watcher.name);
|
|
458
|
+
});
|
|
459
|
+
events.forEach(function(event){
|
|
460
|
+
const watcher=inferredWatcher(event);
|
|
461
|
+
if(watcher&&!names.has(watcher))names.set(watcher,watcher);
|
|
462
|
+
});
|
|
463
|
+
const options=['<option value="">All watchers</option>'].concat(
|
|
464
|
+
Array.from(names.entries()).sort(function(a,b){return a[1].localeCompare(b[1])}).map(function(entry){
|
|
465
|
+
return '<option value="'+esc(entry[0])+'">'+esc(entry[1])+'</option>';
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
$watcherFilter.innerHTML=options.join("");
|
|
469
|
+
if(selected&&names.has(selected)){
|
|
470
|
+
$watcherFilter.value=selected;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function eventRow(e, detail){
|
|
475
|
+
const time=e.timestamp?new Date(e.timestamp).toLocaleTimeString():"";
|
|
476
|
+
const rid=e.requestId?'<span class="request-id">'+esc(e.requestId)+'</span>':"";
|
|
477
|
+
const trace=e.traceId?'<span class="trace-id">trace '+esc(e.traceId.slice(0,8))+'</span>':"";
|
|
478
|
+
return '<article class="event" onclick="this.classList.toggle(\\'open\\')">'+
|
|
479
|
+
'<div class="event-header">'+
|
|
480
|
+
'<span class="event-time">'+esc(time)+'</span>'+
|
|
481
|
+
eventBadge(e)+
|
|
482
|
+
rid+
|
|
483
|
+
trace+
|
|
484
|
+
'<span class="event-summary">'+esc(summarize(e))+'</span>'+
|
|
485
|
+
'</div>'+
|
|
486
|
+
'<div class="event-detail">'+detail+'</div>'+
|
|
487
|
+
'</article>';
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function renderTimeline(list){
|
|
491
|
+
return list.slice().reverse().map(function(event){
|
|
492
|
+
return eventRow(event,json(event));
|
|
493
|
+
}).join("");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function renderRequests(list){
|
|
497
|
+
return list.slice().reverse().map(function(request){
|
|
498
|
+
const related=correlatedEvents(request);
|
|
499
|
+
const relatedHtml=related.length
|
|
500
|
+
? related.slice().reverse().map(function(event){
|
|
501
|
+
return '<div class="correlated-row">'+eventBadge(event)+'<span>'+esc(summarize(event))+'</span></div>';
|
|
502
|
+
}).join("")
|
|
503
|
+
: '<div class="empty">No correlated events for this request.</div>';
|
|
504
|
+
const detail='<div class="request-detail">'+
|
|
505
|
+
'<section class="detail-section"><div class="detail-title">Request</div><pre>'+json(request)+'</pre></section>'+
|
|
506
|
+
'<section class="detail-section"><div class="detail-title">Correlated events</div><div class="correlated">'+relatedHtml+'</div></section>'+
|
|
507
|
+
'</div>';
|
|
508
|
+
return eventRow(request,detail);
|
|
509
|
+
}).join("");
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function detailsObject(event){
|
|
513
|
+
return event.details&&typeof event.details==="object"&&!Array.isArray(event.details)?event.details:{};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function detailValue(event,key){
|
|
517
|
+
return detailsObject(event)[key];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function durationFor(event){
|
|
521
|
+
const detailDuration=detailValue(event,"durationMs");
|
|
522
|
+
const duration=event.durationMs!==undefined?event.durationMs:detailDuration;
|
|
523
|
+
return typeof duration==="number"&&Number.isFinite(duration)?duration:undefined;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function providerFor(event){
|
|
527
|
+
const details=detailsObject(event);
|
|
528
|
+
return details.providerName||details.provider||details.portName||"";
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function failureFor(event){
|
|
532
|
+
const details=detailsObject(event);
|
|
533
|
+
if(event.type==="error")return true;
|
|
534
|
+
if(typeof event.status==="string")return event.status==="failed";
|
|
535
|
+
if(typeof event.name==="string"&&event.name.indexOf("failed")>=0)return true;
|
|
536
|
+
return Boolean(details.error)||details.allowed===false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function averageDuration(list){
|
|
540
|
+
const durations=list.map(durationFor).filter(function(value){return value!==undefined});
|
|
541
|
+
if(!durations.length)return "n/a";
|
|
542
|
+
const total=durations.reduce(function(sum,value){return sum+value},0);
|
|
543
|
+
return Math.round(total/durations.length)+"ms";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function distinctCount(list,selector){
|
|
547
|
+
const values=new Set();
|
|
548
|
+
list.forEach(function(event){
|
|
549
|
+
const value=selector(event);
|
|
550
|
+
if(value!==undefined&&value!==null&&String(value)!=="")values.add(String(value));
|
|
551
|
+
});
|
|
552
|
+
return values.size;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function metric(label,value){
|
|
556
|
+
return '<div class="focus-metric"><div class="focus-metric-label">'+esc(label)+'</div><div class="focus-metric-value">'+esc(value)+'</div></div>';
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function panelMeta(tab){
|
|
560
|
+
switch(tab.id){
|
|
561
|
+
case "events": return {title:"Domain events",subtitle:"Published facts and event bus activity"};
|
|
562
|
+
case "jobs": return {title:"Jobs",subtitle:"Background job lifecycle"};
|
|
563
|
+
case "schedules": return {title:"Schedules",subtitle:"Cron and scheduled task runs"};
|
|
564
|
+
case "db": return {title:"Database",subtitle:"Queries and database provider diagnostics"};
|
|
565
|
+
case "cache": return {title:"Cache",subtitle:"Reads, writes, deletes, and hit rates"};
|
|
566
|
+
case "storage": return {title:"Storage",subtitle:"Object storage operations"};
|
|
567
|
+
case "mail": return {title:"Mail",subtitle:"Delivery attempts and provider results"};
|
|
568
|
+
case "auth": return {title:"Auth",subtitle:"Session and authentication activity"};
|
|
569
|
+
case "audit": return {title:"Audit",subtitle:"Durable activity records emitted by application code"};
|
|
570
|
+
case "rateLimit": return {title:"Rate limits",subtitle:"Allowed, blocked, and failed checks"};
|
|
571
|
+
default: return {title:tab.label,subtitle:"Subsystem activity"};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function panelMetrics(tab,list){
|
|
576
|
+
switch(tab.id){
|
|
577
|
+
case "events":
|
|
578
|
+
return [
|
|
579
|
+
metric("Events",list.length),
|
|
580
|
+
metric("Names",distinctCount(list,function(event){return event.eventName})),
|
|
581
|
+
metric("Traces",distinctCount(list,function(event){return event.traceId})),
|
|
582
|
+
metric("Requests",distinctCount(list,function(event){return event.requestId}))
|
|
583
|
+
].join("");
|
|
584
|
+
case "jobs":
|
|
585
|
+
return [
|
|
586
|
+
metric("Jobs",list.length),
|
|
587
|
+
metric("Failed",list.filter(failureFor).length),
|
|
588
|
+
metric("Names",distinctCount(list,function(event){return event.jobName})),
|
|
589
|
+
metric("Providers",distinctCount(list,providerFor))
|
|
590
|
+
].join("");
|
|
591
|
+
case "schedules":
|
|
592
|
+
return [
|
|
593
|
+
metric("Runs",list.length),
|
|
594
|
+
metric("Failed",list.filter(failureFor).length),
|
|
595
|
+
metric("Names",distinctCount(list,function(event){return event.scheduleName})),
|
|
596
|
+
metric("Cron",distinctCount(list,function(event){return event.cron}))
|
|
597
|
+
].join("");
|
|
598
|
+
case "cache":
|
|
599
|
+
return [
|
|
600
|
+
metric("Operations",list.length),
|
|
601
|
+
metric("Hits",list.filter(function(event){return detailValue(event,"hit")===true}).length),
|
|
602
|
+
metric("Failures",list.filter(failureFor).length),
|
|
603
|
+
metric("Avg",averageDuration(list))
|
|
604
|
+
].join("");
|
|
605
|
+
case "auth":
|
|
606
|
+
return [
|
|
607
|
+
metric("Events",list.length),
|
|
608
|
+
metric("Authenticated",list.filter(function(event){return detailValue(event,"authenticated")===true}).length),
|
|
609
|
+
metric("Failures",list.filter(failureFor).length),
|
|
610
|
+
metric("Avg",averageDuration(list))
|
|
611
|
+
].join("");
|
|
612
|
+
case "audit":
|
|
613
|
+
return [
|
|
614
|
+
metric("Entries",list.length),
|
|
615
|
+
metric("Failures",list.filter(function(event){return detailValue(event,"outcome")==="failure"}).length),
|
|
616
|
+
metric("Actors",distinctCount(list,function(event){const actor=detailValue(event,"actor");return actor&&actor.id})),
|
|
617
|
+
metric("Resources",distinctCount(list,function(event){const resource=detailValue(event,"resource");return resource&&resource.id}))
|
|
618
|
+
].join("");
|
|
619
|
+
case "rateLimit":
|
|
620
|
+
return [
|
|
621
|
+
metric("Checks",list.length),
|
|
622
|
+
metric("Allowed",list.filter(function(event){return detailValue(event,"allowed")===true}).length),
|
|
623
|
+
metric("Blocked",list.filter(function(event){return detailValue(event,"allowed")===false}).length),
|
|
624
|
+
metric("Avg",averageDuration(list))
|
|
625
|
+
].join("");
|
|
626
|
+
default:
|
|
627
|
+
return [
|
|
628
|
+
metric("Events",list.length),
|
|
629
|
+
metric("Failures",list.filter(failureFor).length),
|
|
630
|
+
metric("Providers",distinctCount(list,providerFor)),
|
|
631
|
+
metric("Avg",averageDuration(list))
|
|
632
|
+
].join("");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function primaryFor(event){
|
|
637
|
+
switch(event.type){
|
|
638
|
+
case "eventBus": return event.eventName||"Domain event";
|
|
639
|
+
case "job": return event.jobName||"Job";
|
|
640
|
+
case "schedule": return event.scheduleName||"Schedule";
|
|
641
|
+
case "custom": return event.label||event.name||"Custom event";
|
|
642
|
+
default: return summarize(event);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function secondaryFor(event){
|
|
647
|
+
const details=detailsObject(event);
|
|
648
|
+
if(details.operation)return details.operation;
|
|
649
|
+
if(event.type==="job")return event.status;
|
|
650
|
+
if(event.type==="schedule")return event.status;
|
|
651
|
+
if(event.type==="eventBus")return event.requestId||event.traceId||"";
|
|
652
|
+
if(event.name)return event.name;
|
|
653
|
+
return inferredWatcher(event);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function resultFor(event){
|
|
657
|
+
const details=detailsObject(event);
|
|
658
|
+
if(details.allowed===true)return "allowed";
|
|
659
|
+
if(details.allowed===false)return "blocked";
|
|
660
|
+
if(details.hit===true)return "hit";
|
|
661
|
+
if(details.hit===false)return "miss";
|
|
662
|
+
if(details.authenticated===true)return "signed in";
|
|
663
|
+
if(details.authenticated===false)return "guest";
|
|
664
|
+
if(event.status)return event.status;
|
|
665
|
+
if(details.error)return "failed";
|
|
666
|
+
return providerFor(event)||inferredWatcher(event);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function tableRow(event){
|
|
670
|
+
const time=event.timestamp?new Date(event.timestamp).toLocaleTimeString():"";
|
|
671
|
+
const duration=durationFor(event);
|
|
672
|
+
const durationLabel=duration===undefined?"":duration+"ms";
|
|
673
|
+
const provider=providerFor(event);
|
|
674
|
+
return '<article class="table-row" onclick="this.classList.toggle(\\'open\\')">'+
|
|
675
|
+
'<div class="table-cell">'+esc(time)+'</div>'+
|
|
676
|
+
'<div class="table-cell"><strong>'+esc(primaryFor(event))+'</strong><code>'+esc(secondaryFor(event))+'</code></div>'+
|
|
677
|
+
'<div class="table-cell table-summary">'+esc(summarize(event))+'</div>'+
|
|
678
|
+
'<div class="table-cell">'+esc(durationLabel)+'</div>'+
|
|
679
|
+
'<div class="table-cell">'+esc(resultFor(event)||provider)+'</div>'+
|
|
680
|
+
'<pre class="table-detail">'+json(event)+'</pre>'+
|
|
681
|
+
'</article>';
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function renderFocusPanel(tab,list){
|
|
685
|
+
const meta=panelMeta(tab);
|
|
686
|
+
const rows=list.slice().reverse().map(tableRow).join("");
|
|
687
|
+
return '<section class="focus-panel">'+
|
|
688
|
+
'<div class="focus-head">'+
|
|
689
|
+
'<div><h2 class="focus-title">'+esc(meta.title)+'</h2><div class="focus-subtitle">'+esc(meta.subtitle)+'</div></div>'+
|
|
690
|
+
'<div class="focus-metrics">'+panelMetrics(tab,list)+'</div>'+
|
|
691
|
+
'</div>'+
|
|
692
|
+
'<div class="event-table">'+rows+'</div>'+
|
|
693
|
+
'</section>';
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function render(){
|
|
697
|
+
updateStats();
|
|
698
|
+
renderTabs();
|
|
699
|
+
renderWatcherFilter();
|
|
700
|
+
$toggle.textContent=paused?"Resume":"Pause";
|
|
701
|
+
$dot.classList.toggle("paused",paused);
|
|
702
|
+
const list=filteredEvents();
|
|
703
|
+
if(!list.length){
|
|
704
|
+
$content.innerHTML='<div class="empty">No events match this view.</div>';
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const tab=tabFor(activeTab);
|
|
708
|
+
if(activeTab==="requests"){
|
|
709
|
+
$content.innerHTML=renderRequests(list);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if(FOCUS_PANEL_TABS[tab.id]){
|
|
713
|
+
$content.innerHTML=renderFocusPanel(tab,list);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
$content.innerHTML=renderTimeline(list);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function setStatus(text, pausedDot){
|
|
720
|
+
$statusText.textContent=text;
|
|
721
|
+
$dot.classList.toggle("paused",Boolean(pausedDot)||paused);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async function fetchEvents(){
|
|
725
|
+
try{
|
|
726
|
+
const res=await fetch(BASE+"/core/events?limit=500");
|
|
727
|
+
if(!res.ok)return;
|
|
728
|
+
const data=await res.json();
|
|
729
|
+
events=data.events||[];
|
|
730
|
+
watchers=data.watchers||watchers;
|
|
731
|
+
setStatus("Polling",false);
|
|
732
|
+
if(!paused)render();
|
|
733
|
+
}catch(error){
|
|
734
|
+
setStatus("Disconnected",true);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function startPolling(){
|
|
739
|
+
fetchEvents();
|
|
740
|
+
timer=setInterval(fetchEvents,1500);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function connectStream(){
|
|
744
|
+
if(!("EventSource" in window)){
|
|
745
|
+
startPolling();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
source=new EventSource(BASE+"/stream");
|
|
749
|
+
source.onopen=function(){setStatus("Live",false)};
|
|
750
|
+
source.addEventListener("snapshot",function(message){
|
|
751
|
+
const data=JSON.parse(message.data);
|
|
752
|
+
events=data.events||[];
|
|
753
|
+
watchers=data.watchers||watchers;
|
|
754
|
+
if(!paused)render();
|
|
755
|
+
});
|
|
756
|
+
source.addEventListener("event",function(message){
|
|
757
|
+
const event=JSON.parse(message.data).event;
|
|
758
|
+
events=events.concat(event).slice(-500);
|
|
759
|
+
if(!paused)render();
|
|
760
|
+
});
|
|
761
|
+
source.addEventListener("clear",function(){
|
|
762
|
+
events=[];
|
|
763
|
+
if(!paused)render();
|
|
764
|
+
});
|
|
765
|
+
source.onerror=function(){setStatus("Reconnecting",true)};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
$toggle.addEventListener("click",function(){
|
|
769
|
+
paused=!paused;
|
|
770
|
+
localStorage.setItem("beignet-devtools-paused",String(paused));
|
|
771
|
+
if(!paused)render();
|
|
772
|
+
else setStatus("Paused",true);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
$clear.addEventListener("click",async function(){
|
|
776
|
+
await fetch(BASE+"/clear",{method:"POST"});
|
|
777
|
+
if(!source)await fetchEvents();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
let searchTimer=null;
|
|
781
|
+
function scheduleRender(){
|
|
782
|
+
clearTimeout(searchTimer);
|
|
783
|
+
searchTimer=setTimeout(render,120);
|
|
784
|
+
}
|
|
785
|
+
$search.addEventListener("input",scheduleRender);
|
|
786
|
+
$methodFilter.addEventListener("change",render);
|
|
787
|
+
$statusFilter.addEventListener("change",render);
|
|
788
|
+
$watcherFilter.addEventListener("change",render);
|
|
789
|
+
$resetFilters.addEventListener("click",function(){
|
|
790
|
+
$search.value="";
|
|
791
|
+
$methodFilter.value="";
|
|
792
|
+
$statusFilter.value="";
|
|
793
|
+
$watcherFilter.value="";
|
|
794
|
+
render();
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
render();
|
|
798
|
+
connectStream();
|
|
799
|
+
window.addEventListener("beforeunload",function(){
|
|
800
|
+
if(source)source.close();
|
|
801
|
+
if(timer)clearInterval(timer);
|
|
802
|
+
});
|
|
803
|
+
})();
|
|
804
|
+
</script>
|
|
805
|
+
</body>
|
|
806
|
+
</html>`;
|
|
807
|
+
}
|