network-terminal 1.0.1 → 1.0.3
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/bin/cli.js +40 -0
- package/dist/index.js +48 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +48 -21
- package/dist/index.mjs.map +1 -1
- package/dist/vite-plugin.js +48 -21
- package/dist/vite-plugin.js.map +1 -1
- package/dist/vite-plugin.mjs +48 -21
- package/dist/vite-plugin.mjs.map +1 -1
- package/package.json +19 -6
- package/src/components/LogEntry.tsx +121 -0
- package/src/components/NetworkTerminal.tsx +219 -0
- package/src/components/Terminal.tsx +86 -0
- package/src/components/TerminalHeader.tsx +93 -0
- package/src/hooks/useNetworkInterceptor.ts +190 -0
- package/src/index.ts +17 -0
- package/src/types.ts +50 -0
- package/src/utils/colors.ts +18 -0
- package/src/utils/formatters.ts +26 -0
- package/src/vite-plugin.ts +88 -0
- package/standalone/index.html +25 -0
- package/standalone/main.tsx +216 -0
- package/standalone/vite.config.js +12 -0
package/dist/vite-plugin.js
CHANGED
|
@@ -24,6 +24,8 @@ __export(vite_plugin_exports, {
|
|
|
24
24
|
networkTerminal: () => networkTerminal
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(vite_plugin_exports);
|
|
27
|
+
var VIRTUAL_MODULE_ID = "virtual:network-terminal";
|
|
28
|
+
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
27
29
|
function networkTerminal(options = {}) {
|
|
28
30
|
const {
|
|
29
31
|
position = "bottom",
|
|
@@ -35,34 +37,59 @@ function networkTerminal(options = {}) {
|
|
|
35
37
|
name: "vite-plugin-network-terminal",
|
|
36
38
|
apply: "serve",
|
|
37
39
|
// Only apply during development
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
resolveId(id) {
|
|
41
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
42
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
load(id) {
|
|
46
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
47
|
+
return `
|
|
41
48
|
import React from 'react';
|
|
42
49
|
import ReactDOM from 'react-dom/client';
|
|
43
50
|
import { NetworkTerminal } from 'network-terminal';
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
function initNetworkTerminal() {
|
|
53
|
+
const containerId = '__network-terminal-root__';
|
|
54
|
+
let container = document.getElementById(containerId);
|
|
55
|
+
|
|
56
|
+
if (!container) {
|
|
57
|
+
container = document.createElement('div');
|
|
58
|
+
container.id = containerId;
|
|
59
|
+
document.body.appendChild(container);
|
|
60
|
+
}
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
const root = ReactDOM.createRoot(container);
|
|
63
|
+
root.render(
|
|
64
|
+
React.createElement(NetworkTerminal, {
|
|
65
|
+
position: '${position}',
|
|
66
|
+
maxLogs: ${maxLogs},
|
|
67
|
+
height: '${height}',
|
|
68
|
+
zIndex: ${zIndex},
|
|
69
|
+
defaultVisible: false,
|
|
70
|
+
})
|
|
71
|
+
);
|
|
52
72
|
}
|
|
53
73
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
if (document.readyState === 'loading') {
|
|
75
|
+
document.addEventListener('DOMContentLoaded', initNetworkTerminal);
|
|
76
|
+
} else {
|
|
77
|
+
initNetworkTerminal();
|
|
78
|
+
}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
transformIndexHtml(html) {
|
|
83
|
+
return {
|
|
84
|
+
html,
|
|
85
|
+
tags: [
|
|
86
|
+
{
|
|
87
|
+
tag: "script",
|
|
88
|
+
attrs: { type: "module", src: "/@id/__x00__virtual:network-terminal" },
|
|
89
|
+
injectTo: "body"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
};
|
|
66
93
|
}
|
|
67
94
|
};
|
|
68
95
|
}
|
package/dist/vite-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nexport interface NetworkTerminalPluginOptions {\n /** Position of the terminal: 'top' or 'bottom' (default: 'bottom') */\n position?: 'top' | 'bottom';\n /** Maximum number of logs to keep (default: 100) */\n maxLogs?: number;\n /** Terminal height (default: '450px') */\n height?: string;\n /** CSS z-index (default: 9999) */\n zIndex?: number;\n}\n\nexport function networkTerminal(options: NetworkTerminalPluginOptions = {}): Plugin {\n const {\n position = 'bottom',\n maxLogs = 100,\n height = '450px',\n zIndex = 9999,\n } = options;\n\n return {\n name: 'vite-plugin-network-terminal',\n apply: 'serve', // Only apply during development\n
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nexport interface NetworkTerminalPluginOptions {\n /** Position of the terminal: 'top' or 'bottom' (default: 'bottom') */\n position?: 'top' | 'bottom';\n /** Maximum number of logs to keep (default: 100) */\n maxLogs?: number;\n /** Terminal height (default: '450px') */\n height?: string;\n /** CSS z-index (default: 9999) */\n zIndex?: number;\n}\n\nconst VIRTUAL_MODULE_ID = 'virtual:network-terminal';\nconst RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID;\n\nexport function networkTerminal(options: NetworkTerminalPluginOptions = {}): Plugin {\n const {\n position = 'bottom',\n maxLogs = 100,\n height = '450px',\n zIndex = 9999,\n } = options;\n\n return {\n name: 'vite-plugin-network-terminal',\n apply: 'serve', // Only apply during development\n\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\n return `\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { NetworkTerminal } from 'network-terminal';\n\nfunction initNetworkTerminal() {\n const containerId = '__network-terminal-root__';\n let container = document.getElementById(containerId);\n\n if (!container) {\n container = document.createElement('div');\n container.id = containerId;\n document.body.appendChild(container);\n }\n\n const root = ReactDOM.createRoot(container);\n root.render(\n React.createElement(NetworkTerminal, {\n position: '${position}',\n maxLogs: ${maxLogs},\n height: '${height}',\n zIndex: ${zIndex},\n defaultVisible: false,\n })\n );\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initNetworkTerminal);\n} else {\n initNetworkTerminal();\n}\n`;\n }\n },\n\n transformIndexHtml(html) {\n return {\n html,\n tags: [\n {\n tag: 'script',\n attrs: { type: 'module', src: '/@id/__x00__virtual:network-terminal' },\n injectTo: 'body',\n },\n ],\n };\n },\n };\n}\n\nexport default networkTerminal;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,IAAM,oBAAoB;AAC1B,IAAM,6BAA6B,OAAO;AAEnC,SAAS,gBAAgB,UAAwC,CAAC,GAAW;AAClF,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT,SAAS;AAAA,EACX,IAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,IAEP,UAAU,IAAI;AACZ,UAAI,OAAO,mBAAmB;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,KAAK,IAAI;AACP,UAAI,OAAO,4BAA4B;AACrC,eAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAkBI,QAAQ;AAAA,iBACV,OAAO;AAAA,iBACP,MAAM;AAAA,gBACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYhB;AAAA,IACF;AAAA,IAEA,mBAAmB,MAAM;AACvB,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,UACJ;AAAA,YACE,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,UAAU,KAAK,uCAAuC;AAAA,YACrE,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// src/vite-plugin.ts
|
|
2
|
+
var VIRTUAL_MODULE_ID = "virtual:network-terminal";
|
|
3
|
+
var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
|
|
2
4
|
function networkTerminal(options = {}) {
|
|
3
5
|
const {
|
|
4
6
|
position = "bottom",
|
|
@@ -10,34 +12,59 @@ function networkTerminal(options = {}) {
|
|
|
10
12
|
name: "vite-plugin-network-terminal",
|
|
11
13
|
apply: "serve",
|
|
12
14
|
// Only apply during development
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
resolveId(id) {
|
|
16
|
+
if (id === VIRTUAL_MODULE_ID) {
|
|
17
|
+
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
load(id) {
|
|
21
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
22
|
+
return `
|
|
16
23
|
import React from 'react';
|
|
17
24
|
import ReactDOM from 'react-dom/client';
|
|
18
25
|
import { NetworkTerminal } from 'network-terminal';
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
function initNetworkTerminal() {
|
|
28
|
+
const containerId = '__network-terminal-root__';
|
|
29
|
+
let container = document.getElementById(containerId);
|
|
30
|
+
|
|
31
|
+
if (!container) {
|
|
32
|
+
container = document.createElement('div');
|
|
33
|
+
container.id = containerId;
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
}
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
const root = ReactDOM.createRoot(container);
|
|
38
|
+
root.render(
|
|
39
|
+
React.createElement(NetworkTerminal, {
|
|
40
|
+
position: '${position}',
|
|
41
|
+
maxLogs: ${maxLogs},
|
|
42
|
+
height: '${height}',
|
|
43
|
+
zIndex: ${zIndex},
|
|
44
|
+
defaultVisible: false,
|
|
45
|
+
})
|
|
46
|
+
);
|
|
27
47
|
}
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
if (document.readyState === 'loading') {
|
|
50
|
+
document.addEventListener('DOMContentLoaded', initNetworkTerminal);
|
|
51
|
+
} else {
|
|
52
|
+
initNetworkTerminal();
|
|
53
|
+
}
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
transformIndexHtml(html) {
|
|
58
|
+
return {
|
|
59
|
+
html,
|
|
60
|
+
tags: [
|
|
61
|
+
{
|
|
62
|
+
tag: "script",
|
|
63
|
+
attrs: { type: "module", src: "/@id/__x00__virtual:network-terminal" },
|
|
64
|
+
injectTo: "body"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
};
|
|
41
68
|
}
|
|
42
69
|
};
|
|
43
70
|
}
|
package/dist/vite-plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nexport interface NetworkTerminalPluginOptions {\n /** Position of the terminal: 'top' or 'bottom' (default: 'bottom') */\n position?: 'top' | 'bottom';\n /** Maximum number of logs to keep (default: 100) */\n maxLogs?: number;\n /** Terminal height (default: '450px') */\n height?: string;\n /** CSS z-index (default: 9999) */\n zIndex?: number;\n}\n\nexport function networkTerminal(options: NetworkTerminalPluginOptions = {}): Plugin {\n const {\n position = 'bottom',\n maxLogs = 100,\n height = '450px',\n zIndex = 9999,\n } = options;\n\n return {\n name: 'vite-plugin-network-terminal',\n apply: 'serve', // Only apply during development\n
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\nexport interface NetworkTerminalPluginOptions {\n /** Position of the terminal: 'top' or 'bottom' (default: 'bottom') */\n position?: 'top' | 'bottom';\n /** Maximum number of logs to keep (default: 100) */\n maxLogs?: number;\n /** Terminal height (default: '450px') */\n height?: string;\n /** CSS z-index (default: 9999) */\n zIndex?: number;\n}\n\nconst VIRTUAL_MODULE_ID = 'virtual:network-terminal';\nconst RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID;\n\nexport function networkTerminal(options: NetworkTerminalPluginOptions = {}): Plugin {\n const {\n position = 'bottom',\n maxLogs = 100,\n height = '450px',\n zIndex = 9999,\n } = options;\n\n return {\n name: 'vite-plugin-network-terminal',\n apply: 'serve', // Only apply during development\n\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) {\n return RESOLVED_VIRTUAL_MODULE_ID;\n }\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_MODULE_ID) {\n return `\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { NetworkTerminal } from 'network-terminal';\n\nfunction initNetworkTerminal() {\n const containerId = '__network-terminal-root__';\n let container = document.getElementById(containerId);\n\n if (!container) {\n container = document.createElement('div');\n container.id = containerId;\n document.body.appendChild(container);\n }\n\n const root = ReactDOM.createRoot(container);\n root.render(\n React.createElement(NetworkTerminal, {\n position: '${position}',\n maxLogs: ${maxLogs},\n height: '${height}',\n zIndex: ${zIndex},\n defaultVisible: false,\n })\n );\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initNetworkTerminal);\n} else {\n initNetworkTerminal();\n}\n`;\n }\n },\n\n transformIndexHtml(html) {\n return {\n html,\n tags: [\n {\n tag: 'script',\n attrs: { type: 'module', src: '/@id/__x00__virtual:network-terminal' },\n injectTo: 'body',\n },\n ],\n };\n },\n };\n}\n\nexport default networkTerminal;\n"],"mappings":";AAaA,IAAM,oBAAoB;AAC1B,IAAM,6BAA6B,OAAO;AAEnC,SAAS,gBAAgB,UAAwC,CAAC,GAAW;AAClF,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,UAAU;AAAA,IACV,SAAS;AAAA,IACT,SAAS;AAAA,EACX,IAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA;AAAA,IAEP,UAAU,IAAI;AACZ,UAAI,OAAO,mBAAmB;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,KAAK,IAAI;AACP,UAAI,OAAO,4BAA4B;AACrC,eAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAkBI,QAAQ;AAAA,iBACV,OAAO;AAAA,iBACP,MAAM;AAAA,gBACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYhB;AAAA,IACF;AAAA,IAEA,mBAAmB,MAAM;AACvB,aAAO;AAAA,QACL;AAAA,QACA,MAAM;AAAA,UACJ;AAAA,YACE,KAAK;AAAA,YACL,OAAO,EAAE,MAAM,UAAU,KAAK,uCAAuC;AAAA,YACrE,UAAU;AAAA,UACZ;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-terminal",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A browser-based terminal UI for monitoring Fetch/XHR requests with real-time display of routes, payloads, and responses",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"network-terminal": "./bin/cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +21,10 @@
|
|
|
18
21
|
}
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
21
|
-
"dist"
|
|
24
|
+
"dist",
|
|
25
|
+
"bin",
|
|
26
|
+
"standalone",
|
|
27
|
+
"src"
|
|
22
28
|
],
|
|
23
29
|
"scripts": {
|
|
24
30
|
"build": "tsup",
|
|
@@ -40,18 +46,25 @@
|
|
|
40
46
|
],
|
|
41
47
|
"author": "",
|
|
42
48
|
"license": "MIT",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
51
|
+
"react": "^18.2.0",
|
|
52
|
+
"react-dom": "^18.2.0",
|
|
53
|
+
"vite": "^5.0.0"
|
|
54
|
+
},
|
|
43
55
|
"peerDependencies": {
|
|
44
56
|
"react": ">=17.0.0",
|
|
45
57
|
"react-dom": ">=17.0.0"
|
|
46
58
|
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react": { "optional": true },
|
|
61
|
+
"react-dom": { "optional": true }
|
|
62
|
+
},
|
|
47
63
|
"devDependencies": {
|
|
48
64
|
"@types/react": "^18.2.0",
|
|
49
65
|
"@types/react-dom": "^18.2.0",
|
|
50
|
-
"react": "^18.2.0",
|
|
51
|
-
"react-dom": "^18.2.0",
|
|
52
66
|
"tsup": "^8.0.0",
|
|
53
|
-
"typescript": "^5.0.0"
|
|
54
|
-
"vite": "^7.3.1"
|
|
67
|
+
"typescript": "^5.0.0"
|
|
55
68
|
},
|
|
56
69
|
"repository": {
|
|
57
70
|
"type": "git",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LogEntryProps } from '../types';
|
|
3
|
+
import { formatJson, formatTime } from '../utils/formatters';
|
|
4
|
+
import { getStatusColor, getMethodColor } from '../utils/colors';
|
|
5
|
+
|
|
6
|
+
const styles = {
|
|
7
|
+
container: {
|
|
8
|
+
marginBottom: '16px',
|
|
9
|
+
borderBottom: '1px solid #1f2937',
|
|
10
|
+
paddingBottom: '16px',
|
|
11
|
+
},
|
|
12
|
+
header: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
gap: '8px',
|
|
16
|
+
marginBottom: '4px',
|
|
17
|
+
flexWrap: 'wrap' as const,
|
|
18
|
+
},
|
|
19
|
+
timestamp: {
|
|
20
|
+
color: '#6b7280',
|
|
21
|
+
fontSize: '12px',
|
|
22
|
+
fontFamily: 'monospace',
|
|
23
|
+
},
|
|
24
|
+
method: {
|
|
25
|
+
fontWeight: 'bold',
|
|
26
|
+
fontFamily: 'monospace',
|
|
27
|
+
},
|
|
28
|
+
status: {
|
|
29
|
+
fontWeight: 'bold',
|
|
30
|
+
fontFamily: 'monospace',
|
|
31
|
+
},
|
|
32
|
+
duration: {
|
|
33
|
+
color: '#6b7280',
|
|
34
|
+
fontSize: '12px',
|
|
35
|
+
fontFamily: 'monospace',
|
|
36
|
+
},
|
|
37
|
+
url: {
|
|
38
|
+
color: '#60a5fa',
|
|
39
|
+
wordBreak: 'break-all' as const,
|
|
40
|
+
marginBottom: '8px',
|
|
41
|
+
fontFamily: 'monospace',
|
|
42
|
+
fontSize: '13px',
|
|
43
|
+
},
|
|
44
|
+
bodyContainer: {
|
|
45
|
+
marginTop: '8px',
|
|
46
|
+
},
|
|
47
|
+
bodyLabel: {
|
|
48
|
+
color: '#a78bfa',
|
|
49
|
+
fontSize: '12px',
|
|
50
|
+
marginBottom: '4px',
|
|
51
|
+
fontFamily: 'monospace',
|
|
52
|
+
},
|
|
53
|
+
pre: {
|
|
54
|
+
backgroundColor: '#1f2937',
|
|
55
|
+
padding: '8px',
|
|
56
|
+
borderRadius: '4px',
|
|
57
|
+
color: '#86efac',
|
|
58
|
+
overflowX: 'auto' as const,
|
|
59
|
+
fontSize: '12px',
|
|
60
|
+
whiteSpace: 'pre-wrap' as const,
|
|
61
|
+
fontFamily: 'monospace',
|
|
62
|
+
maxHeight: '192px',
|
|
63
|
+
overflowY: 'auto' as const,
|
|
64
|
+
margin: 0,
|
|
65
|
+
},
|
|
66
|
+
errorPre: {
|
|
67
|
+
backgroundColor: '#1f2937',
|
|
68
|
+
padding: '8px',
|
|
69
|
+
borderRadius: '4px',
|
|
70
|
+
color: '#fca5a5',
|
|
71
|
+
overflowX: 'auto' as const,
|
|
72
|
+
fontSize: '12px',
|
|
73
|
+
whiteSpace: 'pre-wrap' as const,
|
|
74
|
+
fontFamily: 'monospace',
|
|
75
|
+
margin: 0,
|
|
76
|
+
},
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export const LogEntry: React.FC<LogEntryProps> = ({ log, type }) => {
|
|
80
|
+
return (
|
|
81
|
+
<div style={styles.container}>
|
|
82
|
+
<div style={styles.header}>
|
|
83
|
+
<span style={styles.timestamp}>[{formatTime(log.timestamp)}]</span>
|
|
84
|
+
<span style={{ ...styles.method, color: getMethodColor(log.method) }}>
|
|
85
|
+
{log.method}
|
|
86
|
+
</span>
|
|
87
|
+
{type === 'response' && log.status && (
|
|
88
|
+
<span style={{ ...styles.status, color: getStatusColor(log.status) }}>
|
|
89
|
+
{log.status} {log.statusText}
|
|
90
|
+
</span>
|
|
91
|
+
)}
|
|
92
|
+
{log.duration !== undefined && type === 'response' && (
|
|
93
|
+
<span style={styles.duration}>({log.duration}ms)</span>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div style={styles.url}>{log.url}</div>
|
|
98
|
+
|
|
99
|
+
{type === 'request' ? (
|
|
100
|
+
log.requestBody ? (
|
|
101
|
+
<div style={styles.bodyContainer}>
|
|
102
|
+
<div style={styles.bodyLabel}>{'\u25B8'} Payload:</div>
|
|
103
|
+
<pre style={styles.pre}>{formatJson(log.requestBody)}</pre>
|
|
104
|
+
</div>
|
|
105
|
+
) : null
|
|
106
|
+
) : log.error ? (
|
|
107
|
+
<div style={styles.bodyContainer}>
|
|
108
|
+
<div style={{ ...styles.bodyLabel, color: '#f87171' }}>
|
|
109
|
+
{'\u25B8'} Error:
|
|
110
|
+
</div>
|
|
111
|
+
<pre style={styles.errorPre}>{log.error}</pre>
|
|
112
|
+
</div>
|
|
113
|
+
) : log.responseBody ? (
|
|
114
|
+
<div style={styles.bodyContainer}>
|
|
115
|
+
<div style={styles.bodyLabel}>{'\u25B8'} Response:</div>
|
|
116
|
+
<pre style={styles.pre}>{formatJson(log.responseBody)}</pre>
|
|
117
|
+
</div>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { NetworkLog, NetworkTerminalProps } from '../types';
|
|
3
|
+
import { Terminal } from './Terminal';
|
|
4
|
+
import { useNetworkInterceptor } from '../hooks/useNetworkInterceptor';
|
|
5
|
+
|
|
6
|
+
const styles = {
|
|
7
|
+
floatingButton: {
|
|
8
|
+
position: 'fixed' as const,
|
|
9
|
+
bottom: '16px',
|
|
10
|
+
right: '16px',
|
|
11
|
+
zIndex: 9999,
|
|
12
|
+
backgroundColor: '#1f2937',
|
|
13
|
+
color: '#4ade80',
|
|
14
|
+
padding: '8px 16px',
|
|
15
|
+
borderRadius: '8px',
|
|
16
|
+
fontFamily: 'monospace',
|
|
17
|
+
fontSize: '14px',
|
|
18
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
19
|
+
border: '1px solid #374151',
|
|
20
|
+
cursor: 'pointer',
|
|
21
|
+
},
|
|
22
|
+
container: {
|
|
23
|
+
position: 'fixed' as const,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
left: 0,
|
|
26
|
+
right: 0,
|
|
27
|
+
zIndex: 9999,
|
|
28
|
+
backgroundColor: '#111827',
|
|
29
|
+
borderTop: '2px solid #22c55e',
|
|
30
|
+
boxShadow: '0 -10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
31
|
+
},
|
|
32
|
+
containerTop: {
|
|
33
|
+
position: 'fixed' as const,
|
|
34
|
+
top: 0,
|
|
35
|
+
left: 0,
|
|
36
|
+
right: 0,
|
|
37
|
+
zIndex: 9999,
|
|
38
|
+
backgroundColor: '#111827',
|
|
39
|
+
borderBottom: '2px solid #22c55e',
|
|
40
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
41
|
+
},
|
|
42
|
+
mainHeader: {
|
|
43
|
+
display: 'flex',
|
|
44
|
+
alignItems: 'center',
|
|
45
|
+
justifyContent: 'space-between',
|
|
46
|
+
padding: '8px 16px',
|
|
47
|
+
backgroundColor: '#1f2937',
|
|
48
|
+
borderBottom: '1px solid #374151',
|
|
49
|
+
},
|
|
50
|
+
headerLeft: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
gap: '12px',
|
|
54
|
+
},
|
|
55
|
+
title: {
|
|
56
|
+
color: '#4ade80',
|
|
57
|
+
fontFamily: 'monospace',
|
|
58
|
+
fontWeight: 'bold',
|
|
59
|
+
},
|
|
60
|
+
hint: {
|
|
61
|
+
color: '#6b7280',
|
|
62
|
+
fontFamily: 'monospace',
|
|
63
|
+
fontSize: '12px',
|
|
64
|
+
},
|
|
65
|
+
headerRight: {
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
gap: '8px',
|
|
69
|
+
},
|
|
70
|
+
clearButton: {
|
|
71
|
+
color: '#9ca3af',
|
|
72
|
+
fontSize: '14px',
|
|
73
|
+
fontFamily: 'monospace',
|
|
74
|
+
padding: '4px 12px',
|
|
75
|
+
borderRadius: '4px',
|
|
76
|
+
border: 'none',
|
|
77
|
+
background: 'transparent',
|
|
78
|
+
cursor: 'pointer',
|
|
79
|
+
},
|
|
80
|
+
closeButton: {
|
|
81
|
+
color: '#9ca3af',
|
|
82
|
+
fontSize: '18px',
|
|
83
|
+
fontWeight: 'bold',
|
|
84
|
+
padding: '0 8px',
|
|
85
|
+
border: 'none',
|
|
86
|
+
background: 'transparent',
|
|
87
|
+
cursor: 'pointer',
|
|
88
|
+
borderRadius: '4px',
|
|
89
|
+
},
|
|
90
|
+
terminalsContainer: {
|
|
91
|
+
display: 'flex',
|
|
92
|
+
gap: '8px',
|
|
93
|
+
padding: '8px',
|
|
94
|
+
height: '450px',
|
|
95
|
+
},
|
|
96
|
+
terminalWrapper: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
display: 'flex',
|
|
99
|
+
flexDirection: 'column' as const,
|
|
100
|
+
},
|
|
101
|
+
} as const;
|
|
102
|
+
|
|
103
|
+
export const NetworkTerminal: React.FC<NetworkTerminalProps> = ({
|
|
104
|
+
maxLogs = 100,
|
|
105
|
+
defaultVisible = false,
|
|
106
|
+
position = 'bottom',
|
|
107
|
+
height = '450px',
|
|
108
|
+
zIndex = 9999,
|
|
109
|
+
}) => {
|
|
110
|
+
const [logs, setLogs] = useState<NetworkLog[]>([]);
|
|
111
|
+
const [isVisible, setIsVisible] = useState(defaultVisible);
|
|
112
|
+
const [requestExpanded, setRequestExpanded] = useState(true);
|
|
113
|
+
const [responseExpanded, setResponseExpanded] = useState(true);
|
|
114
|
+
|
|
115
|
+
const addLog = useCallback(
|
|
116
|
+
(log: NetworkLog) => {
|
|
117
|
+
setLogs((prev) => [...prev.slice(-(maxLogs - 1)), log]);
|
|
118
|
+
},
|
|
119
|
+
[maxLogs]
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const updateLog = useCallback((id: string, updates: Partial<NetworkLog>) => {
|
|
123
|
+
setLogs((prev) =>
|
|
124
|
+
prev.map((log) => (log.id === id ? { ...log, ...updates } : log))
|
|
125
|
+
);
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
useNetworkInterceptor({
|
|
129
|
+
enabled: isVisible,
|
|
130
|
+
onLogAdd: addLog,
|
|
131
|
+
onLogUpdate: updateLog,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
136
|
+
if (e.ctrlKey && e.shiftKey && e.key === 'N') {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
setIsVisible((prev) => !prev);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
143
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const clearLogs = () => setLogs([]);
|
|
147
|
+
|
|
148
|
+
if (!isVisible) {
|
|
149
|
+
return (
|
|
150
|
+
<button
|
|
151
|
+
onClick={() => setIsVisible(true)}
|
|
152
|
+
style={{
|
|
153
|
+
...styles.floatingButton,
|
|
154
|
+
zIndex,
|
|
155
|
+
}}
|
|
156
|
+
title="Open Network Terminal (Ctrl+Shift+N)"
|
|
157
|
+
>
|
|
158
|
+
{'>'}_ Network
|
|
159
|
+
</button>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const containerStyle =
|
|
164
|
+
position === 'top'
|
|
165
|
+
? { ...styles.containerTop, zIndex }
|
|
166
|
+
: { ...styles.container, zIndex };
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div style={containerStyle}>
|
|
170
|
+
<div style={styles.mainHeader}>
|
|
171
|
+
<div style={styles.headerLeft}>
|
|
172
|
+
<span style={styles.title}>{'>'}_ Network Terminal</span>
|
|
173
|
+
<span style={styles.hint}>Press Ctrl+Shift+N to toggle</span>
|
|
174
|
+
</div>
|
|
175
|
+
<div style={styles.headerRight}>
|
|
176
|
+
<button
|
|
177
|
+
onClick={clearLogs}
|
|
178
|
+
style={styles.clearButton}
|
|
179
|
+
onMouseEnter={(e) => (e.currentTarget.style.color = '#fff')}
|
|
180
|
+
onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
|
|
181
|
+
>
|
|
182
|
+
Clear All
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={() => setIsVisible(false)}
|
|
186
|
+
style={styles.closeButton}
|
|
187
|
+
onMouseEnter={(e) => (e.currentTarget.style.color = '#f87171')}
|
|
188
|
+
onMouseLeave={(e) => (e.currentTarget.style.color = '#9ca3af')}
|
|
189
|
+
>
|
|
190
|
+
{'\u00D7'}
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div style={{ ...styles.terminalsContainer, height }}>
|
|
196
|
+
<div style={styles.terminalWrapper}>
|
|
197
|
+
<Terminal
|
|
198
|
+
title="Requests"
|
|
199
|
+
logs={logs}
|
|
200
|
+
type="request"
|
|
201
|
+
onClear={clearLogs}
|
|
202
|
+
expanded={requestExpanded}
|
|
203
|
+
onToggleExpand={() => setRequestExpanded(!requestExpanded)}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
<div style={styles.terminalWrapper}>
|
|
207
|
+
<Terminal
|
|
208
|
+
title="Responses"
|
|
209
|
+
logs={logs}
|
|
210
|
+
type="response"
|
|
211
|
+
onClear={clearLogs}
|
|
212
|
+
expanded={responseExpanded}
|
|
213
|
+
onToggleExpand={() => setResponseExpanded(!responseExpanded)}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
};
|