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.
@@ -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
- transformIndexHtml(html) {
39
- const script = `
40
- <script type="module">
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
- const containerId = '__network-terminal-root__';
46
- let container = document.getElementById(containerId);
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
- if (!container) {
49
- container = document.createElement('div');
50
- container.id = containerId;
51
- document.body.appendChild(container);
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
- const root = ReactDOM.createRoot(container);
55
- root.render(
56
- React.createElement(NetworkTerminal, {
57
- position: '${position}',
58
- maxLogs: ${maxLogs},
59
- height: '${height}',
60
- zIndex: ${zIndex},
61
- defaultVisible: false,
62
- })
63
- );
64
- </script>`;
65
- return html.replace("</body>", `${script}</body>`);
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
  }
@@ -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 transformIndexHtml(html) {\n const script = `\n<script type=\"module\">\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { NetworkTerminal } from 'network-terminal';\n\nconst containerId = '__network-terminal-root__';\nlet container = document.getElementById(containerId);\n\nif (!container) {\n container = document.createElement('div');\n container.id = containerId;\n document.body.appendChild(container);\n}\n\nconst root = ReactDOM.createRoot(container);\nroot.render(\n React.createElement(NetworkTerminal, {\n position: '${position}',\n maxLogs: ${maxLogs},\n height: '${height}',\n zIndex: ${zIndex},\n defaultVisible: false,\n })\n);\n</script>`;\n\n // Inject before closing body tag\n return html.replace('</body>', `${script}</body>`);\n },\n };\n}\n\nexport default networkTerminal;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,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,IACP,mBAAmB,MAAM;AACvB,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAkBJ,QAAQ;AAAA,eACV,OAAO;AAAA,eACP,MAAM;AAAA,cACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAOd,aAAO,KAAK,QAAQ,WAAW,GAAG,MAAM,SAAS;AAAA,IACnD;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
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":[]}
@@ -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
- transformIndexHtml(html) {
14
- const script = `
15
- <script type="module">
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
- const containerId = '__network-terminal-root__';
21
- let container = document.getElementById(containerId);
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
- if (!container) {
24
- container = document.createElement('div');
25
- container.id = containerId;
26
- document.body.appendChild(container);
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
- const root = ReactDOM.createRoot(container);
30
- root.render(
31
- React.createElement(NetworkTerminal, {
32
- position: '${position}',
33
- maxLogs: ${maxLogs},
34
- height: '${height}',
35
- zIndex: ${zIndex},
36
- defaultVisible: false,
37
- })
38
- );
39
- </script>`;
40
- return html.replace("</body>", `${script}</body>`);
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
  }
@@ -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 transformIndexHtml(html) {\n const script = `\n<script type=\"module\">\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { NetworkTerminal } from 'network-terminal';\n\nconst containerId = '__network-terminal-root__';\nlet container = document.getElementById(containerId);\n\nif (!container) {\n container = document.createElement('div');\n container.id = containerId;\n document.body.appendChild(container);\n}\n\nconst root = ReactDOM.createRoot(container);\nroot.render(\n React.createElement(NetworkTerminal, {\n position: '${position}',\n maxLogs: ${maxLogs},\n height: '${height}',\n zIndex: ${zIndex},\n defaultVisible: false,\n })\n);\n</script>`;\n\n // Inject before closing body tag\n return html.replace('</body>', `${script}</body>`);\n },\n };\n}\n\nexport default networkTerminal;\n"],"mappings":";AAaO,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,IACP,mBAAmB,MAAM;AACvB,YAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAkBJ,QAAQ;AAAA,eACV,OAAO;AAAA,eACP,MAAM;AAAA,cACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAOd,aAAO,KAAK,QAAQ,WAAW,GAAG,MAAM,SAAS;AAAA,IACnD;AAAA,EACF;AACF;AAEA,IAAO,sBAAQ;","names":[]}
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.1",
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
+ };