@composed-di/observability 0.5.0-alpha → 0.6.0-alpha
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/dist/cli.js.map +1 -1
- package/dist/dashboardClient.d.ts +19 -11
- package/dist/dashboardClient.d.ts.map +1 -1
- package/dist/dashboardClient.js +30 -11
- package/dist/dashboardClient.js.map +1 -1
- package/dist/dashboardEventListener.d.ts +27 -8
- package/dist/dashboardEventListener.d.ts.map +1 -1
- package/dist/dashboardEventListener.js +38 -16
- package/dist/dashboardEventListener.js.map +1 -1
- package/dist/dashboardHtml.d.ts +6 -0
- package/dist/dashboardHtml.d.ts.map +1 -1
- package/dist/dashboardHtml.js +618 -98
- package/dist/dashboardHtml.js.map +1 -1
- package/dist/dashboardInstrumentation.d.ts +42 -0
- package/dist/dashboardInstrumentation.d.ts.map +1 -0
- package/dist/dashboardInstrumentation.js +116 -0
- package/dist/dashboardInstrumentation.js.map +1 -0
- package/dist/dashboardServer.d.ts +14 -8
- package/dist/dashboardServer.d.ts.map +1 -1
- package/dist/dashboardServer.js +54 -14
- package/dist/dashboardServer.js.map +1 -1
- package/dist/events.d.ts +20 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/moduleGraph.d.ts.map +1 -1
- package/dist/moduleGraph.js.map +1 -1
- package/package.json +24 -23
- package/src/cli.ts +19 -19
- package/src/dashboardClient.ts +96 -83
- package/src/dashboardHtml.ts +620 -100
- package/src/dashboardInstrumentation.ts +151 -0
- package/src/dashboardServer.ts +185 -148
- package/src/events.ts +55 -35
- package/src/index.ts +5 -5
- package/src/moduleGraph.ts +9 -9
- package/src/dashboardEventListener.ts +0 -104
package/src/dashboardHtml.ts
CHANGED
|
@@ -6,9 +6,15 @@
|
|
|
6
6
|
* dependency ranks, dependencies on the left), a stat-tile row, a services
|
|
7
7
|
* table, and an event log. Service state is shown as a status glyph + word,
|
|
8
8
|
* never color alone.
|
|
9
|
+
*
|
|
10
|
+
* Visual language: warm paper surfaces, ink text, hairline rules. The orange
|
|
11
|
+
* accent is reserved for live activity (call flashes, active edges,
|
|
12
|
+
* selection); blue means initializing, green means ready. Display type is
|
|
13
|
+
* Poppins and editorial notes are Lora — both resolve to local fonts only
|
|
14
|
+
* (Arial / Georgia fallbacks), keeping the no-CDN contract.
|
|
9
15
|
*/
|
|
10
16
|
export function renderDashboardHtml(): string {
|
|
11
|
-
return HTML
|
|
17
|
+
return HTML
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
const HTML = `<!doctype html>
|
|
@@ -19,147 +25,229 @@ const HTML = `<!doctype html>
|
|
|
19
25
|
<title>composed-di dashboard</title>
|
|
20
26
|
<style>
|
|
21
27
|
:root {
|
|
22
|
-
--page: #
|
|
23
|
-
--surface: #
|
|
24
|
-
--ink: #
|
|
25
|
-
--ink-secondary: #
|
|
26
|
-
--ink-muted: #
|
|
27
|
-
--
|
|
28
|
-
--
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
32
|
-
--
|
|
28
|
+
--page: #faf9f5;
|
|
29
|
+
--surface: #fffefb;
|
|
30
|
+
--ink: #141413;
|
|
31
|
+
--ink-secondary: #5c5a53;
|
|
32
|
+
--ink-muted: #87857c;
|
|
33
|
+
--ink-faint: #b0aea5;
|
|
34
|
+
--grid: #e8e6dc;
|
|
35
|
+
--edge: #d6d3c8;
|
|
36
|
+
--border: rgba(20, 20, 19, 0.10);
|
|
37
|
+
--accent: #d97757;
|
|
38
|
+
--good: #788c5d;
|
|
39
|
+
--busy: #6a9bcc;
|
|
40
|
+
--critical: #b8442f;
|
|
41
|
+
--shadow: 0 1px 2px rgba(20, 20, 19, 0.04);
|
|
42
|
+
--overlay-shadow: -14px 0 32px rgba(20, 20, 19, 0.14);
|
|
43
|
+
--font-display: "Poppins", "Avenir Next", "Segoe UI", Arial, sans-serif;
|
|
44
|
+
--font-serif: "Lora", Georgia, "Times New Roman", serif;
|
|
45
|
+
--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
33
46
|
}
|
|
34
47
|
/* Dark values apply via the system preference, or ?theme=dark|light. */
|
|
35
48
|
@media (prefers-color-scheme: dark) {
|
|
36
49
|
:root:not([data-theme="light"]) {
|
|
37
|
-
--page: #
|
|
38
|
-
--surface: #
|
|
39
|
-
--ink: #
|
|
40
|
-
--ink-secondary: #
|
|
41
|
-
--ink-muted: #
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--
|
|
50
|
+
--page: #141413;
|
|
51
|
+
--surface: #1e1d1a;
|
|
52
|
+
--ink: #faf9f5;
|
|
53
|
+
--ink-secondary: #b0aea5;
|
|
54
|
+
--ink-muted: #8c8a80;
|
|
55
|
+
--ink-faint: #57554e;
|
|
56
|
+
--grid: #2c2b27;
|
|
57
|
+
--edge: #3a382f;
|
|
58
|
+
--border: rgba(250, 249, 245, 0.10);
|
|
59
|
+
--accent: #d97757;
|
|
60
|
+
--good: #8fa571;
|
|
61
|
+
--busy: #7ba3ce;
|
|
62
|
+
--critical: #dd5f4b;
|
|
63
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
|
64
|
+
--overlay-shadow: -14px 0 32px rgba(0, 0, 0, 0.45);
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
67
|
:root[data-theme="dark"] {
|
|
48
|
-
--page: #
|
|
49
|
-
--surface: #
|
|
50
|
-
--ink: #
|
|
51
|
-
--ink-secondary: #
|
|
52
|
-
--ink-muted: #
|
|
53
|
-
--
|
|
54
|
-
--
|
|
55
|
-
--
|
|
68
|
+
--page: #141413;
|
|
69
|
+
--surface: #1e1d1a;
|
|
70
|
+
--ink: #faf9f5;
|
|
71
|
+
--ink-secondary: #b0aea5;
|
|
72
|
+
--ink-muted: #8c8a80;
|
|
73
|
+
--ink-faint: #57554e;
|
|
74
|
+
--grid: #2c2b27;
|
|
75
|
+
--edge: #3a382f;
|
|
76
|
+
--border: rgba(250, 249, 245, 0.10);
|
|
77
|
+
--accent: #d97757;
|
|
78
|
+
--good: #8fa571;
|
|
79
|
+
--busy: #7ba3ce;
|
|
80
|
+
--critical: #dd5f4b;
|
|
81
|
+
--shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
|
82
|
+
--overlay-shadow: -14px 0 32px rgba(0, 0, 0, 0.45);
|
|
56
83
|
}
|
|
57
84
|
* { box-sizing: border-box; }
|
|
58
85
|
body {
|
|
59
86
|
margin: 0;
|
|
60
87
|
background: var(--page);
|
|
88
|
+
background-image: radial-gradient(1100px 380px at 75% -120px,
|
|
89
|
+
color-mix(in srgb, var(--accent) 6%, transparent), transparent 70%);
|
|
61
90
|
color: var(--ink);
|
|
62
|
-
font: 14px/1.
|
|
91
|
+
font: 14px/1.5 var(--font-display);
|
|
92
|
+
}
|
|
93
|
+
/* Brand rule: a single orange hairline crowning the page. */
|
|
94
|
+
body::before {
|
|
95
|
+
content: "";
|
|
96
|
+
position: fixed;
|
|
97
|
+
top: 0; left: 0; right: 0;
|
|
98
|
+
height: 3px;
|
|
99
|
+
background: var(--accent);
|
|
100
|
+
z-index: 30;
|
|
63
101
|
}
|
|
102
|
+
::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
|
|
103
|
+
|
|
104
|
+
@keyframes rise {
|
|
105
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
106
|
+
}
|
|
107
|
+
header, .tiles, main > * { animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
|
|
108
|
+
.tiles { animation-delay: 0.06s; }
|
|
109
|
+
main > :nth-child(1) { animation-delay: 0.12s; }
|
|
110
|
+
main > :nth-child(2) { animation-delay: 0.18s; }
|
|
111
|
+
|
|
64
112
|
header {
|
|
65
113
|
display: flex;
|
|
66
|
-
align-items:
|
|
67
|
-
gap:
|
|
68
|
-
padding:
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 10px;
|
|
116
|
+
padding: 20px 24px 0;
|
|
117
|
+
}
|
|
118
|
+
header .mark { flex: none; }
|
|
119
|
+
header h1 {
|
|
120
|
+
font-size: 17px;
|
|
121
|
+
font-weight: 600;
|
|
122
|
+
letter-spacing: -0.01em;
|
|
123
|
+
margin: 0;
|
|
124
|
+
}
|
|
125
|
+
header .sub {
|
|
126
|
+
color: var(--ink-muted);
|
|
127
|
+
font-family: var(--font-serif);
|
|
128
|
+
font-style: italic;
|
|
129
|
+
font-size: 13px;
|
|
69
130
|
}
|
|
70
|
-
header h1 { font-size: 16px; font-weight: 650; margin: 0; }
|
|
71
|
-
header .sub { color: var(--ink-muted); font-size: 12px; }
|
|
72
131
|
#conn {
|
|
73
132
|
margin-left: auto;
|
|
74
133
|
font-size: 12px;
|
|
75
134
|
color: var(--ink-secondary);
|
|
76
135
|
border: 1px solid var(--border);
|
|
136
|
+
background: var(--surface);
|
|
77
137
|
border-radius: 999px;
|
|
78
|
-
padding:
|
|
138
|
+
padding: 3px 12px;
|
|
79
139
|
}
|
|
80
|
-
#conn.live::before {
|
|
81
|
-
|
|
140
|
+
#conn.live::before {
|
|
141
|
+
content: "\\25CF ";
|
|
142
|
+
color: var(--good);
|
|
143
|
+
animation: breathe 2.4s ease-in-out infinite;
|
|
144
|
+
}
|
|
145
|
+
#conn.down::before { content: "\\25CF "; color: var(--critical); }
|
|
146
|
+
@keyframes breathe { 50% { opacity: 0.35; } }
|
|
82
147
|
|
|
83
148
|
.tiles {
|
|
84
149
|
display: flex;
|
|
85
150
|
flex-wrap: wrap;
|
|
86
151
|
gap: 12px;
|
|
87
|
-
padding: 14px
|
|
152
|
+
padding: 18px 24px 14px;
|
|
88
153
|
}
|
|
89
154
|
.tile {
|
|
90
155
|
background: var(--surface);
|
|
91
156
|
border: 1px solid var(--border);
|
|
92
|
-
border-radius:
|
|
93
|
-
|
|
94
|
-
|
|
157
|
+
border-radius: 10px;
|
|
158
|
+
box-shadow: var(--shadow);
|
|
159
|
+
padding: 12px 18px 14px;
|
|
160
|
+
min-width: 148px;
|
|
95
161
|
}
|
|
96
162
|
.tile .label {
|
|
97
|
-
font-size:
|
|
98
|
-
letter-spacing: 0.
|
|
163
|
+
font-size: 10.5px;
|
|
164
|
+
letter-spacing: 0.08em;
|
|
99
165
|
text-transform: uppercase;
|
|
166
|
+
font-weight: 500;
|
|
100
167
|
color: var(--ink-muted);
|
|
101
168
|
}
|
|
102
|
-
.tile .value {
|
|
103
|
-
|
|
169
|
+
.tile .value {
|
|
170
|
+
font-size: 26px;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
letter-spacing: -0.01em;
|
|
173
|
+
margin-top: 2px;
|
|
174
|
+
font-variant-numeric: tabular-nums;
|
|
175
|
+
}
|
|
176
|
+
.tile .value.bad { color: var(--critical); }
|
|
177
|
+
.tile .value small { font-size: 13px; font-weight: 400; color: var(--ink-muted); }
|
|
104
178
|
|
|
105
179
|
main {
|
|
106
180
|
display: grid;
|
|
107
181
|
grid-template-columns: 1fr 340px;
|
|
108
|
-
gap:
|
|
109
|
-
padding: 0
|
|
182
|
+
gap: 14px;
|
|
183
|
+
padding: 0 24px 24px;
|
|
110
184
|
align-items: start;
|
|
111
185
|
}
|
|
112
186
|
.panel {
|
|
113
187
|
background: var(--surface);
|
|
114
188
|
border: 1px solid var(--border);
|
|
115
|
-
border-radius:
|
|
189
|
+
border-radius: 10px;
|
|
190
|
+
box-shadow: var(--shadow);
|
|
116
191
|
overflow: hidden;
|
|
117
192
|
}
|
|
118
193
|
.panel h2 {
|
|
119
|
-
font-size:
|
|
120
|
-
letter-spacing: 0.
|
|
194
|
+
font-size: 11px;
|
|
195
|
+
letter-spacing: 0.08em;
|
|
121
196
|
text-transform: uppercase;
|
|
122
197
|
color: var(--ink-muted);
|
|
123
198
|
font-weight: 600;
|
|
124
199
|
margin: 0;
|
|
125
|
-
padding:
|
|
200
|
+
padding: 12px 16px;
|
|
126
201
|
border-bottom: 1px solid var(--grid);
|
|
127
202
|
}
|
|
128
203
|
#graph-panel svg { display: block; width: 100%; }
|
|
129
|
-
#graph-empty {
|
|
204
|
+
#graph-empty {
|
|
205
|
+
padding: 40px 32px;
|
|
206
|
+
color: var(--ink-muted);
|
|
207
|
+
text-align: center;
|
|
208
|
+
font-family: var(--font-serif);
|
|
209
|
+
font-style: italic;
|
|
210
|
+
font-size: 14px;
|
|
211
|
+
}
|
|
130
212
|
|
|
131
213
|
.legend {
|
|
132
214
|
display: flex;
|
|
133
215
|
flex-wrap: wrap;
|
|
134
|
-
gap:
|
|
135
|
-
padding: 10px
|
|
216
|
+
gap: 16px;
|
|
217
|
+
padding: 10px 16px;
|
|
136
218
|
border-top: 1px solid var(--grid);
|
|
137
219
|
font-size: 12px;
|
|
138
220
|
color: var(--ink-secondary);
|
|
139
221
|
}
|
|
140
222
|
.legend .glyph { font-size: 11px; }
|
|
223
|
+
.legend .hint {
|
|
224
|
+
margin-left: auto;
|
|
225
|
+
color: var(--ink-muted);
|
|
226
|
+
font-family: var(--font-serif);
|
|
227
|
+
font-style: italic;
|
|
228
|
+
}
|
|
141
229
|
|
|
142
230
|
/* Graph marks */
|
|
143
231
|
.edge {
|
|
144
232
|
fill: none;
|
|
145
|
-
stroke: var(--
|
|
233
|
+
stroke: var(--edge);
|
|
146
234
|
stroke-width: 1.5;
|
|
147
235
|
transition: stroke 0.45s ease;
|
|
148
236
|
}
|
|
149
237
|
.edge.active { stroke: var(--accent); stroke-width: 2.5; transition: none; }
|
|
150
238
|
.node rect {
|
|
151
239
|
fill: var(--surface);
|
|
152
|
-
stroke: var(--ink-
|
|
240
|
+
stroke: var(--ink-faint);
|
|
153
241
|
stroke-width: 1.5;
|
|
154
|
-
rx:
|
|
242
|
+
rx: 8;
|
|
155
243
|
}
|
|
156
244
|
.node text { fill: var(--ink); font-size: 13px; font-weight: 600; }
|
|
157
245
|
.node .status { fill: var(--ink-secondary); font-size: 11px; font-weight: 400; }
|
|
158
246
|
.node .glyph { font-size: 10px; }
|
|
159
|
-
.node.pending rect { stroke: var(--ink-
|
|
247
|
+
.node.pending rect { stroke: var(--ink-faint); stroke-dasharray: 4 3; }
|
|
160
248
|
.node.pending .glyph { fill: var(--ink-muted); }
|
|
161
|
-
.node.initializing rect { stroke: var(--
|
|
162
|
-
.node.initializing .glyph { fill: var(--
|
|
249
|
+
.node.initializing rect { stroke: var(--busy); animation: pulse 1s ease-in-out infinite; }
|
|
250
|
+
.node.initializing .glyph { fill: var(--busy); }
|
|
163
251
|
.node.ready rect { stroke: var(--good); }
|
|
164
252
|
.node.ready .glyph { fill: var(--good); }
|
|
165
253
|
.node.error rect { stroke: var(--critical); stroke-width: 2; }
|
|
@@ -167,62 +255,209 @@ const HTML = `<!doctype html>
|
|
|
167
255
|
.node.disposed rect { stroke: var(--grid); }
|
|
168
256
|
.node.disposed .glyph { fill: var(--ink-muted); }
|
|
169
257
|
.node.disposed text { fill: var(--ink-muted); }
|
|
170
|
-
.node
|
|
258
|
+
.node { cursor: pointer; }
|
|
259
|
+
.node.flash rect {
|
|
260
|
+
stroke: var(--accent);
|
|
261
|
+
stroke-width: 2.5;
|
|
262
|
+
fill: color-mix(in srgb, var(--accent) 7%, var(--surface));
|
|
263
|
+
}
|
|
264
|
+
.node.selected rect { stroke: var(--accent); stroke-width: 2.5; }
|
|
265
|
+
.node:focus-visible { outline: none; }
|
|
266
|
+
.node:focus-visible rect { stroke: var(--accent); }
|
|
171
267
|
@keyframes pulse { 50% { stroke-opacity: 0.35; } }
|
|
172
268
|
|
|
173
269
|
/* Tables */
|
|
174
270
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
175
|
-
th, td { text-align: left; padding:
|
|
271
|
+
th, td { text-align: left; padding: 7px 16px; border-top: 1px solid var(--grid); }
|
|
176
272
|
thead th {
|
|
177
273
|
border-top: none;
|
|
178
|
-
font-size:
|
|
179
|
-
letter-spacing: 0.
|
|
274
|
+
font-size: 10.5px;
|
|
275
|
+
letter-spacing: 0.08em;
|
|
180
276
|
text-transform: uppercase;
|
|
181
277
|
color: var(--ink-muted);
|
|
182
278
|
font-weight: 600;
|
|
183
279
|
}
|
|
184
|
-
td.num, th.num { text-align: right;
|
|
280
|
+
td.num, th.num { text-align: right; }
|
|
281
|
+
td.num { font-family: var(--font-mono); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
282
|
+
td.err { color: var(--critical); font-weight: 600; }
|
|
185
283
|
td .glyph { font-size: 10px; }
|
|
186
284
|
|
|
187
|
-
/*
|
|
285
|
+
/* Clickable service rows + per-method drill-down */
|
|
286
|
+
tr.svc-row { cursor: pointer; }
|
|
287
|
+
tr.svc-row:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
|
|
288
|
+
tr.svc-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
289
|
+
tr.svc-row.selected { background: color-mix(in srgb, var(--accent) 10%, transparent); }
|
|
290
|
+
tr.svc-row .caret {
|
|
291
|
+
display: inline-block;
|
|
292
|
+
width: 14px;
|
|
293
|
+
color: var(--ink-faint);
|
|
294
|
+
font-size: 10px;
|
|
295
|
+
}
|
|
296
|
+
tr.method-detail > td { padding: 0; background: var(--page); }
|
|
297
|
+
.methods table { font-size: 12px; }
|
|
298
|
+
.methods thead th { font-size: 10px; }
|
|
299
|
+
.methods th:first-child, .methods td:first-child { padding-left: 46px; }
|
|
300
|
+
.methods .empty {
|
|
301
|
+
padding: 8px 16px 10px 46px;
|
|
302
|
+
color: var(--ink-muted);
|
|
303
|
+
font-family: var(--font-serif);
|
|
304
|
+
font-style: italic;
|
|
305
|
+
font-size: 12px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Event log (shared row styles with the inspector's call history) */
|
|
309
|
+
#log, .insp-body { scrollbar-width: thin; scrollbar-color: var(--grid) transparent; }
|
|
188
310
|
#log { max-height: 520px; overflow-y: auto; }
|
|
189
|
-
|
|
311
|
+
.log .row {
|
|
190
312
|
display: flex;
|
|
191
313
|
gap: 8px;
|
|
192
|
-
padding: 5px
|
|
314
|
+
padding: 5px 16px;
|
|
193
315
|
border-top: 1px solid var(--grid);
|
|
194
316
|
font-size: 12px;
|
|
195
317
|
align-items: baseline;
|
|
196
318
|
}
|
|
197
|
-
#log .
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
319
|
+
#log .row { animation: row-in 0.35s ease backwards; }
|
|
320
|
+
@keyframes row-in { from { opacity: 0; transform: translateX(4px); } }
|
|
321
|
+
.log .time {
|
|
322
|
+
color: var(--ink-muted);
|
|
323
|
+
font-family: var(--font-mono);
|
|
324
|
+
font-size: 11px;
|
|
325
|
+
white-space: nowrap;
|
|
326
|
+
}
|
|
327
|
+
.log .name { flex: 1; color: var(--ink); word-break: break-all; }
|
|
328
|
+
.log .dur {
|
|
329
|
+
color: var(--ink-secondary);
|
|
330
|
+
font-family: var(--font-mono);
|
|
331
|
+
font-size: 11px;
|
|
332
|
+
white-space: nowrap;
|
|
333
|
+
}
|
|
334
|
+
.log .row.error { box-shadow: inset 2px 0 0 var(--critical); }
|
|
335
|
+
.log .row.error .name { color: var(--critical); }
|
|
336
|
+
.log .row.lifecycle .name { color: var(--ink-secondary); }
|
|
337
|
+
.log .err-msg { color: var(--critical); }
|
|
338
|
+
.log .detail {
|
|
339
|
+
font-family: var(--font-mono);
|
|
340
|
+
font-size: 11px;
|
|
341
|
+
color: var(--ink-secondary);
|
|
342
|
+
word-break: break-all;
|
|
343
|
+
margin-top: 1px;
|
|
344
|
+
}
|
|
345
|
+
.log .detail .k { color: var(--ink-muted); }
|
|
346
|
+
|
|
347
|
+
/* Inspector side panel */
|
|
348
|
+
#inspector {
|
|
349
|
+
position: fixed;
|
|
350
|
+
top: 0;
|
|
351
|
+
right: 0;
|
|
352
|
+
bottom: 0;
|
|
353
|
+
width: 380px;
|
|
354
|
+
max-width: 92vw;
|
|
355
|
+
background: var(--surface);
|
|
356
|
+
border-left: 1px solid var(--border);
|
|
357
|
+
box-shadow: var(--overlay-shadow);
|
|
358
|
+
transform: translateX(100%);
|
|
359
|
+
visibility: hidden;
|
|
360
|
+
transition: transform 0.2s ease, visibility 0.2s;
|
|
361
|
+
z-index: 20;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-direction: column;
|
|
364
|
+
}
|
|
365
|
+
#inspector.open { transform: none; visibility: visible; }
|
|
366
|
+
.insp-head {
|
|
367
|
+
display: flex;
|
|
368
|
+
align-items: flex-start;
|
|
369
|
+
gap: 10px;
|
|
370
|
+
padding: 16px;
|
|
371
|
+
border-bottom: 1px solid var(--grid);
|
|
372
|
+
}
|
|
373
|
+
.insp-head .insp-name { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; word-break: break-all; }
|
|
374
|
+
.insp-head .insp-status { font-size: 12px; color: var(--ink-secondary); margin-top: 2px; }
|
|
375
|
+
.insp-head .insp-status .glyph { font-size: 10px; }
|
|
376
|
+
#insp-close {
|
|
377
|
+
margin-left: auto;
|
|
378
|
+
background: none;
|
|
379
|
+
border: 1px solid var(--border);
|
|
380
|
+
border-radius: 999px;
|
|
381
|
+
color: var(--ink-secondary);
|
|
382
|
+
font: inherit;
|
|
383
|
+
font-size: 14px;
|
|
384
|
+
line-height: 1;
|
|
385
|
+
padding: 5px 9px;
|
|
386
|
+
cursor: pointer;
|
|
387
|
+
transition: color 0.15s ease, border-color 0.15s ease;
|
|
388
|
+
}
|
|
389
|
+
#insp-close:hover { color: var(--ink); border-color: var(--ink-faint); }
|
|
390
|
+
#insp-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
391
|
+
.insp-body { flex: 1; overflow-y: auto; padding-bottom: 12px; }
|
|
392
|
+
.insp-body h3 {
|
|
393
|
+
font-size: 10.5px;
|
|
394
|
+
letter-spacing: 0.08em;
|
|
395
|
+
text-transform: uppercase;
|
|
396
|
+
color: var(--ink-muted);
|
|
397
|
+
font-weight: 600;
|
|
398
|
+
margin: 0;
|
|
399
|
+
padding: 16px 16px 4px;
|
|
400
|
+
}
|
|
401
|
+
#insp-summary { padding-top: 10px; }
|
|
402
|
+
.i-row {
|
|
403
|
+
display: flex;
|
|
404
|
+
justify-content: space-between;
|
|
405
|
+
padding: 2px 16px;
|
|
406
|
+
font-size: 12px;
|
|
407
|
+
color: var(--ink-secondary);
|
|
408
|
+
}
|
|
409
|
+
.i-row b {
|
|
410
|
+
color: var(--ink);
|
|
411
|
+
font-weight: 500;
|
|
412
|
+
font-family: var(--font-mono);
|
|
413
|
+
font-size: 11.5px;
|
|
414
|
+
}
|
|
415
|
+
#insp-methods table { font-size: 12px; }
|
|
416
|
+
#insp-methods thead th { font-size: 10px; }
|
|
417
|
+
.empty-note {
|
|
418
|
+
padding: 4px 16px 6px;
|
|
419
|
+
color: var(--ink-muted);
|
|
420
|
+
font-family: var(--font-serif);
|
|
421
|
+
font-style: italic;
|
|
422
|
+
font-size: 12px;
|
|
423
|
+
}
|
|
203
424
|
|
|
204
425
|
#tooltip {
|
|
205
426
|
position: fixed;
|
|
206
427
|
display: none;
|
|
207
428
|
background: var(--surface);
|
|
208
429
|
border: 1px solid var(--border);
|
|
209
|
-
border-radius:
|
|
210
|
-
box-shadow: 0
|
|
211
|
-
padding:
|
|
430
|
+
border-radius: 8px;
|
|
431
|
+
box-shadow: 0 6px 20px rgba(20, 20, 19, 0.18);
|
|
432
|
+
padding: 10px 12px;
|
|
212
433
|
font-size: 12px;
|
|
213
434
|
pointer-events: none;
|
|
214
435
|
z-index: 10;
|
|
215
436
|
max-width: 280px;
|
|
216
437
|
}
|
|
217
|
-
#tooltip .t-name { font-weight:
|
|
438
|
+
#tooltip .t-name { font-weight: 600; margin-bottom: 4px; }
|
|
218
439
|
#tooltip .t-row { display: flex; justify-content: space-between; gap: 16px; color: var(--ink-secondary); }
|
|
219
|
-
#tooltip .t-row b {
|
|
440
|
+
#tooltip .t-row b {
|
|
441
|
+
color: var(--ink);
|
|
442
|
+
font-weight: 500;
|
|
443
|
+
font-family: var(--font-mono);
|
|
444
|
+
font-size: 11.5px;
|
|
445
|
+
}
|
|
220
446
|
|
|
221
447
|
@media (max-width: 900px) { main { grid-template-columns: 1fr; } }
|
|
448
|
+
@media (prefers-reduced-motion: reduce) {
|
|
449
|
+
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
450
|
+
}
|
|
222
451
|
</style>
|
|
223
452
|
</head>
|
|
224
453
|
<body>
|
|
225
454
|
<header>
|
|
455
|
+
<svg class="mark" width="22" height="22" viewBox="0 0 22 22" aria-hidden="true">
|
|
456
|
+
<path d="M7 6.5 L15 11 M7 15.5 L15 11" stroke="var(--ink-faint)" stroke-width="1.5" fill="none"/>
|
|
457
|
+
<circle cx="6" cy="6" r="3" fill="#6a9bcc"/>
|
|
458
|
+
<circle cx="6" cy="16" r="3" fill="#788c5d"/>
|
|
459
|
+
<circle cx="16" cy="11" r="3.5" fill="#d97757"/>
|
|
460
|
+
</svg>
|
|
226
461
|
<h1>composed-di</h1>
|
|
227
462
|
<span class="sub">service dashboard</span>
|
|
228
463
|
<span id="conn" class="down">connecting…</span>
|
|
@@ -241,15 +476,15 @@ const HTML = `<!doctype html>
|
|
|
241
476
|
<h2>Dependency graph</h2>
|
|
242
477
|
<div id="graph-host"><div id="graph-empty">Waiting for module…</div></div>
|
|
243
478
|
<div class="legend">
|
|
244
|
-
<span><span class="glyph" style="color:var(--
|
|
245
|
-
<span><span class="glyph" style="color:var(--warning)">◐</span> initializing</span>
|
|
479
|
+
<span><span class="glyph" style="color:var(--busy)">◐</span> initializing</span>
|
|
246
480
|
<span><span class="glyph" style="color:var(--good)">●</span> ready</span>
|
|
247
481
|
<span><span class="glyph" style="color:var(--critical)">✕</span> error</span>
|
|
248
482
|
<span><span class="glyph" style="color:var(--ink-muted)">◌</span> disposed</span>
|
|
249
483
|
<span><span class="glyph" style="color:var(--accent)">━</span> edge / node flash = live method call</span>
|
|
484
|
+
<span class="hint">click a node to inspect its calls</span>
|
|
250
485
|
</div>
|
|
251
486
|
</div>
|
|
252
|
-
<div class="panel" style="margin-top:
|
|
487
|
+
<div class="panel" style="margin-top:14px">
|
|
253
488
|
<h2>Services</h2>
|
|
254
489
|
<table>
|
|
255
490
|
<thead><tr>
|
|
@@ -263,9 +498,25 @@ const HTML = `<!doctype html>
|
|
|
263
498
|
</div>
|
|
264
499
|
<div class="panel">
|
|
265
500
|
<h2>Event log</h2>
|
|
266
|
-
<div id="log"></div>
|
|
501
|
+
<div id="log" class="log"></div>
|
|
267
502
|
</div>
|
|
268
503
|
</main>
|
|
504
|
+
<aside id="inspector" aria-label="Service inspector">
|
|
505
|
+
<div class="insp-head">
|
|
506
|
+
<div>
|
|
507
|
+
<div class="insp-name" id="insp-name"></div>
|
|
508
|
+
<div class="insp-status" id="insp-status"></div>
|
|
509
|
+
</div>
|
|
510
|
+
<button id="insp-close" aria-label="Close inspector" title="Close (Esc)">✕</button>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="insp-body">
|
|
513
|
+
<div id="insp-summary"></div>
|
|
514
|
+
<h3>Methods</h3>
|
|
515
|
+
<div id="insp-methods"></div>
|
|
516
|
+
<h3>Call history</h3>
|
|
517
|
+
<div id="insp-history" class="log"></div>
|
|
518
|
+
</div>
|
|
519
|
+
</aside>
|
|
269
520
|
<div id="tooltip"></div>
|
|
270
521
|
|
|
271
522
|
<script>
|
|
@@ -286,8 +537,12 @@ const HTML = `<!doctype html>
|
|
|
286
537
|
};
|
|
287
538
|
var NODE_W = 176, NODE_H = 56, COL_W = 248, ROW_H = 84, PAD = 24;
|
|
288
539
|
var MAX_LOG_ROWS = 200;
|
|
540
|
+
var MAX_HISTORY = 100; /* completed calls kept per service for the inspector */
|
|
289
541
|
|
|
290
542
|
var state = { nodes: [], edges: [], services: {} };
|
|
543
|
+
var selectedService = null; /* service whose table row is expanded */
|
|
544
|
+
var inspected = null; /* service shown in the inspector side panel */
|
|
545
|
+
var callHistory = {}; /* service name -> completed calls, newest first */
|
|
291
546
|
var nodeEls = {}; /* service name -> { group, statusText, glyph } */
|
|
292
547
|
var edgeEls = {}; /* "from\\u0000to" -> path element */
|
|
293
548
|
var flashTimers = {};
|
|
@@ -308,9 +563,15 @@ const HTML = `<!doctype html>
|
|
|
308
563
|
|
|
309
564
|
/* ---------- graph layout: rank = longest path to a leaf ---------- */
|
|
310
565
|
|
|
311
|
-
|
|
566
|
+
/** Only services whose initialization has at least started are drawn. */
|
|
567
|
+
function isShown(name) {
|
|
568
|
+
var stats = state.services[name];
|
|
569
|
+
return !!stats && stats.status !== 'pending';
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function computeRanks(nodes) {
|
|
312
573
|
var deps = {};
|
|
313
|
-
|
|
574
|
+
nodes.forEach(function (n) { deps[n.name] = []; });
|
|
314
575
|
state.edges.forEach(function (e) {
|
|
315
576
|
if (deps[e.from] && e.to in deps) deps[e.from].push(e.to);
|
|
316
577
|
});
|
|
@@ -325,7 +586,7 @@ const HTML = `<!doctype html>
|
|
|
325
586
|
ranks[name] = r;
|
|
326
587
|
return r;
|
|
327
588
|
}
|
|
328
|
-
|
|
589
|
+
nodes.forEach(function (n) { rankOf(n.name); });
|
|
329
590
|
return ranks;
|
|
330
591
|
}
|
|
331
592
|
|
|
@@ -334,18 +595,21 @@ const HTML = `<!doctype html>
|
|
|
334
595
|
host.textContent = '';
|
|
335
596
|
nodeEls = {};
|
|
336
597
|
edgeEls = {};
|
|
337
|
-
|
|
598
|
+
var shown = state.nodes.filter(function (n) { return isShown(n.name); });
|
|
599
|
+
if (!shown.length) {
|
|
338
600
|
var empty = document.createElement('div');
|
|
339
601
|
empty.id = 'graph-empty';
|
|
340
|
-
empty.textContent =
|
|
602
|
+
empty.textContent = state.nodes.length
|
|
603
|
+
? 'No services initialized yet \\u2014 nodes appear when initialization starts.'
|
|
604
|
+
: 'No services yet \\u2014 waiting for an application to connect.';
|
|
341
605
|
host.appendChild(empty);
|
|
342
606
|
return;
|
|
343
607
|
}
|
|
344
608
|
|
|
345
|
-
var ranks = computeRanks();
|
|
609
|
+
var ranks = computeRanks(shown);
|
|
346
610
|
var columns = {};
|
|
347
611
|
var maxRank = 0;
|
|
348
|
-
|
|
612
|
+
shown.forEach(function (n) {
|
|
349
613
|
var r = ranks[n.name];
|
|
350
614
|
maxRank = Math.max(maxRank, r);
|
|
351
615
|
(columns[r] = columns[r] || []).push(n.name);
|
|
@@ -406,7 +670,7 @@ const HTML = `<!doctype html>
|
|
|
406
670
|
edgeEls[e.from + '\\u0000' + e.to] = path;
|
|
407
671
|
});
|
|
408
672
|
|
|
409
|
-
|
|
673
|
+
shown.forEach(function (n) {
|
|
410
674
|
var p = pos[n.name];
|
|
411
675
|
var g = svgEl('g');
|
|
412
676
|
g.setAttribute('transform', 'translate(' + p.x + ' ' + p.y + ')');
|
|
@@ -414,7 +678,7 @@ const HTML = `<!doctype html>
|
|
|
414
678
|
var rect = svgEl('rect');
|
|
415
679
|
rect.setAttribute('width', NODE_W);
|
|
416
680
|
rect.setAttribute('height', NODE_H);
|
|
417
|
-
rect.setAttribute('rx',
|
|
681
|
+
rect.setAttribute('rx', 8);
|
|
418
682
|
g.appendChild(rect);
|
|
419
683
|
|
|
420
684
|
var label = svgEl('text');
|
|
@@ -435,8 +699,18 @@ const HTML = `<!doctype html>
|
|
|
435
699
|
status.setAttribute('y', 42);
|
|
436
700
|
g.appendChild(status);
|
|
437
701
|
|
|
702
|
+
g.setAttribute('tabindex', '0');
|
|
703
|
+
g.setAttribute('role', 'button');
|
|
704
|
+
g.setAttribute('aria-label', 'Inspect ' + n.name);
|
|
438
705
|
g.addEventListener('mousemove', function (ev) { showTooltip(n.name, ev); });
|
|
439
706
|
g.addEventListener('mouseleave', hideTooltip);
|
|
707
|
+
g.addEventListener('click', function () { inspect(n.name); });
|
|
708
|
+
g.addEventListener('keydown', function (ev) {
|
|
709
|
+
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
710
|
+
ev.preventDefault();
|
|
711
|
+
inspect(n.name);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
440
714
|
|
|
441
715
|
svg.appendChild(g);
|
|
442
716
|
nodeEls[n.name] = { group: g, statusText: status, glyph: glyph };
|
|
@@ -451,7 +725,9 @@ const HTML = `<!doctype html>
|
|
|
451
725
|
var stats = state.services[name];
|
|
452
726
|
if (!el || !stats) return;
|
|
453
727
|
var flashing = el.group.classList.contains('flash');
|
|
454
|
-
el.group.setAttribute('class', 'node ' + stats.status
|
|
728
|
+
el.group.setAttribute('class', 'node ' + stats.status
|
|
729
|
+
+ (flashing ? ' flash' : '')
|
|
730
|
+
+ (name === inspected ? ' selected' : ''));
|
|
455
731
|
el.glyph.textContent = GLYPHS[stats.status] || '';
|
|
456
732
|
var line = stats.status;
|
|
457
733
|
if (stats.calls > 0) {
|
|
@@ -531,24 +807,43 @@ const HTML = `<!doctype html>
|
|
|
531
807
|
$('tile-calls').textContent = String(calls);
|
|
532
808
|
$('tile-avg').textContent = calls > 0 ? fmtMs(callMs / calls) + ' ms' : '–';
|
|
533
809
|
$('tile-errors').textContent = String(errors);
|
|
810
|
+
$('tile-errors').className = 'value' + (errors > 0 ? ' bad' : '');
|
|
534
811
|
|
|
535
812
|
var tbody = $('service-rows');
|
|
536
813
|
tbody.textContent = '';
|
|
537
814
|
names.sort().forEach(function (n) {
|
|
538
815
|
var s = state.services[n];
|
|
816
|
+
var selected = n === selectedService;
|
|
539
817
|
var tr = document.createElement('tr');
|
|
818
|
+
tr.className = 'svc-row' + (selected ? ' selected' : '');
|
|
819
|
+
tr.tabIndex = 0;
|
|
820
|
+
tr.setAttribute('role', 'button');
|
|
821
|
+
tr.setAttribute('aria-expanded', selected ? 'true' : 'false');
|
|
822
|
+
tr.addEventListener('click', function () { toggleService(n); });
|
|
823
|
+
tr.addEventListener('keydown', function (ev) {
|
|
824
|
+
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
825
|
+
ev.preventDefault();
|
|
826
|
+
toggleService(n);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
540
829
|
function cell(text, cls) {
|
|
541
830
|
var td = document.createElement('td');
|
|
542
831
|
if (cls) td.className = cls;
|
|
543
832
|
td.textContent = text;
|
|
544
833
|
return td;
|
|
545
834
|
}
|
|
546
|
-
|
|
835
|
+
var nameCell = cell('');
|
|
836
|
+
var caret = document.createElement('span');
|
|
837
|
+
caret.className = 'caret';
|
|
838
|
+
caret.textContent = selected ? '\\u25BE' : '\\u25B8'; /* ▾ / ▸ */
|
|
839
|
+
nameCell.appendChild(caret);
|
|
840
|
+
nameCell.appendChild(document.createTextNode(n));
|
|
841
|
+
tr.appendChild(nameCell);
|
|
547
842
|
var status = cell('');
|
|
548
843
|
var glyph = document.createElement('span');
|
|
549
844
|
glyph.className = 'glyph';
|
|
550
845
|
glyph.textContent = GLYPHS[s.status] + ' ';
|
|
551
|
-
var color = { ready: 'var(--good)', initializing: 'var(--
|
|
846
|
+
var color = { ready: 'var(--good)', initializing: 'var(--busy)', error: 'var(--critical)' }[s.status];
|
|
552
847
|
glyph.style.color = color || 'var(--ink-muted)';
|
|
553
848
|
status.appendChild(glyph);
|
|
554
849
|
status.appendChild(document.createTextNode(s.status));
|
|
@@ -556,11 +851,222 @@ const HTML = `<!doctype html>
|
|
|
556
851
|
tr.appendChild(cell(fmtMs(s.initMs), 'num'));
|
|
557
852
|
tr.appendChild(cell(String(s.calls), 'num'));
|
|
558
853
|
tr.appendChild(cell(s.calls > 0 ? fmtMs(s.totalCallMs / s.calls) : '–', 'num'));
|
|
559
|
-
tr.appendChild(cell(String(s.errors), 'num'));
|
|
854
|
+
tr.appendChild(cell(String(s.errors), 'num' + (s.errors > 0 ? ' err' : '')));
|
|
560
855
|
tbody.appendChild(tr);
|
|
856
|
+
if (selected) tbody.appendChild(methodDetailRow(s));
|
|
561
857
|
});
|
|
562
858
|
}
|
|
563
859
|
|
|
860
|
+
/* ---------- per-method drill-down ---------- */
|
|
861
|
+
|
|
862
|
+
function toggleService(name) {
|
|
863
|
+
selectedService = selectedService === name ? null : name;
|
|
864
|
+
renderSummary();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/** A per-method stats table for a service, or null when it has none. */
|
|
868
|
+
function methodsTable(methods) {
|
|
869
|
+
var names = Object.keys(methods);
|
|
870
|
+
if (!names.length) return null;
|
|
871
|
+
names.sort(function (a, b) {
|
|
872
|
+
return methods[b].calls - methods[a].calls || a.localeCompare(b);
|
|
873
|
+
});
|
|
874
|
+
var table = document.createElement('table');
|
|
875
|
+
var thead = document.createElement('thead');
|
|
876
|
+
var headRow = document.createElement('tr');
|
|
877
|
+
[['Method'], ['Calls', 'num'], ['Avg ms', 'num'], ['Last ms', 'num'], ['Errors', 'num']]
|
|
878
|
+
.forEach(function (h) {
|
|
879
|
+
var th = document.createElement('th');
|
|
880
|
+
if (h[1]) th.className = h[1];
|
|
881
|
+
th.textContent = h[0];
|
|
882
|
+
headRow.appendChild(th);
|
|
883
|
+
});
|
|
884
|
+
thead.appendChild(headRow);
|
|
885
|
+
table.appendChild(thead);
|
|
886
|
+
|
|
887
|
+
var body = document.createElement('tbody');
|
|
888
|
+
names.forEach(function (m) {
|
|
889
|
+
var stat = methods[m];
|
|
890
|
+
var row = document.createElement('tr');
|
|
891
|
+
function cell(text, cls) {
|
|
892
|
+
var c = document.createElement('td');
|
|
893
|
+
if (cls) c.className = cls;
|
|
894
|
+
c.textContent = text;
|
|
895
|
+
return c;
|
|
896
|
+
}
|
|
897
|
+
row.appendChild(cell(m));
|
|
898
|
+
row.appendChild(cell(String(stat.calls), 'num'));
|
|
899
|
+
row.appendChild(cell(fmtMs(stat.totalMs / stat.calls), 'num'));
|
|
900
|
+
row.appendChild(cell(fmtMs(stat.lastMs), 'num'));
|
|
901
|
+
row.appendChild(cell(String(stat.errors), 'num' + (stat.errors > 0 ? ' err' : '')));
|
|
902
|
+
body.appendChild(row);
|
|
903
|
+
});
|
|
904
|
+
table.appendChild(body);
|
|
905
|
+
return table;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** The expanded row under a selected service: its method-call breakdown. */
|
|
909
|
+
function methodDetailRow(stats) {
|
|
910
|
+
var tr = document.createElement('tr');
|
|
911
|
+
tr.className = 'method-detail';
|
|
912
|
+
var td = document.createElement('td');
|
|
913
|
+
td.colSpan = 6;
|
|
914
|
+
var table = methodsTable(stats.methods || {});
|
|
915
|
+
if (table) {
|
|
916
|
+
var wrap = document.createElement('div');
|
|
917
|
+
wrap.className = 'methods';
|
|
918
|
+
wrap.appendChild(table);
|
|
919
|
+
td.appendChild(wrap);
|
|
920
|
+
} else {
|
|
921
|
+
var empty = document.createElement('div');
|
|
922
|
+
empty.className = 'empty';
|
|
923
|
+
empty.textContent = 'No method calls yet.';
|
|
924
|
+
td.appendChild(empty);
|
|
925
|
+
}
|
|
926
|
+
tr.appendChild(td);
|
|
927
|
+
return tr;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/* ---------- inspector side panel ---------- */
|
|
931
|
+
|
|
932
|
+
/** Opens the panel for a service; same service (or null) closes it. */
|
|
933
|
+
function inspect(name) {
|
|
934
|
+
inspected = (name === null || inspected === name) ? null : name;
|
|
935
|
+
renderInspector();
|
|
936
|
+
Object.keys(nodeEls).forEach(function (n) { applyNodeState(n); });
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function renderInspector() {
|
|
940
|
+
var panel = $('inspector');
|
|
941
|
+
var stats = inspected ? state.services[inspected] : null;
|
|
942
|
+
if (!stats) {
|
|
943
|
+
inspected = null;
|
|
944
|
+
panel.classList.remove('open');
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
panel.classList.add('open');
|
|
948
|
+
$('insp-name').textContent = inspected;
|
|
949
|
+
|
|
950
|
+
var status = $('insp-status');
|
|
951
|
+
status.textContent = '';
|
|
952
|
+
var glyph = document.createElement('span');
|
|
953
|
+
glyph.className = 'glyph';
|
|
954
|
+
glyph.textContent = GLYPHS[stats.status] + ' ';
|
|
955
|
+
var color = { ready: 'var(--good)', initializing: 'var(--busy)', error: 'var(--critical)' }[stats.status];
|
|
956
|
+
glyph.style.color = color || 'var(--ink-muted)';
|
|
957
|
+
status.appendChild(glyph);
|
|
958
|
+
status.appendChild(document.createTextNode(stats.status));
|
|
959
|
+
|
|
960
|
+
var summary = $('insp-summary');
|
|
961
|
+
summary.textContent = '';
|
|
962
|
+
var avg = stats.calls > 0 ? stats.totalCallMs / stats.calls : null;
|
|
963
|
+
[
|
|
964
|
+
['Init', stats.initMs == null ? '–' : fmtMs(stats.initMs) + ' ms'],
|
|
965
|
+
['Calls', String(stats.calls)],
|
|
966
|
+
['Avg call', avg == null ? '–' : fmtMs(avg) + ' ms'],
|
|
967
|
+
['Errors', String(stats.errors)]
|
|
968
|
+
].forEach(function (pair) {
|
|
969
|
+
var row = document.createElement('div');
|
|
970
|
+
row.className = 'i-row';
|
|
971
|
+
var k = document.createElement('span');
|
|
972
|
+
k.textContent = pair[0];
|
|
973
|
+
var v = document.createElement('b');
|
|
974
|
+
v.textContent = pair[1];
|
|
975
|
+
row.appendChild(k);
|
|
976
|
+
row.appendChild(v);
|
|
977
|
+
summary.appendChild(row);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
var methodsHost = $('insp-methods');
|
|
981
|
+
methodsHost.textContent = '';
|
|
982
|
+
var table = methodsTable(stats.methods || {});
|
|
983
|
+
if (table) methodsHost.appendChild(table);
|
|
984
|
+
else methodsHost.appendChild(emptyNote('No method calls yet.'));
|
|
985
|
+
|
|
986
|
+
var historyHost = $('insp-history');
|
|
987
|
+
historyHost.textContent = '';
|
|
988
|
+
var entries = callHistory[inspected] || [];
|
|
989
|
+
if (!entries.length) {
|
|
990
|
+
historyHost.appendChild(emptyNote('No calls recorded yet.'));
|
|
991
|
+
} else {
|
|
992
|
+
entries.forEach(function (entry) {
|
|
993
|
+
historyHost.appendChild(historyRow(entry));
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function emptyNote(text) {
|
|
999
|
+
var div = document.createElement('div');
|
|
1000
|
+
div.className = 'empty-note';
|
|
1001
|
+
div.textContent = text;
|
|
1002
|
+
return div;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function historyRow(entry) {
|
|
1006
|
+
var row = document.createElement('div');
|
|
1007
|
+
row.className = 'row' + (entry.error ? ' error' : '');
|
|
1008
|
+
|
|
1009
|
+
var time = document.createElement('span');
|
|
1010
|
+
time.className = 'time';
|
|
1011
|
+
time.textContent = fmtTime(entry.time);
|
|
1012
|
+
row.appendChild(time);
|
|
1013
|
+
|
|
1014
|
+
var name = document.createElement('span');
|
|
1015
|
+
name.className = 'name';
|
|
1016
|
+
var text = entry.method;
|
|
1017
|
+
if (entry.parentService && entry.parentService !== inspected) {
|
|
1018
|
+
text = entry.parentService + ' \\u2192 ' + text;
|
|
1019
|
+
}
|
|
1020
|
+
name.textContent = text;
|
|
1021
|
+
/* "[]" (no arguments) and "undefined" (void return) carry no signal. */
|
|
1022
|
+
if (entry.args != null && entry.args !== '[]') {
|
|
1023
|
+
name.appendChild(detailLine('args', entry.args));
|
|
1024
|
+
}
|
|
1025
|
+
if (entry.result != null && entry.result !== 'undefined') {
|
|
1026
|
+
name.appendChild(detailLine('\\u2192', entry.result));
|
|
1027
|
+
}
|
|
1028
|
+
if (entry.error) {
|
|
1029
|
+
var err = document.createElement('div');
|
|
1030
|
+
err.className = 'err-msg';
|
|
1031
|
+
err.textContent = '\\u2715 ' + entry.error;
|
|
1032
|
+
name.appendChild(err);
|
|
1033
|
+
}
|
|
1034
|
+
row.appendChild(name);
|
|
1035
|
+
|
|
1036
|
+
var dur = document.createElement('span');
|
|
1037
|
+
dur.className = 'dur';
|
|
1038
|
+
dur.textContent = fmtMs(entry.durationMs) + ' ms';
|
|
1039
|
+
row.appendChild(dur);
|
|
1040
|
+
return row;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function detailLine(label, text) {
|
|
1044
|
+
var div = document.createElement('div');
|
|
1045
|
+
div.className = 'detail';
|
|
1046
|
+
var k = document.createElement('span');
|
|
1047
|
+
k.className = 'k';
|
|
1048
|
+
k.textContent = label + ' ';
|
|
1049
|
+
div.appendChild(k);
|
|
1050
|
+
div.appendChild(document.createTextNode(text));
|
|
1051
|
+
return div;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** Keeps the per-service history the inspector shows, newest first. */
|
|
1055
|
+
function recordCall(wire) {
|
|
1056
|
+
if (wire.kind !== 'call' || wire.span.type !== 'end' || !wire.service) return;
|
|
1057
|
+
var list = callHistory[wire.service] = callHistory[wire.service] || [];
|
|
1058
|
+
list.unshift({
|
|
1059
|
+
method: wire.method,
|
|
1060
|
+
parentService: wire.parentService,
|
|
1061
|
+
time: wire.span.time,
|
|
1062
|
+
durationMs: wire.span.durationMs,
|
|
1063
|
+
error: wire.span.error,
|
|
1064
|
+
args: wire.args,
|
|
1065
|
+
result: wire.span.result != null ? wire.span.result : null
|
|
1066
|
+
});
|
|
1067
|
+
if (list.length > MAX_HISTORY) list.length = MAX_HISTORY;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
564
1070
|
/* ---------- event log ---------- */
|
|
565
1071
|
|
|
566
1072
|
function logEvent(wire) {
|
|
@@ -608,10 +1114,13 @@ const HTML = `<!doctype html>
|
|
|
608
1114
|
|
|
609
1115
|
function applyEvent(wire) {
|
|
610
1116
|
if (wire.service && wire.stats) {
|
|
611
|
-
var
|
|
1117
|
+
var wasDrawn = wire.service in nodeEls;
|
|
612
1118
|
state.services[wire.service] = wire.stats;
|
|
613
|
-
if (
|
|
614
|
-
|
|
1119
|
+
if (isShown(wire.service) !== wasDrawn) {
|
|
1120
|
+
renderGraph(); /* the node just became visible (or vanished) */
|
|
1121
|
+
} else {
|
|
1122
|
+
applyNodeState(wire.service);
|
|
1123
|
+
}
|
|
615
1124
|
}
|
|
616
1125
|
if (wire.kind === 'call' && wire.service) {
|
|
617
1126
|
var el = nodeEls[wire.service];
|
|
@@ -620,8 +1129,10 @@ const HTML = `<!doctype html>
|
|
|
620
1129
|
flash(edgeEls[wire.parentService + '\\u0000' + wire.service], 'e:' + wire.parentService + '>' + wire.service);
|
|
621
1130
|
}
|
|
622
1131
|
}
|
|
1132
|
+
recordCall(wire);
|
|
623
1133
|
logEvent(wire);
|
|
624
1134
|
renderSummary();
|
|
1135
|
+
if (inspected && wire.service === inspected) renderInspector();
|
|
625
1136
|
}
|
|
626
1137
|
|
|
627
1138
|
function connect() {
|
|
@@ -641,8 +1152,12 @@ const HTML = `<!doctype html>
|
|
|
641
1152
|
state.nodes = snap.nodes;
|
|
642
1153
|
state.edges = snap.edges;
|
|
643
1154
|
state.services = snap.services;
|
|
1155
|
+
callHistory = {};
|
|
1156
|
+
/* recent is oldest-first; recordCall unshifts, so newest ends up first */
|
|
1157
|
+
snap.recent.forEach(recordCall);
|
|
644
1158
|
renderGraph();
|
|
645
1159
|
renderSummary();
|
|
1160
|
+
renderInspector(); /* refreshes, or closes if the service is gone */
|
|
646
1161
|
$('log').textContent = '';
|
|
647
1162
|
snap.recent.forEach(logEvent);
|
|
648
1163
|
});
|
|
@@ -651,9 +1166,14 @@ const HTML = `<!doctype html>
|
|
|
651
1166
|
});
|
|
652
1167
|
}
|
|
653
1168
|
|
|
1169
|
+
$('insp-close').addEventListener('click', function () { inspect(null); });
|
|
1170
|
+
document.addEventListener('keydown', function (ev) {
|
|
1171
|
+
if (ev.key === 'Escape') inspect(null);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
654
1174
|
connect();
|
|
655
1175
|
})();
|
|
656
1176
|
</script>
|
|
657
1177
|
</body>
|
|
658
1178
|
</html>
|
|
659
|
-
|
|
1179
|
+
`
|