shokupan 0.13.0 → 0.14.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-BOtveWL-.cjs → analyzer-BZSVGTmP.cjs} +5 -4
- package/dist/analyzer-BZSVGTmP.cjs.map +1 -0
- package/dist/{analyzer-B0fMzeIo.js → analyzer-Faojwm7c.js} +5 -4
- package/dist/analyzer-Faojwm7c.js.map +1 -0
- package/dist/{analyzer.impl-CUDO6vpn.cjs → analyzer.impl-5aCqtook.cjs} +28 -11
- package/dist/analyzer.impl-5aCqtook.cjs.map +1 -0
- package/dist/{analyzer.impl-DmHe92Oi.js → analyzer.impl-COdN69gL.js} +28 -11
- package/dist/analyzer.impl-COdN69gL.js.map +1 -0
- package/dist/ast-analyzer-worker-C3jrQ8VR.js +184 -0
- package/dist/ast-analyzer-worker-C3jrQ8VR.js.map +1 -0
- package/dist/ast-analyzer-worker-D_uYkqmY.cjs +184 -0
- package/dist/ast-analyzer-worker-D_uYkqmY.cjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +39 -4
- package/dist/decorators/di.d.ts +31 -0
- package/dist/decorators/hooks.d.ts +28 -0
- package/dist/decorators/http.d.ts +60 -0
- package/dist/decorators/index.d.ts +8 -0
- package/dist/decorators/mcp.d.ts +48 -0
- package/dist/decorators/util/container.d.ts +36 -0
- package/dist/decorators/websocket.d.ts +172 -0
- package/dist/index-BP7v0Hiv.cjs +12216 -0
- package/dist/index-BP7v0Hiv.cjs.map +1 -0
- package/dist/index-CUNBeZKj.js +12176 -0
- package/dist/index-CUNBeZKj.js.map +1 -0
- package/dist/index.cjs +137 -10518
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +137 -10477
- package/dist/index.js.map +1 -1
- package/dist/{json-parser-COdZ0fqY.cjs → json-parser-BA0mUgMF.cjs} +3 -3
- package/dist/json-parser-BA0mUgMF.cjs.map +1 -0
- package/dist/{json-parser-B3dnQmCC.js → json-parser-BFM-SnBR.js} +3 -3
- package/dist/json-parser-BFM-SnBR.js.map +1 -0
- package/dist/knex-DDPXR-sQ.js +218 -0
- package/dist/knex-DDPXR-sQ.js.map +1 -0
- package/dist/knex-DghF-jjm.cjs +240 -0
- package/dist/knex-DghF-jjm.cjs.map +1 -0
- package/dist/level-BU87Jbus.js +184 -0
- package/dist/level-BU87Jbus.js.map +1 -0
- package/dist/level-DNFl2n-m.cjs +184 -0
- package/dist/level-DNFl2n-m.cjs.map +1 -0
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +54 -28
- package/dist/plugins/application/asyncapi/plugin.d.ts +1 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +22 -11
- package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +3 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +5 -3
- package/dist/plugins/application/dashboard/plugin.d.ts +36 -3
- package/dist/plugins/application/dashboard/static/requests.js +517 -53
- package/dist/plugins/application/dashboard/static/tabs.js +2 -2
- package/dist/plugins/application/error-view/index.d.ts +25 -0
- package/dist/plugins/application/error-view/reason-phrases.d.ts +1 -0
- package/dist/plugins/application/openapi/analyzer.d.ts +3 -1
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +4 -2
- package/dist/router.d.ts +56 -21
- package/dist/shokupan.d.ts +25 -11
- package/dist/sqlite-CLrcTkti.js +180 -0
- package/dist/sqlite-CLrcTkti.js.map +1 -0
- package/dist/sqlite-n7FQ6Ja6.cjs +180 -0
- package/dist/sqlite-n7FQ6Ja6.cjs.map +1 -0
- package/dist/surreal-6QONU6xa.cjs +210 -0
- package/dist/surreal-6QONU6xa.cjs.map +1 -0
- package/dist/surreal-w7DeGVI-.js +188 -0
- package/dist/surreal-w7DeGVI-.js.map +1 -0
- package/dist/util/adapter/datastore/knex.d.ts +29 -0
- package/dist/util/adapter/datastore/level.d.ts +26 -0
- package/dist/util/adapter/datastore/sqlite.d.ts +24 -0
- package/dist/util/adapter/datastore/surreal.d.ts +29 -0
- package/dist/util/adapter/datastore.d.ts +59 -0
- package/dist/util/adapter/h3.d.ts +8 -0
- package/dist/util/adapter/index.d.ts +1 -0
- package/dist/util/ast-analyzer-worker.d.ts +77 -0
- package/dist/util/ast-worker-thread.d.ts +1 -0
- package/dist/util/cookie-parser.d.ts +6 -0
- package/dist/util/env-loader.d.ts +7 -0
- package/dist/util/html.d.ts +15 -0
- package/dist/util/ide.d.ts +9 -0
- package/dist/util/logger.d.ts +25 -0
- package/dist/util/query-string.d.ts +8 -0
- package/dist/util/response-transformer.d.ts +87 -0
- package/dist/util/symbol.d.ts +1 -0
- package/dist/util/types.d.ts +116 -42
- package/dist/websocket.d.ts +163 -0
- package/package.json +27 -1
- package/dist/analyzer-B0fMzeIo.js.map +0 -1
- package/dist/analyzer-BOtveWL-.cjs.map +0 -1
- package/dist/analyzer.impl-CUDO6vpn.cjs.map +0 -1
- package/dist/analyzer.impl-DmHe92Oi.js.map +0 -1
- package/dist/json-parser-B3dnQmCC.js.map +0 -1
- package/dist/json-parser-COdZ0fqY.cjs.map +0 -1
- package/dist/plugins/application/error-view/views/error.d.ts +0 -2
- package/dist/plugins/application/error-view/views/status.d.ts +0 -2
- package/dist/util/decorators.d.ts +0 -134
- package/dist/util/di.d.ts +0 -13
- /package/dist/{util → decorators/util}/metadata.d.ts +0 -0
- /package/dist/{util → decorators/util}/stack.d.ts +0 -0
|
@@ -28,6 +28,9 @@ let maxRequestTime = 0;
|
|
|
28
28
|
function initRequests() {
|
|
29
29
|
console.log('[requests.js] Initializing...');
|
|
30
30
|
|
|
31
|
+
if (window.updateRequestsList) console.log('[requests.js] updateRequestsList is already defined!');
|
|
32
|
+
else console.log('[requests.js] Defining updateRequestsList...');
|
|
33
|
+
|
|
31
34
|
// Initialize Filter Listeners
|
|
32
35
|
const txtFilter = document.getElementById('network-filter-text');
|
|
33
36
|
const typeFilter = document.getElementById('network-filter-type');
|
|
@@ -164,7 +167,7 @@ function initRequests() {
|
|
|
164
167
|
} catch (e) { }
|
|
165
168
|
|
|
166
169
|
return `<div style="display: flex; flex-direction: column; line-height: 1.2;">
|
|
167
|
-
<span style="color: var(--text-secondary);">${name}</span>
|
|
170
|
+
<span style="color: var(--text-secondary);">${escapeHtml(name)}</span>
|
|
168
171
|
</div>`;
|
|
169
172
|
},
|
|
170
173
|
headerContextMenu: headerMenu
|
|
@@ -198,8 +201,8 @@ function initRequests() {
|
|
|
198
201
|
formatter: function (cell) {
|
|
199
202
|
const row = cell.getData();
|
|
200
203
|
// Prefer explicit protocol version (e.g. 1.1, h2) if available
|
|
201
|
-
if (row.protocol && row.protocol !== 'http' && row.protocol !== 'https') return row.protocol;
|
|
202
|
-
return row.scheme || row.protocol || '-';
|
|
204
|
+
if (row.protocol && row.protocol !== 'http' && row.protocol !== 'https') return escapeHtml(row.protocol);
|
|
205
|
+
return escapeHtml(row.scheme || row.protocol || '-');
|
|
203
206
|
},
|
|
204
207
|
headerContextMenu: headerMenu
|
|
205
208
|
},
|
|
@@ -238,7 +241,7 @@ function initRequests() {
|
|
|
238
241
|
if (r.type === 'fetch') return 'fetch';
|
|
239
242
|
if (r.type === 'xhr') return 'xhr';
|
|
240
243
|
if (r.type === 'ws') return 'ws';
|
|
241
|
-
return r.contentType || 'document';
|
|
244
|
+
return escapeHtml(r.contentType || 'document');
|
|
242
245
|
},
|
|
243
246
|
headerContextMenu: headerMenu
|
|
244
247
|
},
|
|
@@ -327,44 +330,7 @@ function initRequests() {
|
|
|
327
330
|
label: "Replay Request",
|
|
328
331
|
action: function (e, row) {
|
|
329
332
|
const data = row.getData();
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
// Determine direction if not explicit
|
|
333
|
-
const direction = data.direction || 'inbound';
|
|
334
|
-
|
|
335
|
-
fetch(basePath + '/replay', {
|
|
336
|
-
method: 'POST',
|
|
337
|
-
headers: { 'Content-Type': 'application/json' },
|
|
338
|
-
body: JSON.stringify({
|
|
339
|
-
method: data.method,
|
|
340
|
-
url: data.url,
|
|
341
|
-
headers: data.requestHeaders,
|
|
342
|
-
body: data.requestBody,
|
|
343
|
-
direction: direction
|
|
344
|
-
})
|
|
345
|
-
})
|
|
346
|
-
.then(res => res.json())
|
|
347
|
-
.then(result => {
|
|
348
|
-
if (result.error) {
|
|
349
|
-
alert('Replay Failed: ' + result.error);
|
|
350
|
-
} else {
|
|
351
|
-
// Show result in a simplified details view or just alert success?
|
|
352
|
-
// User requirement: "presents the response data to the user"
|
|
353
|
-
// Let's create a temporary object mimicking a request log and show it in details view
|
|
354
|
-
const replayLog = {
|
|
355
|
-
...data,
|
|
356
|
-
id: 'replay-' + Date.now(),
|
|
357
|
-
status: result.status,
|
|
358
|
-
duration: result.duration || 0,
|
|
359
|
-
timestamp: Date.now(),
|
|
360
|
-
responseHeaders: result.headers,
|
|
361
|
-
responseBody: result.data,
|
|
362
|
-
size: result.data ? result.data.length : 0
|
|
363
|
-
};
|
|
364
|
-
showRequestDetails(replayLog);
|
|
365
|
-
}
|
|
366
|
-
})
|
|
367
|
-
.catch(err => console.error("Replay fetch failed", err));
|
|
333
|
+
openReplayModal(data);
|
|
368
334
|
}
|
|
369
335
|
},
|
|
370
336
|
{
|
|
@@ -674,7 +640,12 @@ function updateTimestamps(requests) {
|
|
|
674
640
|
|
|
675
641
|
// Global handler for Client.js
|
|
676
642
|
window.updateRequestsList = function (newRequests) {
|
|
677
|
-
|
|
643
|
+
console.log('[requests.js] updateRequestsList called with', newRequests ? newRequests.length : 0, 'items');
|
|
644
|
+
if (!window.requestsTable) {
|
|
645
|
+
console.warn('[requests.js] requestsTable is missing!');
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!newRequests || !newRequests.length) return;
|
|
678
649
|
|
|
679
650
|
// Update Timestamps
|
|
680
651
|
const changed = updateTimestamps(newRequests);
|
|
@@ -736,6 +707,11 @@ function showRequestDetails(request) {
|
|
|
736
707
|
// { id: 'security', label: 'Security' } // Enable if we have data
|
|
737
708
|
];
|
|
738
709
|
|
|
710
|
+
// Add Middleware tab if we have handler stack data with state changes
|
|
711
|
+
if (request.handlerStack && request.handlerStack.some(h => h.stateChanges && Object.keys(h.stateChanges).length > 0)) {
|
|
712
|
+
tabs.splice(5, 0, { id: 'middleware', label: 'Middleware' });
|
|
713
|
+
}
|
|
714
|
+
|
|
739
715
|
if (request.scheme === 'https' || request.scheme === 'wss') {
|
|
740
716
|
tabs.push({ id: 'security', label: 'Security' });
|
|
741
717
|
}
|
|
@@ -809,6 +785,8 @@ function renderTabContent(tabId, request) {
|
|
|
809
785
|
return renderResponseTab(request);
|
|
810
786
|
case 'timings':
|
|
811
787
|
return renderTimingsTab(request);
|
|
788
|
+
case 'middleware':
|
|
789
|
+
return renderMiddlewareTab(request);
|
|
812
790
|
case 'security':
|
|
813
791
|
return renderSecurityTab(request);
|
|
814
792
|
default:
|
|
@@ -816,6 +794,17 @@ function renderTabContent(tabId, request) {
|
|
|
816
794
|
}
|
|
817
795
|
}
|
|
818
796
|
|
|
797
|
+
// Utility
|
|
798
|
+
function escapeHtml(text) {
|
|
799
|
+
if (!text) return '';
|
|
800
|
+
return String(text)
|
|
801
|
+
.replace(/&/g, "&")
|
|
802
|
+
.replace(/</g, "<")
|
|
803
|
+
.replace(/>/g, ">")
|
|
804
|
+
.replace(/"/g, """)
|
|
805
|
+
.replace(/'/g, "'");
|
|
806
|
+
}
|
|
807
|
+
|
|
819
808
|
function renderNameValueTable(items, emptyMessage = 'No items found') {
|
|
820
809
|
if (!items || !items.length) return `<div style="padding: 8px; color: var(--text-secondary);">${emptyMessage}</div>`;
|
|
821
810
|
return `
|
|
@@ -829,8 +818,8 @@ function renderNameValueTable(items, emptyMessage = 'No items found') {
|
|
|
829
818
|
<tbody>
|
|
830
819
|
${items.map(c => `
|
|
831
820
|
<tr style="border-bottom: 1px solid var(--border-color-dim, #33333333);">
|
|
832
|
-
<td style="padding: 4px 8px; font-weight: 500;">${c.name}</td>
|
|
833
|
-
<td style="padding: 4px 8px; word-break: break-all;">${c.value}</td>
|
|
821
|
+
<td style="padding: 4px 8px; font-weight: 500;">${escapeHtml(c.name)}</td>
|
|
822
|
+
<td style="padding: 4px 8px; word-break: break-all;">${escapeHtml(c.value)}</td>
|
|
834
823
|
</tr>
|
|
835
824
|
`).join('')}
|
|
836
825
|
</tbody>
|
|
@@ -843,13 +832,13 @@ function renderHeadersTab(request) {
|
|
|
843
832
|
if (!headers || Object.keys(headers).length === 0) return '';
|
|
844
833
|
const rows = Object.entries(headers).map(([k, v]) => `
|
|
845
834
|
<tr>
|
|
846
|
-
<td style="font-weight: 500; color: var(--text-flavor); padding: 4px 8px; vertical-align: top;">${k}:</td>
|
|
847
|
-
<td style="word-break: break-all; padding: 4px 8px;">${v}</td>
|
|
835
|
+
<td style="font-weight: 500; color: var(--text-flavor); padding: 4px 8px; vertical-align: top;">${escapeHtml(k)}:</td>
|
|
836
|
+
<td style="word-break: break-all; padding: 4px 8px;">${escapeHtml(v)}</td>
|
|
848
837
|
</tr>
|
|
849
838
|
`).join('');
|
|
850
839
|
return `
|
|
851
840
|
<details open style="margin-bottom: 1rem;">
|
|
852
|
-
<summary style="font-weight: bold; padding: 4px 0; cursor: pointer; color: var(--text-primary);">${title}</summary>
|
|
841
|
+
<summary style="font-weight: bold; padding: 4px 0; cursor: pointer; color: var(--text-primary);">${escapeHtml(title)}</summary>
|
|
853
842
|
<table style="width: 100%; border-collapse: collapse; font-size: 0.9em;">
|
|
854
843
|
${rows}
|
|
855
844
|
</table>
|
|
@@ -862,11 +851,11 @@ function renderHeadersTab(request) {
|
|
|
862
851
|
<details open style="margin-bottom: 1rem;">
|
|
863
852
|
<summary style="font-weight: bold; padding: 4px 0; cursor: pointer; color: var(--text-primary);">General</summary>
|
|
864
853
|
<div style="display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 0.9em; padding-left: 8px;">
|
|
865
|
-
<div style="color: var(--text-flavor);">Request URL:</div><div style="word-break: break-all;">${request.url}</div>
|
|
866
|
-
<div style="color: var(--text-flavor);">Request Method:</div><div>${request.method}</div>
|
|
867
|
-
<div style="color: var(--text-flavor);">Status Code:</div><div>${request.status}</div>
|
|
868
|
-
<div style="color: var(--text-flavor);">Remote Address:</div><div>${request.remoteIP || '-'}</div>
|
|
869
|
-
<div style="color: var(--text-flavor);">Referrer Policy:</div><div>${request.requestHeaders?.['referrer-policy'] || 'strict-origin-when-cross-origin'}</div>
|
|
854
|
+
<div style="color: var(--text-flavor);">Request URL:</div><div style="word-break: break-all;">${escapeHtml(request.url)}</div>
|
|
855
|
+
<div style="color: var(--text-flavor);">Request Method:</div><div>${escapeHtml(request.method)}</div>
|
|
856
|
+
<div style="color: var(--text-flavor);">Status Code:</div><div>${escapeHtml(request.status)}</div>
|
|
857
|
+
<div style="color: var(--text-flavor);">Remote Address:</div><div>${escapeHtml(request.remoteIP || '-')}</div>
|
|
858
|
+
<div style="color: var(--text-flavor);">Referrer Policy:</div><div>${escapeHtml(request.requestHeaders?.['referrer-policy'] || 'strict-origin-when-cross-origin')}</div>
|
|
870
859
|
</div>
|
|
871
860
|
</details>
|
|
872
861
|
${formatHeaderSection('Response Headers', request.responseHeaders)}
|
|
@@ -998,6 +987,116 @@ function renderSecurityTab(request) {
|
|
|
998
987
|
`;
|
|
999
988
|
}
|
|
1000
989
|
|
|
990
|
+
function renderMiddlewareTab(request) {
|
|
991
|
+
if (!request.handlerStack || request.handlerStack.length === 0) {
|
|
992
|
+
return `
|
|
993
|
+
<div style="padding: 2rem; text-align: center; color: var(--text-secondary);">
|
|
994
|
+
No middleware tracking data available.
|
|
995
|
+
<br><br>
|
|
996
|
+
Enable middleware tracking by setting <code style="background: var(--bg-primary); padding: 2px 6px; border-radius: 3px;">enableMiddlewareTracking: true</code> in your application config.
|
|
997
|
+
</div>
|
|
998
|
+
`;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const totalDuration = request.duration || 1;
|
|
1002
|
+
const formatValue = (val) => {
|
|
1003
|
+
if (val === undefined) return '<span style="color: var(--text-secondary); font-style: italic;">undefined</span>';
|
|
1004
|
+
if (val === null) return '<span style="color: var(--text-secondary); font-style: italic;">null</span>';
|
|
1005
|
+
if (typeof val === 'string') return `"<span style="color: var(--color-success);">${escapeHtml(val)}</span>"`;
|
|
1006
|
+
if (typeof val === 'number') return `<span style="color: var(--color-info);">${val}</span>`;
|
|
1007
|
+
if (typeof val === 'boolean') return `<span style="color: var(--color-warning);">${val}</span>`;
|
|
1008
|
+
if (typeof val === 'object') {
|
|
1009
|
+
try {
|
|
1010
|
+
return `<span style="color: var(--text-secondary);">${escapeHtml(JSON.stringify(val, null, 2))}</span>`;
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
return `<span style="color: var(--text-secondary);">[Object]</span>`;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return escapeHtml(String(val));
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
let html = '<div style="padding: 1rem;">';
|
|
1019
|
+
html += '<div style="margin-bottom: 1rem;">';
|
|
1020
|
+
html += '<div style="font-size: 0.9em; color: var(--text-secondary); margin-bottom: 0.5rem;">';
|
|
1021
|
+
html += 'This tab shows state mutations made by each middleware handler during request processing.';
|
|
1022
|
+
html += '</div>';
|
|
1023
|
+
html += '</div>';
|
|
1024
|
+
|
|
1025
|
+
html += '<div style="display: flex; flex-direction: column; gap: 12px;">';
|
|
1026
|
+
|
|
1027
|
+
request.handlerStack.forEach((item, index) => {
|
|
1028
|
+
const duration = item.duration > 0 ? item.duration : 0.01;
|
|
1029
|
+
const percent = Math.min(100, Math.max(1, (duration / totalDuration) * 100));
|
|
1030
|
+
const isSlow = percent > 15;
|
|
1031
|
+
const hasStateChanges = item.stateChanges && Object.keys(item.stateChanges).length > 0;
|
|
1032
|
+
|
|
1033
|
+
const detailsId = `middleware-${index}`;
|
|
1034
|
+
|
|
1035
|
+
html += `
|
|
1036
|
+
<details ${hasStateChanges ? 'open' : ''} id="${detailsId}" style="border: 1px solid var(--border-color); border-radius: 6px; overflow: hidden; background: var(--bg-primary);">
|
|
1037
|
+
<summary style="padding: 12px; cursor: pointer; background: var(--bg-secondary); border-left: 3px solid ${hasStateChanges ? 'var(--primary-color, #3b82f6)' : 'var(--border-color)'}; display: flex; justify-content: space-between; align-items: center;">
|
|
1038
|
+
<div style="flex: 1;">
|
|
1039
|
+
<div style="font-weight: 500; margin-bottom: 4px;">
|
|
1040
|
+
${hasStateChanges ? '🔹 ' : '⚪ '}${escapeHtml(item.name)}
|
|
1041
|
+
</div>
|
|
1042
|
+
<div style="font-size: 0.85em; color: var(--text-secondary); font-family: monospace;">
|
|
1043
|
+
${escapeHtml(item.file)}:${item.line}
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div style="text-align: right;">
|
|
1047
|
+
<div style="font-family: monospace; font-size: 0.9em; color: ${isSlow ? 'var(--color-warning)' : 'var(--text-secondary)'};">
|
|
1048
|
+
${printDuration(duration)}
|
|
1049
|
+
</div>
|
|
1050
|
+
${hasStateChanges ? `<div style="font-size: 0.8em; color: var(--primary-color, #3b82f6); margin-top: 2px;">${Object.keys(item.stateChanges).length} change${Object.keys(item.stateChanges).length === 1 ? '' : 's'}</div>` : '<div style="font-size: 0.8em; color: var(--text-secondary); margin-top: 2px;">No changes</div>'}
|
|
1051
|
+
</div>
|
|
1052
|
+
</summary>
|
|
1053
|
+
|
|
1054
|
+
<div style="padding: 12px; border-top: 1px solid var(--border-color);">`;
|
|
1055
|
+
|
|
1056
|
+
if (hasStateChanges) {
|
|
1057
|
+
html += '<div style="margin-bottom: 8px; font-weight: 500; color: var(--text-primary);">State Changes:</div>';
|
|
1058
|
+
html += '<table style="width: 100%; border-collapse: collapse; font-size: 0.9em; font-family: monospace;">';
|
|
1059
|
+
|
|
1060
|
+
Object.entries(item.stateChanges).forEach(([key, value]) => {
|
|
1061
|
+
html += `
|
|
1062
|
+
<tr style="border-bottom: 1px solid var(--border-color-dim, #33333333);">
|
|
1063
|
+
<td style="padding: 6px 8px; color: var(--text-flavor); font-weight: 500; vertical-align: top; width: 30%;">
|
|
1064
|
+
${escapeHtml(key)}
|
|
1065
|
+
</td>
|
|
1066
|
+
<td style="padding: 6px 8px; color: var(--text-secondary); vertical-align: top; width: 10%; text-align: center;">
|
|
1067
|
+
→
|
|
1068
|
+
</td>
|
|
1069
|
+
<td style="padding: 6px 8px; word-break: break-all; vertical-align: top;">
|
|
1070
|
+
${formatValue(value)}
|
|
1071
|
+
</td>
|
|
1072
|
+
</tr>
|
|
1073
|
+
`;
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
html += '</table>';
|
|
1077
|
+
} else {
|
|
1078
|
+
html += '<div style="color: var(--text-secondary); font-style: italic; text-align: center; padding: 1rem;">';
|
|
1079
|
+
html += 'This middleware did not modify ctx.state';
|
|
1080
|
+
html += '</div>';
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
html += '<div style="margin-top: 12px;">';
|
|
1084
|
+
html += '<div style="font-size: 0.8em; color: var(--text-secondary); margin-bottom: 4px;">Execution Time</div>';
|
|
1085
|
+
html += '<div style="height: 6px; background: var(--bg-secondary); border-radius: 3px; overflow: hidden;">';
|
|
1086
|
+
html += `<div style="height: 100%; width: ${percent}%; background: ${isSlow ? 'var(--color-warning)' : 'var(--color-success)'}; transition: width 0.3s ease;"></div>`;
|
|
1087
|
+
html += '</div>';
|
|
1088
|
+
html += '</div>';
|
|
1089
|
+
|
|
1090
|
+
html += '</div>';
|
|
1091
|
+
html += '</details>';
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
html += '</div>';
|
|
1095
|
+
html += '</div>';
|
|
1096
|
+
|
|
1097
|
+
return html;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1001
1100
|
function closeRequestDetails() {
|
|
1002
1101
|
document.getElementById('request-details-container').style.display = 'none';
|
|
1003
1102
|
if (window.requestsTable) window.requestsTable.redraw();
|
|
@@ -1212,3 +1311,368 @@ function formatBytes(bytes, decimals = 2) {
|
|
|
1212
1311
|
}
|
|
1213
1312
|
|
|
1214
1313
|
|
|
1314
|
+
|
|
1315
|
+
// --- Replay Modal Implementation ---
|
|
1316
|
+
|
|
1317
|
+
function injectReplayStyles() {
|
|
1318
|
+
if (document.getElementById('replay-modal-styles')) return;
|
|
1319
|
+
const style = document.createElement('style');
|
|
1320
|
+
style.id = 'replay-modal-styles';
|
|
1321
|
+
style.textContent = `
|
|
1322
|
+
#replay-modal-overlay {
|
|
1323
|
+
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
|
|
1324
|
+
display: flex; align-items: center; justify-content: center;
|
|
1325
|
+
backdrop-filter: blur(2px);
|
|
1326
|
+
}
|
|
1327
|
+
#replay-modal {
|
|
1328
|
+
background: var(--bg-secondary); width: 800px; max-width: 95vw; height: 80vh;
|
|
1329
|
+
border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
|
|
1330
|
+
display: flex; flex-direction: column; border: 1px solid var(--border-color);
|
|
1331
|
+
}
|
|
1332
|
+
.replay-header {
|
|
1333
|
+
padding: 1rem; border-bottom: 1px solid var(--border-color);
|
|
1334
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
1335
|
+
font-weight: 600; font-size: 1.1rem;
|
|
1336
|
+
}
|
|
1337
|
+
.replay-body { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
|
1338
|
+
.replay-toolbar {
|
|
1339
|
+
padding: 1rem; display: flex; gap: 0.5rem; border-bottom: 1px solid var(--border-color);
|
|
1340
|
+
background: var(--bg-primary);
|
|
1341
|
+
}
|
|
1342
|
+
.replay-input {
|
|
1343
|
+
flex: 1; padding: 0.5rem; border-radius: 4px; border: 1px solid var(--border-color);
|
|
1344
|
+
background: var(--bg-secondary); color: var(--text-primary);
|
|
1345
|
+
}
|
|
1346
|
+
.replay-method {
|
|
1347
|
+
padding: 0.5rem; border-radius: 4px; border: 1px solid var(--border-color);
|
|
1348
|
+
background: var(--bg-secondary); color: var(--text-primary); font-weight: bold;
|
|
1349
|
+
}
|
|
1350
|
+
.replay-btn {
|
|
1351
|
+
padding: 0.5rem 1rem; border-radius: 4px; border: none; cursor: pointer;
|
|
1352
|
+
font-weight: 500; display: flex; align-items: center; gap: 0.5rem;
|
|
1353
|
+
}
|
|
1354
|
+
.btn-primary { background: var(--primary-color, #3b82f6); color: white; }
|
|
1355
|
+
.btn-secondary { background: var(--bg-primary, #e5e7eb); color: var(--text-primary); }
|
|
1356
|
+
.dark .btn-secondary { background: #374151; }
|
|
1357
|
+
|
|
1358
|
+
.replay-tabs { display: flex; border-bottom: 1px solid var(--border-color); background: var(--bg-primary); }
|
|
1359
|
+
.replay-tab {
|
|
1360
|
+
padding: 0.75rem 1rem; cursor: pointer; border-bottom: 2px solid transparent;
|
|
1361
|
+
color: var(--text-secondary);
|
|
1362
|
+
}
|
|
1363
|
+
.replay-tab.active {
|
|
1364
|
+
border-color: var(--primary-color, #3b82f6); color: var(--text-primary);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.replay-content { flex: 1; overflow-y: auto; padding: 1rem; position: relative; }
|
|
1368
|
+
.code-editor {
|
|
1369
|
+
width: 100%; height: 100%; font-family: monospace; border: none; resize: none;
|
|
1370
|
+
background: transparent; color: var(--text-primary); outline: none;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.kv-editor-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
|
|
1374
|
+
.kv-key, .kv-val { flex: 1; padding: 0.4rem; border: 1px solid var(--border-color); background: var(--bg-secondary); color: var(--text-primary); border-radius: 4px; }
|
|
1375
|
+
.kv-remove { padding: 0.4rem; cursor: pointer; color: #ef4444; }
|
|
1376
|
+
|
|
1377
|
+
.response-status-badge {
|
|
1378
|
+
padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.85rem; font-weight: bold;
|
|
1379
|
+
}
|
|
1380
|
+
.status-2xx { background: rgba(16, 185, 129, 0.2); color: #10b981; }
|
|
1381
|
+
.status-4xx { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
|
|
1382
|
+
.status-5xx { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
|
|
1383
|
+
`;
|
|
1384
|
+
document.head.appendChild(style);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
let currentReplayState = {
|
|
1388
|
+
method: 'GET',
|
|
1389
|
+
url: '',
|
|
1390
|
+
headers: [],
|
|
1391
|
+
body: '',
|
|
1392
|
+
activeTab: 'body'
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
function openReplayModal(request) {
|
|
1396
|
+
injectReplayStyles();
|
|
1397
|
+
|
|
1398
|
+
// Initialize State
|
|
1399
|
+
currentReplayState = {
|
|
1400
|
+
method: request.method || 'GET',
|
|
1401
|
+
url: request.url || '',
|
|
1402
|
+
headers: Object.entries(request.requestHeaders || {}).map(([k, v]) => ({ key: k, value: v })),
|
|
1403
|
+
body: typeof (request.requestBody) === 'string' ? request.requestBody : (request.requestBody ? JSON.stringify(request.requestBody || {}, null, 2) : ''),
|
|
1404
|
+
direction: request.direction || 'outbound',
|
|
1405
|
+
activeTab: 'body',
|
|
1406
|
+
response: null
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
renderReplayModal();
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function closeReplayModal() {
|
|
1413
|
+
const el = document.getElementById('replay-modal-overlay');
|
|
1414
|
+
if (el) el.remove();
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function renderReplayModal() {
|
|
1418
|
+
let el = document.getElementById('replay-modal-overlay');
|
|
1419
|
+
if (!el) {
|
|
1420
|
+
el = document.createElement('div');
|
|
1421
|
+
el.id = 'replay-modal-overlay';
|
|
1422
|
+
document.body.appendChild(el);
|
|
1423
|
+
|
|
1424
|
+
// Close on backdrop click
|
|
1425
|
+
el.addEventListener('click', (e) => {
|
|
1426
|
+
if (e.target === el) closeReplayModal();
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const { method, url, headers, body, activeTab, response } = currentReplayState;
|
|
1431
|
+
const isResponse = activeTab === 'response';
|
|
1432
|
+
|
|
1433
|
+
const tabs = ['body', 'headers', 'params', 'response'];
|
|
1434
|
+
|
|
1435
|
+
el.innerHTML = `
|
|
1436
|
+
<div id="replay-modal">
|
|
1437
|
+
<div class="replay-header">
|
|
1438
|
+
<span>Replay Request</span>
|
|
1439
|
+
<div style="display:flex; gap: 0.5rem">
|
|
1440
|
+
<button class="replay-btn btn-secondary" onclick="document.getElementById('replay-import-file').click()">
|
|
1441
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
1442
|
+
Import
|
|
1443
|
+
</button>
|
|
1444
|
+
<input type="file" id="replay-import-file" style="display:none" onchange="handleReplayImport(this)">
|
|
1445
|
+
<button class="replay-btn btn-secondary" onclick="copyReplayCurl()">
|
|
1446
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
1447
|
+
Copy Curl
|
|
1448
|
+
</button>
|
|
1449
|
+
<button class="replay-btn" style="background: transparent; color: var(--text-secondary)" onclick="closeReplayModal()">✕</button>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
|
|
1453
|
+
<div class="replay-toolbar">
|
|
1454
|
+
<select class="replay-method" onchange="updateReplayState('method', this.value)">
|
|
1455
|
+
<option value="GET" ${method === 'GET' ? 'selected' : ''}>GET</option>
|
|
1456
|
+
<option value="POST" ${method === 'POST' ? 'selected' : ''}>POST</option>
|
|
1457
|
+
<option value="PUT" ${method === 'PUT' ? 'selected' : ''}>PUT</option>
|
|
1458
|
+
<option value="DELETE" ${method === 'DELETE' ? 'selected' : ''}>DELETE</option>
|
|
1459
|
+
<option value="PATCH" ${method === 'PATCH' ? 'selected' : ''}>PATCH</option>
|
|
1460
|
+
</select>
|
|
1461
|
+
<input class="replay-input" value="${escapeHtml(url)}" oninput="updateReplayState('url', this.value)" placeholder="https://api.example.com/v1/...">
|
|
1462
|
+
<button class="replay-btn btn-primary" onclick="executeReplay()">
|
|
1463
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
1464
|
+
Send
|
|
1465
|
+
</button>
|
|
1466
|
+
</div>
|
|
1467
|
+
|
|
1468
|
+
<div class="replay-tabs">
|
|
1469
|
+
<div class="replay-tab ${activeTab === 'body' ? 'active' : ''}" onclick="updateReplayState('activeTab', 'body')">Body</div>
|
|
1470
|
+
<div class="replay-tab ${activeTab === 'headers' ? 'active' : ''}" onclick="updateReplayState('activeTab', 'headers')">Headers</div>
|
|
1471
|
+
<div class="replay-tab ${activeTab === 'response' ? 'active' : ''}" onclick="updateReplayState('activeTab', 'response')">
|
|
1472
|
+
Response ${response ? `<span style="font-size: 0.8em; opacity: 0.8">(${response.status})</span>` : ''}
|
|
1473
|
+
</div>
|
|
1474
|
+
</div>
|
|
1475
|
+
|
|
1476
|
+
<div class="replay-content">
|
|
1477
|
+
${activeTab === 'body' ? `
|
|
1478
|
+
<textarea class="code-editor" spellcheck="false" oninput="updateReplayState('body', this.value)">${escapeHtml(body)}</textarea>
|
|
1479
|
+
` : ''}
|
|
1480
|
+
|
|
1481
|
+
${activeTab === 'headers' ? `
|
|
1482
|
+
<div id="replay-headers-list">
|
|
1483
|
+
${headers.map((h, i) => `
|
|
1484
|
+
<div class="kv-editor-row">
|
|
1485
|
+
<input class="kv-key" value="${escapeHtml(h.key)}" oninput="updateReplayHeader(${i}, 'key', this.value)" placeholder="Key">
|
|
1486
|
+
<input class="kv-val" value="${escapeHtml(h.value)}" oninput="updateReplayHeader(${i}, 'value', this.value)" placeholder="Value">
|
|
1487
|
+
<div class="kv-remove" onclick="removeReplayHeader(${i})">✕</div>
|
|
1488
|
+
</div>
|
|
1489
|
+
`).join('')}
|
|
1490
|
+
</div>
|
|
1491
|
+
<button class="replay-btn btn-secondary" style="margin-top: 1rem" onclick="addReplayHeader()">+ Add Header</button>
|
|
1492
|
+
` : ''}
|
|
1493
|
+
|
|
1494
|
+
${activeTab === 'response' ? renderReplayResponsePlaceholder(response) : ''}
|
|
1495
|
+
</div>
|
|
1496
|
+
</div>
|
|
1497
|
+
`;
|
|
1498
|
+
|
|
1499
|
+
if (activeTab === 'response' && response) {
|
|
1500
|
+
setTimeout(() => {
|
|
1501
|
+
const el = document.getElementById('replay-response-editor');
|
|
1502
|
+
if (el) {
|
|
1503
|
+
let content = response.body || '';
|
|
1504
|
+
if (typeof content === 'object') content = JSON.stringify(content, null, 2);
|
|
1505
|
+
|
|
1506
|
+
let lang = 'json'; // default
|
|
1507
|
+
// try to sniff
|
|
1508
|
+
if (typeof content === 'string' && !content.trim().startsWith('{') && !content.trim().startsWith('[')) {
|
|
1509
|
+
lang = 'plaintext';
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
renderMonacoEditor(el, content, lang, true);
|
|
1513
|
+
}
|
|
1514
|
+
}, 0);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function renderReplayResponsePlaceholder(response) {
|
|
1519
|
+
if (!response) return `<div style="color: var(--text-secondary); text-align: center; margin-top: 2rem;">No response yet. Click Send to replay.</div>`;
|
|
1520
|
+
|
|
1521
|
+
let colorClass = response.status >= 500 ? 'status-5xx' : response.status >= 400 ? 'status-4xx' : 'status-2xx';
|
|
1522
|
+
|
|
1523
|
+
return `
|
|
1524
|
+
<div style="margin-bottom: 1rem; display: flex; gap: 1rem; align-items: center;">
|
|
1525
|
+
<span class="response-status-badge ${colorClass}">${response.status} ${response.statusText || ''}</span>
|
|
1526
|
+
<span style="color: var(--text-secondary)">${formatBytes(response.size || 0)}</span>
|
|
1527
|
+
<span style="color: var(--text-secondary)">${response.duration || 0}ms</span>
|
|
1528
|
+
<div style="flex:1"></div>
|
|
1529
|
+
<button class="replay-btn btn-secondary" onclick="copyToClipboard(currentReplayState.responseBodyStr)">Copy</button>
|
|
1530
|
+
</div>
|
|
1531
|
+
<div style="border: 1px solid var(--border-color); border-radius: 4px; overflow: hidden; display: flex; flex-direction: column; height: calc(100% - 40px)">
|
|
1532
|
+
<div id="replay-response-editor" style="flex: 1;"></div>
|
|
1533
|
+
</div>
|
|
1534
|
+
`;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function updateReplayState(key, value) {
|
|
1538
|
+
currentReplayState[key] = value;
|
|
1539
|
+
if (key === 'activeTab') renderReplayModal(); // Re-render for tab switch
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function updateReplayHeader(index, field, value) {
|
|
1543
|
+
currentReplayState.headers[index][field] = value;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function addReplayHeader() {
|
|
1547
|
+
currentReplayState.headers.push({ key: '', value: '' });
|
|
1548
|
+
renderReplayModal();
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
function removeReplayHeader(index) {
|
|
1552
|
+
currentReplayState.headers.splice(index, 1);
|
|
1553
|
+
renderReplayModal();
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function executeReplay() {
|
|
1557
|
+
const { method, url, headers, body, direction } = currentReplayState;
|
|
1558
|
+
|
|
1559
|
+
// Construct headers object
|
|
1560
|
+
const headersObj = {};
|
|
1561
|
+
headers.forEach(h => {
|
|
1562
|
+
if (h.key) headersObj[h.key] = h.value;
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// Parse body if JSON
|
|
1566
|
+
let bodyData = body;
|
|
1567
|
+
try {
|
|
1568
|
+
bodyData = JSON.parse(body);
|
|
1569
|
+
} catch (e) {
|
|
1570
|
+
// Keep as string
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// Using dashboard replay endpoint
|
|
1574
|
+
const basePath = window.location.pathname.endsWith('/') ? window.location.pathname.slice(0, -1) : window.location.pathname;
|
|
1575
|
+
|
|
1576
|
+
// Show loading?
|
|
1577
|
+
const btn = document.querySelector('.replay-toolbar .btn-primary');
|
|
1578
|
+
if (btn) btn.innerText = 'Sending...';
|
|
1579
|
+
|
|
1580
|
+
console.log('[Dashboard] Replaying request:', { method, url, direction });
|
|
1581
|
+
|
|
1582
|
+
fetch(basePath + '/replay', {
|
|
1583
|
+
method: 'POST',
|
|
1584
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1585
|
+
body: JSON.stringify({
|
|
1586
|
+
method,
|
|
1587
|
+
url,
|
|
1588
|
+
headers: headersObj,
|
|
1589
|
+
body: bodyData,
|
|
1590
|
+
direction: direction || 'outbound'
|
|
1591
|
+
})
|
|
1592
|
+
})
|
|
1593
|
+
.then(res => res.json())
|
|
1594
|
+
.then(result => {
|
|
1595
|
+
console.log('[Dashboard] Replay result:', result);
|
|
1596
|
+
if (result.error) {
|
|
1597
|
+
alert("Error: " + result.error);
|
|
1598
|
+
} else {
|
|
1599
|
+
let bodyStr = result.data;
|
|
1600
|
+
if (typeof bodyStr === 'object') bodyStr = JSON.stringify(bodyStr, null, 2);
|
|
1601
|
+
|
|
1602
|
+
currentReplayState.response = {
|
|
1603
|
+
status: result.status,
|
|
1604
|
+
headers: result.headers,
|
|
1605
|
+
body: result.data,
|
|
1606
|
+
duration: result.duration,
|
|
1607
|
+
size: bodyStr ? bodyStr.length : 0
|
|
1608
|
+
};
|
|
1609
|
+
currentReplayState.responseBodyStr = bodyStr; // Store for copy
|
|
1610
|
+
currentReplayState.activeTab = 'response';
|
|
1611
|
+
renderReplayModal();
|
|
1612
|
+
}
|
|
1613
|
+
})
|
|
1614
|
+
.catch(err => {
|
|
1615
|
+
console.error('[Dashboard] Replay failed:', err);
|
|
1616
|
+
alert("Replay failed: " + err);
|
|
1617
|
+
})
|
|
1618
|
+
.finally(() => {
|
|
1619
|
+
if (btn) btn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg> Send`;
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
function copyReplayCurl() {
|
|
1624
|
+
// Generate Curl
|
|
1625
|
+
const { method, url, headers, body } = currentReplayState;
|
|
1626
|
+
let cmd = `curl -X ${method} "${url}"`;
|
|
1627
|
+
headers.forEach(h => {
|
|
1628
|
+
if (h.key) cmd += ` \\\n -H "${h.key}: ${h.value}"`;
|
|
1629
|
+
});
|
|
1630
|
+
if (body) {
|
|
1631
|
+
// Escape body for shell
|
|
1632
|
+
const escaped = body.replace(/"/g, '\\"');
|
|
1633
|
+
cmd += ` \\\n -d "${escaped}"`;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
copyToClipboard(cmd);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function handleReplayImport(input) {
|
|
1640
|
+
const file = input.files[0];
|
|
1641
|
+
if (!file) return;
|
|
1642
|
+
|
|
1643
|
+
const reader = new FileReader();
|
|
1644
|
+
reader.onload = (e) => {
|
|
1645
|
+
try {
|
|
1646
|
+
const data = JSON.parse(e.target.result);
|
|
1647
|
+
// Try to map HAR or simple JSON
|
|
1648
|
+
if (data.log && data.log.entries) {
|
|
1649
|
+
// HAR
|
|
1650
|
+
const entry = data.log.entries[0];
|
|
1651
|
+
if (entry && entry.request) {
|
|
1652
|
+
currentReplayState.method = entry.request.method;
|
|
1653
|
+
currentReplayState.url = entry.request.url;
|
|
1654
|
+
currentReplayState.headers = entry.request.headers.map(h => ({ key: h.name, value: h.value }));
|
|
1655
|
+
if (entry.request.postData && entry.request.postData.text) {
|
|
1656
|
+
currentReplayState.body = entry.request.postData.text;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
// Simple format
|
|
1661
|
+
currentReplayState.method = data.method || 'GET';
|
|
1662
|
+
currentReplayState.url = data.url || '';
|
|
1663
|
+
if (data.headers) {
|
|
1664
|
+
if (Array.isArray(data.headers)) currentReplayState.headers = data.headers;
|
|
1665
|
+
else currentReplayState.headers = Object.entries(data.headers).map(([k, v]) => ({ key: k, value: v }));
|
|
1666
|
+
}
|
|
1667
|
+
if (data.body) {
|
|
1668
|
+
currentReplayState.body = typeof data.body === 'string' ? data.body : JSON.stringify(data.body, null, 2);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
renderReplayModal();
|
|
1672
|
+
} catch (err) {
|
|
1673
|
+
alert("Failed to parse file: " + err.message);
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
reader.readAsText(file);
|
|
1677
|
+
input.value = ''; // Reset
|
|
1678
|
+
}
|