@rozenite/network-activity-plugin 1.0.0-alpha.5 → 1.0.0-alpha.6
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/{panel.html → App.html} +3 -3
- package/dist/assets/App-CIflVb88.js +24164 -0
- package/dist/assets/App-Czu6Vt2P.css +1233 -0
- package/dist/react-native.cjs +1 -1
- package/dist/react-native.d.ts +1 -90
- package/dist/rozenite.config.d.ts +7 -0
- package/dist/rozenite.json +1 -1
- package/dist/src/react-native/network-inspector.d.ts +8 -0
- package/dist/src/react-native/network-requests-registry.d.ts +6 -0
- package/dist/src/react-native/useNetworkActivityDevTools.d.ts +2 -0
- package/dist/src/react-native/xhr-interceptor.d.ts +38 -0
- package/dist/src/shared/client.d.ts +64 -0
- package/dist/src/ui/App.d.ts +1 -0
- package/dist/src/ui/components/Badge.d.ts +9 -0
- package/dist/src/ui/components/Button.d.ts +11 -0
- package/dist/src/ui/components/Input.d.ts +3 -0
- package/dist/src/ui/components/JsonTree.d.ts +5 -0
- package/dist/src/ui/components/RequestList.d.ts +45 -0
- package/dist/src/ui/components/ScrollArea.d.ts +4 -0
- package/dist/src/ui/components/Separator.d.ts +3 -0
- package/dist/src/ui/tabs/CookiesTab.d.ts +8 -0
- package/dist/src/ui/tabs/HeadersTab.d.ts +17 -0
- package/dist/src/ui/tabs/RequestTab.d.ts +10 -0
- package/dist/src/ui/tabs/ResponseTab.d.ts +12 -0
- package/dist/src/ui/tabs/TimingTab.d.ts +7 -0
- package/dist/src/ui/types.d.ts +23 -0
- package/dist/src/ui/utils.d.ts +2 -0
- package/dist/src/ui/views/InspectorView.d.ts +5 -0
- package/dist/src/ui/views/LoadingView.d.ts +1 -0
- package/dist/useNetworkActivityDevTools.cjs +360 -0
- package/dist/useNetworkActivityDevTools.js +108 -236
- package/package.json +23 -16
- package/postcss.config.js +6 -0
- package/rozenite.config.ts +1 -1
- package/src/react-native/network-inspector.ts +113 -260
- package/src/react-native/network-requests-registry.ts +7 -77
- package/src/react-native/useNetworkActivityDevTools.ts +1 -1
- package/src/react-native/xhr-interceptor.ts +2 -2
- package/src/react-native/xml-request.d.ts +11 -1
- package/src/shared/client.ts +80 -0
- package/src/ui/App.tsx +19 -0
- package/src/ui/components/Badge.tsx +36 -0
- package/src/ui/components/Button.tsx +56 -0
- package/src/ui/components/Input.tsx +22 -0
- package/src/ui/components/JsonTree.tsx +37 -0
- package/src/ui/components/RequestList.tsx +376 -0
- package/src/ui/components/ScrollArea.tsx +48 -0
- package/src/ui/components/Separator.tsx +31 -0
- package/src/ui/components/Tabs.tsx +55 -0
- package/src/ui/globals.css +90 -0
- package/src/ui/tabs/CookiesTab.tsx +290 -0
- package/src/ui/tabs/HeadersTab.tsx +117 -0
- package/src/ui/tabs/RequestTab.tsx +72 -0
- package/src/ui/tabs/ResponseTab.tsx +140 -0
- package/src/ui/tabs/TimingTab.tsx +71 -0
- package/src/ui/types.ts +30 -0
- package/src/ui/utils.ts +5 -97
- package/src/ui/views/InspectorView.tsx +349 -0
- package/src/ui/views/LoadingView.tsx +19 -0
- package/tailwind.config.ts +93 -0
- package/dist/assets/panel-BNxB_KsS.js +0 -16663
- package/dist/assets/panel-DXGMsavf.css +0 -555
- package/src/types/client.ts +0 -111
- package/src/types/network.ts +0 -32
- package/src/ui/components.module.css +0 -158
- package/src/ui/components.tsx +0 -241
- package/src/ui/network-details.module.css +0 -197
- package/src/ui/network-details.tsx +0 -345
- package/src/ui/network-list.module.css +0 -128
- package/src/ui/network-list.tsx +0 -240
- package/src/ui/network-toolbar.module.css +0 -9
- package/src/ui/network-toolbar.tsx +0 -34
- package/src/ui/panel.module.css +0 -67
- package/src/ui/panel.tsx +0 -318
- package/src/ui/tanstack-query.tsx +0 -204
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer utilities {
|
|
6
|
+
.text-balance {
|
|
7
|
+
text-wrap: balance;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@layer base {
|
|
12
|
+
:root {
|
|
13
|
+
--background: 0 0% 100%;
|
|
14
|
+
--foreground: 0 0% 3.9%;
|
|
15
|
+
--card: 0 0% 100%;
|
|
16
|
+
--card-foreground: 0 0% 3.9%;
|
|
17
|
+
--popover: 0 0% 100%;
|
|
18
|
+
--popover-foreground: 0 0% 3.9%;
|
|
19
|
+
--primary: 0 0% 9%;
|
|
20
|
+
--primary-foreground: 0 0% 98%;
|
|
21
|
+
--secondary: 0 0% 96.1%;
|
|
22
|
+
--secondary-foreground: 0 0% 9%;
|
|
23
|
+
--muted: 0 0% 96.1%;
|
|
24
|
+
--muted-foreground: 0 0% 45.1%;
|
|
25
|
+
--accent: 0 0% 96.1%;
|
|
26
|
+
--accent-foreground: 0 0% 9%;
|
|
27
|
+
--destructive: 0 84.2% 60.2%;
|
|
28
|
+
--destructive-foreground: 0 0% 98%;
|
|
29
|
+
--border: 0 0% 89.8%;
|
|
30
|
+
--input: 0 0% 89.8%;
|
|
31
|
+
--ring: 0 0% 3.9%;
|
|
32
|
+
--chart-1: 12 76% 61%;
|
|
33
|
+
--chart-2: 173 58% 39%;
|
|
34
|
+
--chart-3: 197 37% 24%;
|
|
35
|
+
--chart-4: 43 74% 66%;
|
|
36
|
+
--chart-5: 27 87% 67%;
|
|
37
|
+
--radius: 0.5rem;
|
|
38
|
+
--sidebar-background: 0 0% 98%;
|
|
39
|
+
--sidebar-foreground: 240 5.3% 26.1%;
|
|
40
|
+
--sidebar-primary: 240 5.9% 10%;
|
|
41
|
+
--sidebar-primary-foreground: 0 0% 98%;
|
|
42
|
+
--sidebar-accent: 240 4.8% 95.9%;
|
|
43
|
+
--sidebar-accent-foreground: 240 5.9% 10%;
|
|
44
|
+
--sidebar-border: 220 13% 91%;
|
|
45
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
46
|
+
}
|
|
47
|
+
.dark {
|
|
48
|
+
--background: 0 0% 3.9%;
|
|
49
|
+
--foreground: 0 0% 98%;
|
|
50
|
+
--card: 0 0% 3.9%;
|
|
51
|
+
--card-foreground: 0 0% 98%;
|
|
52
|
+
--popover: 0 0% 3.9%;
|
|
53
|
+
--popover-foreground: 0 0% 98%;
|
|
54
|
+
--primary: 0 0% 98%;
|
|
55
|
+
--primary-foreground: 0 0% 9%;
|
|
56
|
+
--secondary: 0 0% 14.9%;
|
|
57
|
+
--secondary-foreground: 0 0% 98%;
|
|
58
|
+
--muted: 0 0% 14.9%;
|
|
59
|
+
--muted-foreground: 0 0% 63.9%;
|
|
60
|
+
--accent: 0 0% 14.9%;
|
|
61
|
+
--accent-foreground: 0 0% 98%;
|
|
62
|
+
--destructive: 0 62.8% 30.6%;
|
|
63
|
+
--destructive-foreground: 0 0% 98%;
|
|
64
|
+
--border: 0 0% 14.9%;
|
|
65
|
+
--input: 0 0% 14.9%;
|
|
66
|
+
--ring: 0 0% 83.1%;
|
|
67
|
+
--chart-1: 220 70% 50%;
|
|
68
|
+
--chart-2: 160 60% 45%;
|
|
69
|
+
--chart-3: 30 80% 55%;
|
|
70
|
+
--chart-4: 280 65% 60%;
|
|
71
|
+
--chart-5: 340 75% 55%;
|
|
72
|
+
--sidebar-background: 240 5.9% 10%;
|
|
73
|
+
--sidebar-foreground: 240 4.8% 95.9%;
|
|
74
|
+
--sidebar-primary: 224.3 76.3% 48%;
|
|
75
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
76
|
+
--sidebar-accent: 240 3.7% 15.9%;
|
|
77
|
+
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
78
|
+
--sidebar-border: 240 3.7% 15.9%;
|
|
79
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@layer base {
|
|
84
|
+
* {
|
|
85
|
+
@apply border-border;
|
|
86
|
+
}
|
|
87
|
+
body {
|
|
88
|
+
@apply bg-background text-foreground;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
+
import { Badge } from '../components/Badge';
|
|
3
|
+
import { NetworkEntry } from '../types';
|
|
4
|
+
|
|
5
|
+
type Cookie = {
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
domain?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
expires?: string;
|
|
11
|
+
maxAge?: string;
|
|
12
|
+
secure?: boolean;
|
|
13
|
+
httpOnly?: boolean;
|
|
14
|
+
sameSite?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type CookiesTabProps = {
|
|
18
|
+
selectedRequest: {
|
|
19
|
+
id: string;
|
|
20
|
+
};
|
|
21
|
+
networkEntries: Map<string, NetworkEntry>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const parseCookieString = (cookieString: string): Cookie[] => {
|
|
25
|
+
if (!cookieString) return [];
|
|
26
|
+
|
|
27
|
+
return cookieString
|
|
28
|
+
.split(';')
|
|
29
|
+
.map((cookieStr) => {
|
|
30
|
+
const [nameValue, ...attributes] = cookieStr.trim().split(';');
|
|
31
|
+
const [name, value] = nameValue.split('=');
|
|
32
|
+
|
|
33
|
+
const cookieObj: Cookie = {
|
|
34
|
+
name: name?.trim() || '',
|
|
35
|
+
value: value?.trim() || '',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Parse attributes
|
|
39
|
+
attributes.forEach((attr) => {
|
|
40
|
+
const [attrName, attrValue] = attr.trim().split('=');
|
|
41
|
+
const lowerAttrName = attrName.toLowerCase();
|
|
42
|
+
|
|
43
|
+
switch (lowerAttrName) {
|
|
44
|
+
case 'domain':
|
|
45
|
+
cookieObj.domain = attrValue;
|
|
46
|
+
break;
|
|
47
|
+
case 'path':
|
|
48
|
+
cookieObj.path = attrValue;
|
|
49
|
+
break;
|
|
50
|
+
case 'expires':
|
|
51
|
+
cookieObj.expires = attrValue;
|
|
52
|
+
break;
|
|
53
|
+
case 'max-age':
|
|
54
|
+
cookieObj.maxAge = attrValue;
|
|
55
|
+
break;
|
|
56
|
+
case 'secure':
|
|
57
|
+
cookieObj.secure = true;
|
|
58
|
+
break;
|
|
59
|
+
case 'httponly':
|
|
60
|
+
cookieObj.httpOnly = true;
|
|
61
|
+
break;
|
|
62
|
+
case 'samesite':
|
|
63
|
+
cookieObj.sameSite = attrValue;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return cookieObj;
|
|
69
|
+
})
|
|
70
|
+
.filter((cookieObj) => cookieObj.name); // Filter out empty cookies
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const extractCookiesFromHeaders = (
|
|
74
|
+
headers: Record<string, string>
|
|
75
|
+
): {
|
|
76
|
+
requestCookies: Cookie[];
|
|
77
|
+
responseCookies: Cookie[];
|
|
78
|
+
} => {
|
|
79
|
+
const requestCookies: Cookie[] = [];
|
|
80
|
+
const responseCookies: Cookie[] = [];
|
|
81
|
+
|
|
82
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
83
|
+
const lowerKey = key.toLowerCase();
|
|
84
|
+
|
|
85
|
+
if (lowerKey === 'cookie') {
|
|
86
|
+
// Cookie header contains all cookies in one string
|
|
87
|
+
requestCookies.push(...parseCookieString(value));
|
|
88
|
+
} else if (lowerKey === 'set-cookie') {
|
|
89
|
+
// Set-Cookie header contains one cookie with attributes
|
|
90
|
+
const cookies = parseCookieString(value);
|
|
91
|
+
responseCookies.push(...cookies);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return { requestCookies, responseCookies };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const CookiesTab = ({
|
|
99
|
+
selectedRequest,
|
|
100
|
+
networkEntries,
|
|
101
|
+
}: CookiesTabProps) => {
|
|
102
|
+
return (
|
|
103
|
+
<ScrollArea className="h-full min-h-0 p-4">
|
|
104
|
+
{(() => {
|
|
105
|
+
const entry = networkEntries.get(selectedRequest.id);
|
|
106
|
+
if (!entry) {
|
|
107
|
+
return (
|
|
108
|
+
<div className="text-sm text-gray-400">
|
|
109
|
+
No request data available
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Extract cookies from request and response headers separately
|
|
115
|
+
const requestHeaders = entry.request?.headers || {};
|
|
116
|
+
const responseHeaders = entry.response?.headers || {};
|
|
117
|
+
|
|
118
|
+
const { requestCookies } = extractCookiesFromHeaders(requestHeaders);
|
|
119
|
+
const { responseCookies } = extractCookiesFromHeaders(responseHeaders);
|
|
120
|
+
|
|
121
|
+
const hasRequestCookies = requestCookies.length > 0;
|
|
122
|
+
const hasResponseCookies = responseCookies.length > 0;
|
|
123
|
+
|
|
124
|
+
if (!hasRequestCookies && !hasResponseCookies) {
|
|
125
|
+
return (
|
|
126
|
+
<div className="text-sm text-gray-400">
|
|
127
|
+
No cookies for this request
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="space-y-6">
|
|
134
|
+
{/* Request Cookies */}
|
|
135
|
+
{hasRequestCookies && (
|
|
136
|
+
<div>
|
|
137
|
+
<h4 className="text-sm font-medium text-gray-300 mb-3">
|
|
138
|
+
Request Cookies ({requestCookies.length})
|
|
139
|
+
</h4>
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
{requestCookies.map((cookie, index) => (
|
|
142
|
+
<div
|
|
143
|
+
key={`request-${index}`}
|
|
144
|
+
className="bg-gray-800 border border-gray-700 rounded p-3"
|
|
145
|
+
>
|
|
146
|
+
<div className="flex items-center justify-between mb-2">
|
|
147
|
+
<span className="text-sm font-medium text-blue-400">
|
|
148
|
+
{cookie.name}
|
|
149
|
+
</span>
|
|
150
|
+
<div className="flex items-center gap-2">
|
|
151
|
+
{cookie.secure && (
|
|
152
|
+
<Badge
|
|
153
|
+
variant="outline"
|
|
154
|
+
className="text-xs text-yellow-400 border-yellow-400"
|
|
155
|
+
>
|
|
156
|
+
Secure
|
|
157
|
+
</Badge>
|
|
158
|
+
)}
|
|
159
|
+
{cookie.httpOnly && (
|
|
160
|
+
<Badge
|
|
161
|
+
variant="outline"
|
|
162
|
+
className="text-xs text-purple-400 border-purple-400"
|
|
163
|
+
>
|
|
164
|
+
HttpOnly
|
|
165
|
+
</Badge>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="text-sm text-gray-300 mb-2">
|
|
170
|
+
{cookie.value}
|
|
171
|
+
</div>
|
|
172
|
+
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
|
|
173
|
+
{cookie.domain && (
|
|
174
|
+
<div>
|
|
175
|
+
<span className="font-medium">Domain:</span>{' '}
|
|
176
|
+
{cookie.domain}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
{cookie.path && (
|
|
180
|
+
<div>
|
|
181
|
+
<span className="font-medium">Path:</span>{' '}
|
|
182
|
+
{cookie.path}
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
{cookie.expires && (
|
|
186
|
+
<div>
|
|
187
|
+
<span className="font-medium">Expires:</span>{' '}
|
|
188
|
+
{cookie.expires}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
{cookie.maxAge && (
|
|
192
|
+
<div>
|
|
193
|
+
<span className="font-medium">Max-Age:</span>{' '}
|
|
194
|
+
{cookie.maxAge}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
{cookie.sameSite && (
|
|
198
|
+
<div>
|
|
199
|
+
<span className="font-medium">SameSite:</span>{' '}
|
|
200
|
+
{cookie.sameSite}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
))}
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
{/* Response Cookies */}
|
|
211
|
+
{hasResponseCookies && (
|
|
212
|
+
<div>
|
|
213
|
+
<h4 className="text-sm font-medium text-gray-300 mb-3">
|
|
214
|
+
Response Cookies ({responseCookies.length})
|
|
215
|
+
</h4>
|
|
216
|
+
<div className="space-y-2">
|
|
217
|
+
{responseCookies.map((cookie, index) => (
|
|
218
|
+
<div
|
|
219
|
+
key={`response-${index}`}
|
|
220
|
+
className="bg-gray-800 border border-gray-700 rounded p-3"
|
|
221
|
+
>
|
|
222
|
+
<div className="flex items-center justify-between mb-2">
|
|
223
|
+
<span className="text-sm font-medium text-green-400">
|
|
224
|
+
{cookie.name}
|
|
225
|
+
</span>
|
|
226
|
+
<div className="flex items-center gap-2">
|
|
227
|
+
{cookie.secure && (
|
|
228
|
+
<Badge
|
|
229
|
+
variant="outline"
|
|
230
|
+
className="text-xs text-yellow-400 border-yellow-400"
|
|
231
|
+
>
|
|
232
|
+
Secure
|
|
233
|
+
</Badge>
|
|
234
|
+
)}
|
|
235
|
+
{cookie.httpOnly && (
|
|
236
|
+
<Badge
|
|
237
|
+
variant="outline"
|
|
238
|
+
className="text-xs text-purple-400 border-purple-400"
|
|
239
|
+
>
|
|
240
|
+
HttpOnly
|
|
241
|
+
</Badge>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
<div className="text-sm text-gray-300 mb-2">
|
|
246
|
+
{cookie.value}
|
|
247
|
+
</div>
|
|
248
|
+
<div className="grid grid-cols-2 gap-4 text-xs text-gray-400">
|
|
249
|
+
{cookie.domain && (
|
|
250
|
+
<div>
|
|
251
|
+
<span className="font-medium">Domain:</span>{' '}
|
|
252
|
+
{cookie.domain}
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
{cookie.path && (
|
|
256
|
+
<div>
|
|
257
|
+
<span className="font-medium">Path:</span>{' '}
|
|
258
|
+
{cookie.path}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
{cookie.expires && (
|
|
262
|
+
<div>
|
|
263
|
+
<span className="font-medium">Expires:</span>{' '}
|
|
264
|
+
{cookie.expires}
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
{cookie.maxAge && (
|
|
268
|
+
<div>
|
|
269
|
+
<span className="font-medium">Max-Age:</span>{' '}
|
|
270
|
+
{cookie.maxAge}
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
{cookie.sameSite && (
|
|
274
|
+
<div>
|
|
275
|
+
<span className="font-medium">SameSite:</span>{' '}
|
|
276
|
+
{cookie.sameSite}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
))}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
})()}
|
|
288
|
+
</ScrollArea>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
+
import { NetworkEntry } from '../types';
|
|
3
|
+
|
|
4
|
+
export type HeadersTabProps = {
|
|
5
|
+
selectedRequest: {
|
|
6
|
+
id: string;
|
|
7
|
+
domain: string;
|
|
8
|
+
path: string;
|
|
9
|
+
method: string;
|
|
10
|
+
status: number;
|
|
11
|
+
requestBody?: {
|
|
12
|
+
type: string;
|
|
13
|
+
data: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
networkEntries: Map<string, NetworkEntry>;
|
|
17
|
+
getStatusColor: (status: number) => string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const HeadersTab = ({
|
|
21
|
+
selectedRequest,
|
|
22
|
+
networkEntries,
|
|
23
|
+
getStatusColor,
|
|
24
|
+
}: HeadersTabProps) => {
|
|
25
|
+
return (
|
|
26
|
+
<ScrollArea className="h-full min-h-0">
|
|
27
|
+
<div className="p-4 space-y-4">
|
|
28
|
+
<div>
|
|
29
|
+
<h4 className="text-sm font-medium text-gray-300 mb-2">General</h4>
|
|
30
|
+
<div className="space-y-1 text-sm">
|
|
31
|
+
<div className="flex">
|
|
32
|
+
<span className="w-32 text-gray-400">Request URL:</span>
|
|
33
|
+
<span className="text-blue-400">
|
|
34
|
+
{selectedRequest.domain}
|
|
35
|
+
{selectedRequest.path}
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="flex">
|
|
39
|
+
<span className="w-32 text-gray-400">Request Method:</span>
|
|
40
|
+
<span>{selectedRequest.method}</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex">
|
|
43
|
+
<span className="w-32 text-gray-400">Status Code:</span>
|
|
44
|
+
<span className={getStatusColor(selectedRequest.status)}>
|
|
45
|
+
{selectedRequest.status}
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
{selectedRequest.requestBody && (
|
|
49
|
+
<div className="flex">
|
|
50
|
+
<span className="w-32 text-gray-400">Content-Type:</span>
|
|
51
|
+
<span className="text-blue-400">
|
|
52
|
+
{selectedRequest.requestBody.type}
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div>
|
|
60
|
+
<h4 className="text-sm font-medium text-gray-300 mb-2">
|
|
61
|
+
Response Headers
|
|
62
|
+
</h4>
|
|
63
|
+
<div className="space-y-1 text-sm font-mono">
|
|
64
|
+
{(() => {
|
|
65
|
+
const entry = networkEntries.get(selectedRequest.id);
|
|
66
|
+
const responseHeaders = entry?.response?.headers;
|
|
67
|
+
if (responseHeaders && Object.keys(responseHeaders).length > 0) {
|
|
68
|
+
return Object.entries(responseHeaders).map(([key, value]) => (
|
|
69
|
+
<div key={key} className="flex">
|
|
70
|
+
<span className="w-32 text-gray-400">
|
|
71
|
+
{key.toLowerCase()}:
|
|
72
|
+
</span>
|
|
73
|
+
<span className="flex-1 break-all">{value}</span>
|
|
74
|
+
</div>
|
|
75
|
+
));
|
|
76
|
+
} else {
|
|
77
|
+
return (
|
|
78
|
+
<div className="text-gray-500 italic">
|
|
79
|
+
No response headers available
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
})()}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div>
|
|
88
|
+
<h4 className="text-sm font-medium text-gray-300 mb-2">
|
|
89
|
+
Request Headers
|
|
90
|
+
</h4>
|
|
91
|
+
<div className="space-y-1 text-sm font-mono">
|
|
92
|
+
{(() => {
|
|
93
|
+
const entry = networkEntries.get(selectedRequest.id);
|
|
94
|
+
const requestHeaders = entry?.request?.headers;
|
|
95
|
+
if (requestHeaders && Object.keys(requestHeaders).length > 0) {
|
|
96
|
+
return Object.entries(requestHeaders).map(([key, value]) => (
|
|
97
|
+
<div key={key} className="flex">
|
|
98
|
+
<span className="w-32 text-gray-400">
|
|
99
|
+
{key.toLowerCase()}:
|
|
100
|
+
</span>
|
|
101
|
+
<span className="flex-1 break-all">{value}</span>
|
|
102
|
+
</div>
|
|
103
|
+
));
|
|
104
|
+
} else {
|
|
105
|
+
return (
|
|
106
|
+
<div className="text-gray-500 italic">
|
|
107
|
+
No request headers available
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
})()}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</ScrollArea>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ScrollArea } from '../components/ScrollArea';
|
|
2
|
+
import { JsonTree } from '../components/JsonTree';
|
|
3
|
+
|
|
4
|
+
export type RequestTabProps = {
|
|
5
|
+
selectedRequest: {
|
|
6
|
+
method: string;
|
|
7
|
+
requestBody?: {
|
|
8
|
+
type: string;
|
|
9
|
+
data: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const RequestTab = ({ selectedRequest }: RequestTabProps) => {
|
|
15
|
+
const renderRequestBody = () => {
|
|
16
|
+
if (!selectedRequest?.requestBody) return null;
|
|
17
|
+
|
|
18
|
+
const { type, data } = selectedRequest.requestBody;
|
|
19
|
+
|
|
20
|
+
if (type === 'application/json') {
|
|
21
|
+
try {
|
|
22
|
+
const jsonData = JSON.parse(data);
|
|
23
|
+
return (
|
|
24
|
+
<div className="bg-gray-800 p-3 rounded border border-gray-700">
|
|
25
|
+
<JsonTree data={jsonData} />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
} catch {
|
|
29
|
+
// Fallback to pre tag if JSON parsing fails
|
|
30
|
+
return (
|
|
31
|
+
<pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
|
|
32
|
+
{data}
|
|
33
|
+
</pre>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// For non-JSON content types, use the existing pre tag
|
|
39
|
+
return (
|
|
40
|
+
<pre className="text-sm font-mono text-gray-300 whitespace-pre-wrap bg-gray-800 p-3 rounded border border-gray-700 overflow-x-auto">
|
|
41
|
+
{data}
|
|
42
|
+
</pre>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<ScrollArea className="h-full min-h-0 p-4">
|
|
48
|
+
{selectedRequest?.requestBody ? (
|
|
49
|
+
<div className="space-y-4">
|
|
50
|
+
<div>
|
|
51
|
+
<h4 className="text-sm font-medium text-gray-300 mb-2">
|
|
52
|
+
Request Body
|
|
53
|
+
</h4>
|
|
54
|
+
<div className="text-sm mb-2">
|
|
55
|
+
<span className="text-gray-400">Content-Type: </span>
|
|
56
|
+
<span className="text-blue-400">
|
|
57
|
+
{selectedRequest.requestBody.type}
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div>{renderRequestBody()}</div>
|
|
62
|
+
</div>
|
|
63
|
+
) : (
|
|
64
|
+
<div className="text-sm text-gray-400">
|
|
65
|
+
{selectedRequest?.method === 'GET'
|
|
66
|
+
? "GET requests don't have a request body"
|
|
67
|
+
: 'No request body for this request'}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</ScrollArea>
|
|
71
|
+
);
|
|
72
|
+
};
|