shokupan 0.10.4 → 0.11.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/dist/{analyzer-CKLGLFtx.cjs → analyzer-BAhvpNY_.cjs} +2 -7
- package/dist/{analyzer-CKLGLFtx.cjs.map → analyzer-BAhvpNY_.cjs.map} +1 -1
- package/dist/{analyzer-BqIe1p0R.js → analyzer-CnKnQ5KV.js} +3 -8
- package/dist/{analyzer-BqIe1p0R.js.map → analyzer-CnKnQ5KV.js.map} +1 -1
- package/dist/{analyzer.impl-D9Yi1Hax.cjs → analyzer.impl-CfpMu4-g.cjs} +586 -40
- package/dist/analyzer.impl-CfpMu4-g.cjs.map +1 -0
- package/dist/{analyzer.impl-CV6W1Eq7.js → analyzer.impl-DCiqlXI5.js} +586 -40
- package/dist/analyzer.impl-DCiqlXI5.js.map +1 -0
- package/dist/cli.cjs +206 -18
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +206 -18
- package/dist/cli.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/index.cjs +2405 -1008
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2402 -1006
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +423 -30
- package/dist/plugins/application/api-explorer/static/style.css +351 -10
- package/dist/plugins/application/api-explorer/static/theme.css +7 -2
- package/dist/plugins/application/asyncapi/generator.d.ts +4 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +154 -22
- package/dist/plugins/application/asyncapi/static/style.css +24 -8
- package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +107 -0
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +38 -2
- package/dist/plugins/application/dashboard/plugin.d.ts +44 -1
- package/dist/plugins/application/dashboard/static/charts.js +127 -62
- package/dist/plugins/application/dashboard/static/client.js +160 -0
- package/dist/plugins/application/dashboard/static/graph.mjs +167 -56
- package/dist/plugins/application/dashboard/static/reactflow.css +20 -10
- package/dist/plugins/application/dashboard/static/registry.js +112 -8
- package/dist/plugins/application/dashboard/static/requests.js +868 -58
- package/dist/plugins/application/dashboard/static/styles.css +186 -14
- package/dist/plugins/application/dashboard/static/tabs.js +44 -9
- package/dist/plugins/application/dashboard/static/theme.css +7 -2
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +61 -1
- package/dist/plugins/application/openapi/openapi.d.ts +3 -0
- package/dist/plugins/application/shared/ast-utils.d.ts +7 -0
- package/dist/router.d.ts +55 -16
- package/dist/shokupan.d.ts +7 -2
- package/dist/util/adapter/adapters.d.ts +19 -0
- package/dist/util/adapter/filesystem.d.ts +20 -0
- package/dist/util/controller-scanner.d.ts +4 -0
- package/dist/util/cpu-monitor.d.ts +2 -0
- package/dist/util/middleware-tracker.d.ts +10 -0
- package/dist/util/types.d.ts +37 -0
- package/package.json +5 -5
- package/dist/analyzer.impl-CV6W1Eq7.js.map +0 -1
- package/dist/analyzer.impl-D9Yi1Hax.cjs.map +0 -1
- package/dist/http-server-BEMPIs33.cjs +0 -85
- package/dist/http-server-BEMPIs33.cjs.map +0 -1
- package/dist/http-server-CCeagTyU.js +0 -68
- package/dist/http-server-CCeagTyU.js.map +0 -1
- package/dist/plugins/application/dashboard/static/poll.js +0 -146
|
@@ -10,9 +10,36 @@ const state = {
|
|
|
10
10
|
logEntries: [],
|
|
11
11
|
logAutoScroll: true,
|
|
12
12
|
isConsoleMaximized: false,
|
|
13
|
-
disableSourceView: !!window.DISABLE_SOURCE_VIEW
|
|
13
|
+
disableSourceView: !!window.DISABLE_SOURCE_VIEW,
|
|
14
|
+
// Layout State names
|
|
15
|
+
STORAGE_KEYS: {
|
|
16
|
+
SIDEBAR_WIDTH: 'asyncapi_sidebar_width',
|
|
17
|
+
CONSOLE_WIDTH: 'asyncapi_console_width',
|
|
18
|
+
NAV_COLLAPSED: 'asyncapi_nav_collapsed',
|
|
19
|
+
CONSOLE_COLLAPSED: 'asyncapi_console_collapsed',
|
|
20
|
+
CONSOLE_MAXIMIZED: 'asyncapi_console_maximized'
|
|
21
|
+
}
|
|
14
22
|
};
|
|
15
23
|
|
|
24
|
+
const STORAGE_PREFIX = 'shokupan:asyncapi:';
|
|
25
|
+
|
|
26
|
+
function saveState(key, value) {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn('Failed to save state', e);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getState(key, defaultValue) {
|
|
35
|
+
try {
|
|
36
|
+
const item = localStorage.getItem(STORAGE_PREFIX + key);
|
|
37
|
+
return item ? JSON.parse(item) : defaultValue;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return defaultValue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
16
43
|
const els = {
|
|
17
44
|
url: document.getElementById('url'),
|
|
18
45
|
protocol: document.getElementById('protocol'),
|
|
@@ -41,6 +68,7 @@ const els = {
|
|
|
41
68
|
btnMaximizeConsole: document.getElementById('btn-maximize-console')
|
|
42
69
|
};
|
|
43
70
|
|
|
71
|
+
// Resizers
|
|
44
72
|
// Resizers
|
|
45
73
|
function initResizers() {
|
|
46
74
|
const setup = (id, varName, isLeft) => {
|
|
@@ -64,6 +92,10 @@ function initResizers() {
|
|
|
64
92
|
document.removeEventListener('mouseup', onUp);
|
|
65
93
|
document.body.style.cursor = '';
|
|
66
94
|
el.classList.remove('resizing');
|
|
95
|
+
|
|
96
|
+
// Save new width
|
|
97
|
+
const finalW = parseInt(getComputedStyle(root).getPropertyValue(varName), 10);
|
|
98
|
+
saveState(varName === '--sidebar-width' ? state.STORAGE_KEYS.SIDEBAR_WIDTH : state.STORAGE_KEYS.CONSOLE_WIDTH, finalW);
|
|
67
99
|
};
|
|
68
100
|
document.addEventListener('mousemove', onMove);
|
|
69
101
|
document.addEventListener('mouseup', onUp);
|
|
@@ -73,8 +105,10 @@ function initResizers() {
|
|
|
73
105
|
setup('resizer-right', '--console-width', false);
|
|
74
106
|
}
|
|
75
107
|
|
|
76
|
-
function toggleConsoleMaximize() {
|
|
77
|
-
state.isConsoleMaximized = !state.isConsoleMaximized;
|
|
108
|
+
function toggleConsoleMaximize(save = true) {
|
|
109
|
+
if (save) state.isConsoleMaximized = !state.isConsoleMaximized;
|
|
110
|
+
// implied else: state already toggled if called from restore
|
|
111
|
+
|
|
78
112
|
const btn = els.btnMaximizeConsole;
|
|
79
113
|
|
|
80
114
|
if (state.isConsoleMaximized) {
|
|
@@ -89,10 +123,6 @@ function toggleConsoleMaximize() {
|
|
|
89
123
|
btn.title = "Restore Console";
|
|
90
124
|
} else {
|
|
91
125
|
// Restore
|
|
92
|
-
els.mainWrapper.style.display = 'block'; // Or flex/whatever logic
|
|
93
|
-
// Actually main-wrapper was display:flex via style attribute in HTML,
|
|
94
|
-
// but let's check if we hid it. display='none' hides it.
|
|
95
|
-
// We can just set it empty to revert to stylesheet or inline default.
|
|
96
126
|
els.mainWrapper.style.display = '';
|
|
97
127
|
els.resizerRight.style.display = 'block';
|
|
98
128
|
els.consolePanel.style.flex = ''; // Revert to CSS default
|
|
@@ -102,6 +132,49 @@ function toggleConsoleMaximize() {
|
|
|
102
132
|
btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>`;
|
|
103
133
|
btn.title = "Maximize Console";
|
|
104
134
|
}
|
|
135
|
+
|
|
136
|
+
if (save) {
|
|
137
|
+
saveState(state.STORAGE_KEYS.CONSOLE_MAXIMIZED, state.isConsoleMaximized);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function restoreLayout() {
|
|
142
|
+
const root = document.documentElement;
|
|
143
|
+
|
|
144
|
+
// Widths
|
|
145
|
+
const sidebarW = getState(state.STORAGE_KEYS.SIDEBAR_WIDTH, null);
|
|
146
|
+
if (sidebarW) root.style.setProperty('--sidebar-width', sidebarW + 'px');
|
|
147
|
+
|
|
148
|
+
const consoleW = getState(state.STORAGE_KEYS.CONSOLE_WIDTH, null);
|
|
149
|
+
if (consoleW) root.style.setProperty('--console-width', consoleW + 'px');
|
|
150
|
+
|
|
151
|
+
// Nav State
|
|
152
|
+
const navCollapsed = getState(state.STORAGE_KEYS.NAV_COLLAPSED, false);
|
|
153
|
+
if (navCollapsed && els.btnCollapseNav) {
|
|
154
|
+
els.sidebar.style.display = 'none';
|
|
155
|
+
els.resizerLeft.style.display = 'none';
|
|
156
|
+
els.btnExpandNav.style.display = 'flex';
|
|
157
|
+
// Ensure collapse button is hidden? Stylesheet handles it via display:none on expand usually?
|
|
158
|
+
// Based on original logic:
|
|
159
|
+
// collapse click -> sidebar none, resizer none, expand flex.
|
|
160
|
+
// expand click -> sidebar flex, resizer block, expand none.
|
|
161
|
+
// We assume collapse btn is always visible when sidebar is visible.
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Console State
|
|
165
|
+
const consoleCollapsed = getState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, false);
|
|
166
|
+
if (consoleCollapsed && els.btnCollapseConsole) {
|
|
167
|
+
els.consolePanel.style.display = 'none';
|
|
168
|
+
els.resizerRight.style.display = 'none';
|
|
169
|
+
els.btnExpandConsole.style.display = 'flex';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Maximize State
|
|
173
|
+
const consoleMaximized = getState(state.STORAGE_KEYS.CONSOLE_MAXIMIZED, false);
|
|
174
|
+
if (consoleMaximized && els.btnMaximizeConsole && !consoleCollapsed) { // Don't maximize if collapsed?
|
|
175
|
+
state.isConsoleMaximized = true;
|
|
176
|
+
toggleConsoleMaximize(false);
|
|
177
|
+
}
|
|
105
178
|
}
|
|
106
179
|
|
|
107
180
|
// Hydrate Navigation
|
|
@@ -165,15 +238,18 @@ require(['vs/editor/editor.main'], function () {
|
|
|
165
238
|
// Auto-connect if URL is present (or wait for user?)
|
|
166
239
|
// Original script called connect() immediately.
|
|
167
240
|
// Toggles
|
|
241
|
+
|
|
168
242
|
if (els.btnCollapseNav) els.btnCollapseNav.onclick = () => {
|
|
169
243
|
els.sidebar.style.display = 'none';
|
|
170
244
|
els.resizerLeft.style.display = 'none';
|
|
171
245
|
els.btnExpandNav.style.display = 'flex';
|
|
246
|
+
saveState(state.STORAGE_KEYS.NAV_COLLAPSED, true);
|
|
172
247
|
};
|
|
173
248
|
if (els.btnExpandNav) els.btnExpandNav.onclick = () => {
|
|
174
249
|
els.sidebar.style.display = 'flex';
|
|
175
250
|
els.resizerLeft.style.display = 'block';
|
|
176
251
|
els.btnExpandNav.style.display = 'none';
|
|
252
|
+
saveState(state.STORAGE_KEYS.NAV_COLLAPSED, false);
|
|
177
253
|
};
|
|
178
254
|
|
|
179
255
|
if (els.btnCollapseConsole) els.btnCollapseConsole.onclick = () => {
|
|
@@ -183,17 +259,21 @@ require(['vs/editor/editor.main'], function () {
|
|
|
183
259
|
els.consolePanel.style.display = 'none';
|
|
184
260
|
els.resizerRight.style.display = 'none';
|
|
185
261
|
els.btnExpandConsole.style.display = 'flex';
|
|
262
|
+
saveState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, true);
|
|
186
263
|
};
|
|
187
264
|
if (els.btnExpandConsole) els.btnExpandConsole.onclick = () => {
|
|
188
265
|
els.consolePanel.style.display = 'flex';
|
|
189
266
|
els.resizerRight.style.display = 'block';
|
|
190
267
|
els.btnExpandConsole.style.display = 'none';
|
|
268
|
+
saveState(state.STORAGE_KEYS.CONSOLE_COLLAPSED, false);
|
|
191
269
|
|
|
192
|
-
// Reset maximize state if it was maximized
|
|
270
|
+
// Reset maximize state if it was maximized (handled by collapse logic mostly, but good to be safe)
|
|
193
271
|
if (state.isConsoleMaximized) toggleConsoleMaximize();
|
|
194
272
|
};
|
|
195
273
|
|
|
196
|
-
if (els.btnMaximizeConsole) els.btnMaximizeConsole.onclick = toggleConsoleMaximize;
|
|
274
|
+
if (els.btnMaximizeConsole) els.btnMaximizeConsole.onclick = () => toggleConsoleMaximize(true);
|
|
275
|
+
|
|
276
|
+
restoreLayout();
|
|
197
277
|
|
|
198
278
|
connect();
|
|
199
279
|
});
|
|
@@ -240,8 +320,8 @@ async function selectEvent(name, el) {
|
|
|
240
320
|
if (!state.disableSourceView && sourceInfos.length > 0) {
|
|
241
321
|
sourceLinksHtml = sourceInfos.map(s => {
|
|
242
322
|
const filename = s.file ? s.file.split('/').pop() : 'unknown';
|
|
243
|
-
return `<a href="vscode://file/${s.file}:${s.line}" style="color: #fbbf24; text-decoration:
|
|
244
|
-
|
|
323
|
+
return `<a href="vscode://file/${s.file}:${s.line}" style="color: #fbbf24; text-decoration: none; display: block;" class="code-link">
|
|
324
|
+
<code style="font-family: 'JetBrains Mono', monospace; background: rgba(251, 191, 36, 0.1); padding: 2px 4px; border-radius: 4px;">${filename}:${s.line}</code>
|
|
245
325
|
</a>`;
|
|
246
326
|
}).join('');
|
|
247
327
|
}
|
|
@@ -267,7 +347,7 @@ async function selectEvent(name, el) {
|
|
|
267
347
|
</div>
|
|
268
348
|
|
|
269
349
|
${!state.disableSourceView && sourceInfos.length > 0 ? `
|
|
270
|
-
<div class="section-title">Source
|
|
350
|
+
<div class="section-title">Source Code</div>
|
|
271
351
|
<div id="snippet-container"></div>
|
|
272
352
|
` : ''}
|
|
273
353
|
</div>
|
|
@@ -282,7 +362,7 @@ async function selectEvent(name, el) {
|
|
|
282
362
|
let code = null;
|
|
283
363
|
if (src.file) {
|
|
284
364
|
try {
|
|
285
|
-
const res = await fetch(
|
|
365
|
+
const res = await fetch(`${window.BASE_PATH}/_code?file=${encodeURIComponent(src.file)}`);
|
|
286
366
|
if (res.ok) code = await res.text();
|
|
287
367
|
else code = `// Failed to load source: ${res.statusText}`;
|
|
288
368
|
} catch (e) { code = `// Error loading source: ${e.message}`; }
|
|
@@ -290,9 +370,25 @@ async function selectEvent(name, el) {
|
|
|
290
370
|
|
|
291
371
|
if (code) {
|
|
292
372
|
const wrapper = document.createElement('div');
|
|
373
|
+
wrapper.id = `snippet-group-${i}`;
|
|
374
|
+
wrapper.classList.add('source-group');
|
|
293
375
|
wrapper.style.marginBottom = '16px';
|
|
294
|
-
|
|
295
|
-
|
|
376
|
+
|
|
377
|
+
wrapper.innerHTML = `
|
|
378
|
+
<div class="source-header-actions" style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
|
|
379
|
+
<a href="vscode://file/${src.file}:${src.line}" class="doc-source-link" title="${src.file}:${src.line}">
|
|
380
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
|
|
381
|
+
<polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
|
|
382
|
+
</svg>
|
|
383
|
+
${src.file.split('/').pop()}:${src.line}
|
|
384
|
+
</a>
|
|
385
|
+
<button class="btn-icon" title="Toggle Fullscreen" onclick="toggleFullscreen('snippet-group-${i}', 'snippet-editor-${i}')">
|
|
386
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
387
|
+
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"></path>
|
|
388
|
+
</svg>
|
|
389
|
+
</button>
|
|
390
|
+
</div>
|
|
391
|
+
<div id="snippet-editor-${i}" style="height: 400px; border: 1px solid #333; border-radius: 6px; overflow: hidden;"></div>`;
|
|
296
392
|
container.appendChild(wrapper);
|
|
297
393
|
|
|
298
394
|
monaco.editor.colorize(code, 'typescript', {}).then(() => {
|
|
@@ -300,9 +396,6 @@ async function selectEvent(name, el) {
|
|
|
300
396
|
if (!el) return;
|
|
301
397
|
|
|
302
398
|
const model = monaco.editor.createModel(code, "typescript");
|
|
303
|
-
|
|
304
|
-
el.style.height = '400px';
|
|
305
|
-
|
|
306
399
|
const editor = monaco.editor.create(el, {
|
|
307
400
|
model: model,
|
|
308
401
|
readOnly: true,
|
|
@@ -342,6 +435,7 @@ async function selectEvent(name, el) {
|
|
|
342
435
|
});
|
|
343
436
|
}
|
|
344
437
|
}
|
|
438
|
+
|
|
345
439
|
}
|
|
346
440
|
return;
|
|
347
441
|
}
|
|
@@ -360,7 +454,7 @@ async function selectEvent(name, el) {
|
|
|
360
454
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
|
|
361
455
|
<polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
|
|
362
456
|
</svg>
|
|
363
|
-
|
|
457
|
+
<code style="font-family: inherit;">${filename}:${s.line}</code>
|
|
364
458
|
</a>`;
|
|
365
459
|
} else {
|
|
366
460
|
sourceLinkHtml = `<div class="doc-source-link" title="Multiple sources">
|
|
@@ -387,7 +481,7 @@ async function selectEvent(name, el) {
|
|
|
387
481
|
${desc ? `<p style="line-height: 1.6; margin-bottom: 2rem;">${desc}</p>` : ''}
|
|
388
482
|
|
|
389
483
|
<div class="section-title">Payload Schema</div>
|
|
390
|
-
${payload ? renderSchemaToDOM(payload) : '<div class="empty-state-text" style="color:var(--text-muted); font-style:italic;">
|
|
484
|
+
${payload ? renderSchemaToDOM(payload) : '<div class="empty-state-text" style="color:var(--text-muted); font-style:italic;">Payload Unused</div>'}
|
|
391
485
|
|
|
392
486
|
${!state.disableSourceView && sourceInfos.length > 0 ? `
|
|
393
487
|
<div class="section-title" style="margin-top: 24px;">Source Code</div>
|
|
@@ -455,7 +549,7 @@ async function selectEvent(name, el) {
|
|
|
455
549
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right:6px">
|
|
456
550
|
<polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>
|
|
457
551
|
</svg>
|
|
458
|
-
|
|
552
|
+
<code style="font-family: inherit;">${fileName.split('/').pop()}:${sources[0].line}</code>
|
|
459
553
|
</a>
|
|
460
554
|
<button class="btn-icon" title="Toggle Fullscreen" onclick="toggleFullscreen('source-group-${i}', 'source-editor-${i}')">
|
|
461
555
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
@@ -469,7 +563,7 @@ async function selectEvent(name, el) {
|
|
|
469
563
|
(async () => {
|
|
470
564
|
let code = null;
|
|
471
565
|
try {
|
|
472
|
-
const res = await fetch(
|
|
566
|
+
const res = await fetch(`${window.BASE_PATH}/_code?file=${encodeURIComponent(fileName)}`);
|
|
473
567
|
if (res.ok) code = await res.text();
|
|
474
568
|
else code = `// Failed to load source: ${res.statusText}`;
|
|
475
569
|
} catch (e) { code = `// Error loading source: ${e.message}`; }
|
|
@@ -746,3 +840,41 @@ els.sendBtn.onclick = () => {
|
|
|
746
840
|
}
|
|
747
841
|
} catch (e) { log('System', 'Invalid JSON', 'error'); }
|
|
748
842
|
};
|
|
843
|
+
|
|
844
|
+
/* ================= Fullscreen Toggle ================= */
|
|
845
|
+
window.toggleFullscreen = function (containerId, editorId) {
|
|
846
|
+
const container = document.getElementById(containerId);
|
|
847
|
+
if (!container) return;
|
|
848
|
+
|
|
849
|
+
const isFullscreen = container.classList.toggle('fullscreen');
|
|
850
|
+
|
|
851
|
+
// Find the button to update icon
|
|
852
|
+
// The button is inside .source-header-actions
|
|
853
|
+
const btn = container.querySelector('button[title="Toggle Fullscreen"]') || container.querySelector('button[title="Exit Fullscreen"]');
|
|
854
|
+
|
|
855
|
+
if (btn) {
|
|
856
|
+
if (isFullscreen) {
|
|
857
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path></svg>`;
|
|
858
|
+
btn.title = "Exit Fullscreen";
|
|
859
|
+
} else {
|
|
860
|
+
btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"></path></svg>`;
|
|
861
|
+
btn.title = "Toggle Fullscreen";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ESC to exit
|
|
866
|
+
if (isFullscreen) {
|
|
867
|
+
const onEsc = (e) => {
|
|
868
|
+
if (e.key === 'Escape') {
|
|
869
|
+
window.toggleFullscreen(containerId, editorId);
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
container._escHandler = onEsc;
|
|
873
|
+
document.addEventListener('keydown', onEsc);
|
|
874
|
+
} else {
|
|
875
|
+
if (container._escHandler) {
|
|
876
|
+
document.removeEventListener('keydown', container._escHandler);
|
|
877
|
+
delete container._escHandler;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
};
|
|
@@ -151,7 +151,7 @@ a {
|
|
|
151
151
|
font-weight: 700;
|
|
152
152
|
padding: 2px 6px;
|
|
153
153
|
border-radius: 4px;
|
|
154
|
-
font-family:
|
|
154
|
+
font-family: var(--shokupan-font-mono);
|
|
155
155
|
text-transform: uppercase;
|
|
156
156
|
min-width: 38px;
|
|
157
157
|
text-align: center;
|
|
@@ -215,7 +215,7 @@ a {
|
|
|
215
215
|
|
|
216
216
|
/* Structured Schema View */
|
|
217
217
|
.schema-root {
|
|
218
|
-
font-family:
|
|
218
|
+
font-family: var(--shokupan-font-mono);
|
|
219
219
|
font-size: 0.9rem;
|
|
220
220
|
background: var(--bg-card);
|
|
221
221
|
border-radius: 8px;
|
|
@@ -290,7 +290,7 @@ a {
|
|
|
290
290
|
border: none;
|
|
291
291
|
color: var(--text-secondary);
|
|
292
292
|
flex: 1;
|
|
293
|
-
font-family:
|
|
293
|
+
font-family: var(--shokupan-font-mono);
|
|
294
294
|
font-size: 0.8rem;
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -362,13 +362,9 @@ a {
|
|
|
362
362
|
.logs-container {
|
|
363
363
|
flex: 1;
|
|
364
364
|
overflow-y: auto;
|
|
365
|
-
|
|
366
|
-
font-family: 'JetBrains Mono', monospace;
|
|
365
|
+
font-family: var(--shokupan-font-mono);
|
|
367
366
|
font-size: 0.8rem;
|
|
368
|
-
display: block;
|
|
369
|
-
/* flex-direction: column; gap: 0.5rem; -- removed for virtual scroll stability */
|
|
370
367
|
position: relative;
|
|
371
|
-
/* For absolute children */
|
|
372
368
|
}
|
|
373
369
|
|
|
374
370
|
.log-shim {
|
|
@@ -562,4 +558,24 @@ a {
|
|
|
562
558
|
|
|
563
559
|
.floating-toggle.right {
|
|
564
560
|
right: 10px;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/* Fullscreen Mode */
|
|
564
|
+
.source-group.fullscreen {
|
|
565
|
+
position: fixed;
|
|
566
|
+
top: 0;
|
|
567
|
+
left: 0;
|
|
568
|
+
width: 100vw;
|
|
569
|
+
height: 100vh;
|
|
570
|
+
z-index: 9999;
|
|
571
|
+
background: var(--bg-primary);
|
|
572
|
+
padding: 1rem;
|
|
573
|
+
border: none;
|
|
574
|
+
border-radius: 0;
|
|
575
|
+
display: flex !important;
|
|
576
|
+
/* Force display flex for fullscreen */
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.source-group.fullscreen .source-header-actions {
|
|
580
|
+
margin-bottom: 1rem;
|
|
565
581
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface representing the details of an intercepted outbound request.
|
|
3
|
+
*/
|
|
4
|
+
export interface OutboundRequestLog {
|
|
5
|
+
/**
|
|
6
|
+
* The HTTP method of the request (e.g., GET, POST).
|
|
7
|
+
*/
|
|
8
|
+
method: string;
|
|
9
|
+
/**
|
|
10
|
+
* The full URL of the request.
|
|
11
|
+
*/
|
|
12
|
+
url: string;
|
|
13
|
+
/**
|
|
14
|
+
* The request headers.
|
|
15
|
+
*/
|
|
16
|
+
requestHeaders: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP status code of the response.
|
|
19
|
+
*/
|
|
20
|
+
status: number;
|
|
21
|
+
/**
|
|
22
|
+
* The response headers.
|
|
23
|
+
*/
|
|
24
|
+
responseHeaders: Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* The duration of the request in milliseconds.
|
|
27
|
+
*/
|
|
28
|
+
duration: number;
|
|
29
|
+
/**
|
|
30
|
+
* The timestamp when the request started.
|
|
31
|
+
*/
|
|
32
|
+
startTime: number;
|
|
33
|
+
/**
|
|
34
|
+
* The request body (if any).
|
|
35
|
+
*/
|
|
36
|
+
requestBody?: any;
|
|
37
|
+
/**
|
|
38
|
+
* The response body (if any).
|
|
39
|
+
*/
|
|
40
|
+
responseBody?: any;
|
|
41
|
+
/**
|
|
42
|
+
* The hostname of the request.
|
|
43
|
+
*/
|
|
44
|
+
domain?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The pathname of the request.
|
|
47
|
+
*/
|
|
48
|
+
path?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The protocol scheme (http/https) or version (1.1, 2.0).
|
|
51
|
+
*/
|
|
52
|
+
protocol?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The protocol scheme (http/https).
|
|
55
|
+
*/
|
|
56
|
+
scheme?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The remote IP address (if available).
|
|
59
|
+
*/
|
|
60
|
+
remoteIP?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The number of cookies sent.
|
|
63
|
+
*/
|
|
64
|
+
cookies?: number;
|
|
65
|
+
/**
|
|
66
|
+
* The estimated transfer size in bytes.
|
|
67
|
+
*/
|
|
68
|
+
transferred?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A callback function type for handling captured outbound requests.
|
|
72
|
+
*/
|
|
73
|
+
export type OutboundRequestCallback = (log: OutboundRequestLog) => void;
|
|
74
|
+
/**
|
|
75
|
+
* A utility class that intercepts calls to `global.fetch` to track outbound HTTP requests.
|
|
76
|
+
*
|
|
77
|
+
* @warning This class monkey-patches the global `fetch` function. While it attempts to transparently
|
|
78
|
+
* pass through all calls, it may have side effects on global state or other libraries that rely on
|
|
79
|
+
* the original `fetch`. Proceed with caution.
|
|
80
|
+
*/
|
|
81
|
+
export declare class FetchInterceptor {
|
|
82
|
+
private originalFetch;
|
|
83
|
+
private originalHttpRequest;
|
|
84
|
+
private originalHttpsRequest;
|
|
85
|
+
private callbacks;
|
|
86
|
+
private isPatched;
|
|
87
|
+
constructor();
|
|
88
|
+
/**
|
|
89
|
+
* Patches the global `fetch` function to intercept requests.
|
|
90
|
+
* If already patched, this method does nothing.
|
|
91
|
+
*/
|
|
92
|
+
patch(): void;
|
|
93
|
+
private patchGlobalFetch;
|
|
94
|
+
private patchNodeRequests;
|
|
95
|
+
/**
|
|
96
|
+
* Restores the original functions.
|
|
97
|
+
*/
|
|
98
|
+
unpatch(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Adds a callback to be notified of outbound requests.
|
|
101
|
+
* @param callback The callback function.
|
|
102
|
+
*/
|
|
103
|
+
on(callback: OutboundRequestCallback): void;
|
|
104
|
+
private extractRequestMeta;
|
|
105
|
+
private processResponse;
|
|
106
|
+
private notify;
|
|
107
|
+
}
|
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
import { SurrealDatastore } from '../../../util/datastore';
|
|
2
|
+
interface AggregatedMetric {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
interval: string;
|
|
5
|
+
cpu: number;
|
|
6
|
+
memory: {
|
|
7
|
+
used: number;
|
|
8
|
+
total: number;
|
|
9
|
+
heapUsed: number;
|
|
10
|
+
heapTotal: number;
|
|
11
|
+
};
|
|
12
|
+
load: number[];
|
|
13
|
+
eventLoopLatency: {
|
|
14
|
+
min: number;
|
|
15
|
+
max: number;
|
|
16
|
+
mean: number;
|
|
17
|
+
p50: number;
|
|
18
|
+
p95: number;
|
|
19
|
+
p99: number;
|
|
20
|
+
};
|
|
21
|
+
requests: {
|
|
22
|
+
total: number;
|
|
23
|
+
rps: number;
|
|
24
|
+
success: number;
|
|
25
|
+
error: number;
|
|
26
|
+
};
|
|
27
|
+
responseTime: {
|
|
28
|
+
min: number;
|
|
29
|
+
max: number;
|
|
30
|
+
avg: number;
|
|
31
|
+
p50: number;
|
|
32
|
+
p95: number;
|
|
33
|
+
p99: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
2
36
|
export declare class MetricsCollector {
|
|
3
|
-
private
|
|
37
|
+
private onCollect?;
|
|
4
38
|
private currentIntervalStart;
|
|
5
39
|
private pendingDetails;
|
|
6
40
|
private eventLoopHistogram;
|
|
7
41
|
private timer;
|
|
8
|
-
|
|
42
|
+
db?: SurrealDatastore;
|
|
43
|
+
constructor(db?: SurrealDatastore, onCollect?: (metric: AggregatedMetric) => void);
|
|
9
44
|
recordRequest(duration: number, isError: boolean): void;
|
|
10
45
|
private alignTimestamp;
|
|
11
46
|
private collect;
|
|
12
47
|
private flushInterval;
|
|
13
48
|
stop(): void;
|
|
14
49
|
}
|
|
50
|
+
export {};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { HeadersInit } from 'bun';
|
|
2
1
|
import { $appRoot } from '../../../util/symbol';
|
|
3
2
|
import { ShokupanHooks, ShokupanPlugin } from '../../../util/types';
|
|
4
3
|
export interface RequestLog {
|
|
@@ -8,6 +7,21 @@ export interface RequestLog {
|
|
|
8
7
|
duration: number;
|
|
9
8
|
timestamp: number;
|
|
10
9
|
handlerStack?: any[];
|
|
10
|
+
body?: any;
|
|
11
|
+
contentType?: string;
|
|
12
|
+
type: 'xhr' | 'fetch' | 'ws';
|
|
13
|
+
direction: 'inbound' | 'outbound';
|
|
14
|
+
size?: number;
|
|
15
|
+
protocol?: string;
|
|
16
|
+
domain?: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
scheme?: string;
|
|
19
|
+
remoteIP?: string;
|
|
20
|
+
cookies?: number;
|
|
21
|
+
transferred?: number;
|
|
22
|
+
requestHeaders?: Record<string, string>;
|
|
23
|
+
responseHeaders?: Record<string, string>;
|
|
24
|
+
requestBody?: any;
|
|
11
25
|
}
|
|
12
26
|
export interface DashboardConfig {
|
|
13
27
|
getRequestHeaders?: () => HeadersInit;
|
|
@@ -23,15 +37,35 @@ export interface DashboardConfig {
|
|
|
23
37
|
asyncapi?: boolean | {
|
|
24
38
|
path?: string;
|
|
25
39
|
};
|
|
40
|
+
apiExplorer?: boolean | {
|
|
41
|
+
path?: string;
|
|
42
|
+
};
|
|
26
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Strategy for pushing request updates to the dashboard.
|
|
46
|
+
* 'immediate' - pushes every request as soon as it completes.
|
|
47
|
+
* 'batched' - buffers requests and pushes them at the interval specified by updateInterval.
|
|
48
|
+
* @default 'immediate'
|
|
49
|
+
*/
|
|
50
|
+
updateStrategy?: 'immediate' | 'batched';
|
|
51
|
+
/**
|
|
52
|
+
* Interval in milliseconds for pushing batched updates.
|
|
53
|
+
* @default 10_000
|
|
54
|
+
*/
|
|
55
|
+
updateInterval?: number;
|
|
27
56
|
}
|
|
28
57
|
export declare class Dashboard implements ShokupanPlugin {
|
|
29
58
|
private readonly dashboardConfig;
|
|
30
59
|
private [$appRoot];
|
|
31
60
|
private router;
|
|
32
61
|
private metrics;
|
|
62
|
+
private clients;
|
|
63
|
+
private broadcastTimer;
|
|
64
|
+
private requestPushTimer;
|
|
65
|
+
private requestsBuffer;
|
|
33
66
|
private startTime;
|
|
34
67
|
private instrumented;
|
|
68
|
+
private mountPath;
|
|
35
69
|
private metricsCollector;
|
|
36
70
|
get db(): import('../../../util/datastore').SurrealDatastore;
|
|
37
71
|
constructor(dashboardConfig?: DashboardConfig);
|
|
@@ -41,12 +75,21 @@ export declare class Dashboard implements ShokupanPlugin {
|
|
|
41
75
|
private detectIntegrations;
|
|
42
76
|
private static getBasePath;
|
|
43
77
|
private setupRoutes;
|
|
78
|
+
private getUptime;
|
|
79
|
+
private getPublicMetrics;
|
|
80
|
+
private broadcastMetricUpdate;
|
|
81
|
+
private sendHistory;
|
|
82
|
+
private broadcastMetrics;
|
|
44
83
|
private instrumentApp;
|
|
45
84
|
private assignIdsToRegistry;
|
|
46
85
|
recordNodeMetric(id: string, type: string, duration: number, isError: boolean): void;
|
|
47
86
|
recordEdgeMetric(from: string, to: string): void;
|
|
48
87
|
private getLinkPattern;
|
|
49
88
|
getHooks(): ShokupanHooks;
|
|
89
|
+
private startRequestPushTimer;
|
|
90
|
+
private broadcastRequestUpdates;
|
|
50
91
|
private updateTiming;
|
|
92
|
+
private serializeHandlerStack;
|
|
93
|
+
private serializeBody;
|
|
51
94
|
}
|
|
52
95
|
export default function DebugDashboard(config?: DashboardConfig): Dashboard;
|