@rozenite/network-activity-plugin 1.0.0-alpha.1 → 1.0.0-alpha.4

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.
@@ -1,14 +1,124 @@
1
- import React from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { NetworkEntry } from '../types/network';
3
3
  import { formatFileSize, formatDuration, formatLongUrl } from './utils';
4
4
  import { Card, EmptyState, Tooltip } from './components';
5
5
  import styles from './network-details.module.css';
6
6
 
7
+ // Enhanced network entry type to match the panel
8
+ type EnhancedNetworkEntry = NetworkEntry & {
9
+ type?: string;
10
+ initiator?: {
11
+ type: string;
12
+ url?: string;
13
+ lineNumber?: number;
14
+ columnNumber?: number;
15
+ };
16
+ request?: {
17
+ url: string;
18
+ method: string;
19
+ headers: Record<string, string>;
20
+ postData?: string;
21
+ hasPostData?: boolean;
22
+ };
23
+ response?: {
24
+ url: string;
25
+ status: number;
26
+ statusText: string;
27
+ headers: Record<string, string>;
28
+ mimeType: string;
29
+ encodedDataLength: number;
30
+ responseTime: number;
31
+ };
32
+ responseBody?: {
33
+ body: string;
34
+ base64Encoded: boolean;
35
+ };
36
+ dataLength?: number;
37
+ };
38
+
39
+ // Symbolication response types
40
+ interface SymbolicatedStackFrame {
41
+ column: number;
42
+ file: string;
43
+ lineNumber: number;
44
+ methodName: string;
45
+ collapse: boolean;
46
+ }
47
+
48
+ interface CodeFrame {
49
+ content: string;
50
+ location: {
51
+ row: number;
52
+ column: number;
53
+ };
54
+ fileName: string;
55
+ }
56
+
57
+ interface SymbolicationResponse {
58
+ codeFrame: CodeFrame;
59
+ stack: SymbolicatedStackFrame[];
60
+ }
61
+
7
62
  interface NetworkDetailsProps {
8
- entry: NetworkEntry | null;
63
+ entry: EnhancedNetworkEntry | null;
64
+ onRequestResponseBody?: (requestId: string) => void;
9
65
  }
10
66
 
11
- export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
67
+ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({
68
+ entry,
69
+ onRequestResponseBody,
70
+ }) => {
71
+ const [isSymbolicating, setIsSymbolicating] = useState(false);
72
+ const [, forceUpdate] = useState({});
73
+
74
+ const symbolicateCaller = async () => {
75
+ if (!entry?.initiator) return;
76
+
77
+ setIsSymbolicating(true);
78
+ try {
79
+ const stackData = {
80
+ stack: [
81
+ {
82
+ column: entry.initiator.columnNumber || 0,
83
+ file: entry.initiator.url || '',
84
+ lineNumber: entry.initiator.lineNumber || 0,
85
+ },
86
+ ],
87
+ };
88
+
89
+ const response = await fetch(`${window.location.origin}/symbolicate`, {
90
+ method: 'POST',
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ },
94
+ body: JSON.stringify(stackData),
95
+ });
96
+
97
+ if (response.ok) {
98
+ const data: SymbolicationResponse = await response.json();
99
+
100
+ // Update the entry.initiator with symbolicated data
101
+ if (data.stack && data.stack.length > 0) {
102
+ const symbolicatedFrame = data.stack[0];
103
+ if (entry.initiator) {
104
+ entry.initiator.url = symbolicatedFrame.file;
105
+ entry.initiator.lineNumber = symbolicatedFrame.lineNumber;
106
+ entry.initiator.columnNumber = symbolicatedFrame.column;
107
+ // Add method name to initiator
108
+ (entry.initiator as any).methodName = symbolicatedFrame.methodName;
109
+ }
110
+ }
111
+ forceUpdate({});
112
+ } else {
113
+ console.error('Symbolication failed:', response.statusText);
114
+ }
115
+ } catch (error) {
116
+ console.error('Symbolication error:', error);
117
+ } finally {
118
+ setIsSymbolicating(false);
119
+ }
120
+ };
121
+
12
122
  if (!entry) {
13
123
  return <EmptyState message="Select a request to view details" />;
14
124
  }
@@ -20,25 +130,59 @@ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
20
130
  <h3 className={styles.cardTitle}>General</h3>
21
131
  <div className={styles.infoText}>
22
132
  <div className={styles.infoRowUrl}>
23
- <strong>Request URL:</strong>
24
- <Tooltip content={entry.request.request.url} showOnlyWhenTruncated>
133
+ <strong>Request URL:</strong>
134
+ <Tooltip content={entry.url} showOnlyWhenTruncated>
25
135
  <span className={styles.urlText}>
26
- {formatLongUrl(entry.request.request.url, 100)}
136
+ {formatLongUrl(entry.url, 100)}
27
137
  </span>
28
138
  </Tooltip>
29
139
  </div>
30
140
  <div className={styles.infoRow}>
31
- <strong>Request Method:</strong> {entry.request.request.method}
32
- </div>
33
- <div className={styles.infoRow}>
34
- <strong>Status Code:</strong> {entry.response?.response.status || 'Pending'}
141
+ <strong>Request Method:</strong> {entry.method}
35
142
  </div>
36
143
  <div className={styles.infoRow}>
37
- <strong>Remote Address:</strong> {entry.response?.response.remoteIPAddress || 'Unknown'}
144
+ <strong>Status Code:</strong> {entry.response?.status || 'Pending'}
38
145
  </div>
39
146
  <div className={styles.infoRow}>
40
- <strong>Referrer Policy:</strong> {entry.request.request.headers['referer'] || 'no-referrer'}
147
+ <strong>Status:</strong> {entry.status}
41
148
  </div>
149
+ {entry.type && (
150
+ <div className={styles.infoRow}>
151
+ <strong>Resource Type:</strong> {entry.type}
152
+ </div>
153
+ )}
154
+ {entry.initiator && (
155
+ <div className={styles.infoRow}>
156
+ <strong>Initiator:</strong> {entry.initiator.type}
157
+ {(entry.initiator as any).methodName && (
158
+ <div className={styles.infoRowSub}>
159
+ <strong>Method:</strong> {(entry.initiator as any).methodName}
160
+ </div>
161
+ )}
162
+ {entry.initiator.url && (
163
+ <div className={styles.infoRowSub}>
164
+ <strong>URL:</strong> {entry.initiator.url}
165
+ </div>
166
+ )}
167
+ {entry.initiator.lineNumber && (
168
+ <div className={styles.infoRowSub}>
169
+ <strong>Line:</strong> {entry.initiator.lineNumber}:
170
+ {entry.initiator.columnNumber || 0}
171
+ </div>
172
+ )}
173
+ <div className={styles.infoRowSub}>
174
+ <button
175
+ className={styles.symbolicateButton}
176
+ onClick={symbolicateCaller}
177
+ disabled={isSymbolicating}
178
+ >
179
+ {isSymbolicating
180
+ ? 'Symbolicating...'
181
+ : 'Symbolicate initiator'}
182
+ </button>
183
+ </div>
184
+ </div>
185
+ )}
42
186
  </div>
43
187
  </Card>
44
188
 
@@ -47,13 +191,11 @@ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
47
191
  <Card className={styles.card}>
48
192
  <h3 className={styles.cardTitle}>Response Headers</h3>
49
193
  <div className={styles.headersContainer}>
50
- {Object.entries(entry.response.response.headers).map(([key, value]) => (
194
+ {Object.entries(entry.response.headers).map(([key, value]) => (
51
195
  <div key={key} className={styles.headerRow}>
52
- <strong>{key}:</strong>
196
+ <strong>{key}:</strong>
53
197
  <Tooltip content={value} showOnlyWhenTruncated>
54
- <span className={styles.headerValue}>
55
- {value}
56
- </span>
198
+ <span className={styles.headerValue}>{value}</span>
57
199
  </Tooltip>
58
200
  </div>
59
201
  ))}
@@ -65,13 +207,11 @@ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
65
207
  <Card className={styles.card}>
66
208
  <h3 className={styles.cardTitle}>Request Headers</h3>
67
209
  <div className={styles.headersContainer}>
68
- {Object.entries(entry.request.request.headers).map(([key, value]) => (
210
+ {Object.entries(entry.headers).map(([key, value]) => (
69
211
  <div key={key} className={styles.headerRow}>
70
- <strong>{key}:</strong>
212
+ <strong>{key}:</strong>
71
213
  <Tooltip content={value} showOnlyWhenTruncated>
72
- <span className={styles.headerValue}>
73
- {value}
74
- </span>
214
+ <span className={styles.headerValue}>{value}</span>
75
215
  </Tooltip>
76
216
  </div>
77
217
  ))}
@@ -79,51 +219,122 @@ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
79
219
  </Card>
80
220
 
81
221
  {/* Size Information */}
82
- {entry.response && (
222
+ {(entry.encodedDataLength || entry.dataLength) && (
83
223
  <Card className={styles.card}>
84
224
  <h3 className={styles.cardTitle}>Size Information</h3>
85
225
  <div className={styles.infoText}>
86
- <div className={styles.infoRow}>
87
- <strong>Decoded Body Size:</strong> {formatFileSize(entry.response.response.decodedBodySize)}
88
- </div>
226
+ {entry.dataLength && (
227
+ <div className={styles.infoRow}>
228
+ <strong>Data Length:</strong> {formatFileSize(entry.dataLength)}
229
+ </div>
230
+ )}
231
+ {entry.encodedDataLength && (
232
+ <div className={styles.infoRow}>
233
+ <strong>Encoded Data Length:</strong>{' '}
234
+ {formatFileSize(entry.encodedDataLength)}
235
+ </div>
236
+ )}
237
+ {entry.response?.mimeType && (
238
+ <div className={styles.infoRow}>
239
+ <strong>MIME Type:</strong> {entry.response.mimeType}
240
+ </div>
241
+ )}
89
242
  </div>
90
243
  </Card>
91
244
  )}
92
245
 
93
246
  {/* Timing Information */}
94
- {entry.response?.response.timing && (
247
+ {entry.duration && (
95
248
  <Card className={styles.card}>
96
249
  <h3 className={styles.cardTitle}>Timing</h3>
97
250
  <div className={styles.infoText}>
98
251
  <div className={styles.infoRow}>
99
- <strong>Time to First Byte:</strong> {entry.response.response.timing.receiveHeadersEnd - entry.response.response.timing.sendEnd}ms
252
+ <strong>Duration:</strong> {formatDuration(entry.duration)}
100
253
  </div>
101
254
  <div className={styles.infoRow}>
102
- <strong>Total Duration:</strong> {entry.duration ? formatDuration(entry.duration) : 'Unknown'}
255
+ <strong>Start Time:</strong>{' '}
256
+ {new Date(entry.startTime).toLocaleTimeString()}
103
257
  </div>
258
+ {entry.endTime && (
259
+ <div className={styles.infoRow}>
260
+ <strong>End Time:</strong>{' '}
261
+ {new Date(entry.endTime).toLocaleTimeString()}
262
+ </div>
263
+ )}
264
+ {entry.response?.responseTime && (
265
+ <div className={styles.infoRow}>
266
+ <strong>Response Time:</strong>{' '}
267
+ {new Date(entry.response.responseTime).toLocaleTimeString()}
268
+ </div>
269
+ )}
104
270
  </div>
105
271
  </Card>
106
272
  )}
107
273
 
108
274
  {/* Error Information */}
109
- {entry.loadingFailed && (
275
+ {entry.status === 'failed' && (
110
276
  <Card className={styles.card}>
111
- <h3 className={styles.cardTitleError}>Error</h3>
277
+ <h3 className={styles.cardTitle}>Error Information</h3>
112
278
  <div className={styles.infoText}>
113
279
  <div className={styles.infoRow}>
114
- <strong>Error Text:</strong> {entry.loadingFailed.errorText}
280
+ <strong>Error:</strong> {entry.errorText || 'Unknown error'}
115
281
  </div>
116
282
  <div className={styles.infoRow}>
117
- <strong>Type:</strong> {entry.loadingFailed.type}
283
+ <strong>Canceled:</strong> {entry.canceled ? 'Yes' : 'No'}
118
284
  </div>
119
- {entry.loadingFailed.blockedReason && (
120
- <div className={styles.infoRow}>
121
- <strong>Blocked Reason:</strong> {entry.loadingFailed.blockedReason}
122
- </div>
123
- )}
124
- {entry.loadingFailed.canceled && (
285
+ </div>
286
+ </Card>
287
+ )}
288
+
289
+ {/* Post Data */}
290
+ {entry.postData && (
291
+ <Card className={styles.card}>
292
+ <h3 className={styles.cardTitle}>Post Data</h3>
293
+ <div className={styles.postDataContainer}>
294
+ <pre className={styles.postDataText}>{entry.postData}</pre>
295
+ </div>
296
+ </Card>
297
+ )}
298
+
299
+ {/* Response Body */}
300
+ {entry.response && (
301
+ <Card className={styles.card}>
302
+ <h3 className={styles.cardTitle}>Response Body</h3>
303
+ {entry.responseBody ? (
304
+ <div className={styles.responseBodyContainer}>
305
+ <pre className={styles.responseBodyText}>
306
+ {entry.responseBody.base64Encoded
307
+ ? atob(entry.responseBody.body)
308
+ : entry.responseBody.body}
309
+ </pre>
310
+ </div>
311
+ ) : (
312
+ <div className={styles.responseBodyContainer}>
313
+ <button
314
+ className={styles.loadResponseBodyButton}
315
+ onClick={() => onRequestResponseBody?.(entry.requestId)}
316
+ disabled={!onRequestResponseBody}
317
+ >
318
+ Load Response Body
319
+ </button>
320
+ </div>
321
+ )}
322
+ </Card>
323
+ )}
324
+
325
+ {/* Request Details */}
326
+ {entry.request && (
327
+ <Card className={styles.card}>
328
+ <h3 className={styles.cardTitle}>Request Details</h3>
329
+ <div className={styles.infoText}>
330
+ <div className={styles.infoRow}>
331
+ <strong>Has Post Data:</strong>{' '}
332
+ {entry.request.hasPostData ? 'Yes' : 'No'}
333
+ </div>
334
+ {entry.request.postData && (
125
335
  <div className={styles.infoRow}>
126
- <strong>Canceled:</strong> Yes
336
+ <strong>Post Data Length:</strong>{' '}
337
+ {entry.request.postData.length} characters
127
338
  </div>
128
339
  )}
129
340
  </div>
@@ -131,4 +342,4 @@ export const NetworkDetails: React.FC<NetworkDetailsProps> = ({ entry }) => {
131
342
  )}
132
343
  </div>
133
344
  );
134
- };
345
+ };
@@ -63,6 +63,12 @@
63
63
  min-width: 0;
64
64
  }
65
65
 
66
+ .typeColumn {
67
+ width: 100px;
68
+ text-align: center;
69
+ flex-shrink: 0;
70
+ }
71
+
66
72
  .domainText {
67
73
  font-weight: bold;
68
74
  color: #333;
@@ -1,12 +1,46 @@
1
1
  import React from 'react';
2
2
  import { useVirtualizer } from '@tanstack/react-virtual';
3
3
  import { NetworkEntry } from '../types/network';
4
- import { getStatusColor, getMethodColor, formatDuration, formatFileSize, parseUrl } from './utils';
4
+ import {
5
+ getStatusColor,
6
+ getMethodColor,
7
+ formatDuration,
8
+ formatFileSize,
9
+ parseUrl,
10
+ } from './utils';
5
11
  import { Badge, Tooltip } from './components';
6
12
  import styles from './network-list.module.css';
7
13
 
14
+ // Enhanced network entry type to match the panel
15
+ type EnhancedNetworkEntry = NetworkEntry & {
16
+ type?: string;
17
+ initiator?: {
18
+ type: string;
19
+ url?: string;
20
+ lineNumber?: number;
21
+ columnNumber?: number;
22
+ };
23
+ request?: {
24
+ url: string;
25
+ method: string;
26
+ headers: Record<string, string>;
27
+ postData?: string;
28
+ hasPostData?: boolean;
29
+ };
30
+ response?: {
31
+ url: string;
32
+ status: number;
33
+ statusText: string;
34
+ headers: Record<string, string>;
35
+ mimeType: string;
36
+ encodedDataLength: number;
37
+ responseTime: number;
38
+ };
39
+ dataLength?: number;
40
+ };
41
+
8
42
  interface NetworkListProps {
9
- entries: NetworkEntry[];
43
+ entries: EnhancedNetworkEntry[];
10
44
  selectedRequestId: string | null;
11
45
  onSelect: (requestId: string) => void;
12
46
  height: number;
@@ -14,6 +48,56 @@ interface NetworkListProps {
14
48
 
15
49
  const ITEM_HEIGHT = 60; // Height of each network list item
16
50
 
51
+ const getResourceTypeColor = (type: string): string => {
52
+ switch (type) {
53
+ case 'Document':
54
+ return '#4285f4';
55
+ case 'Stylesheet':
56
+ return '#34a853';
57
+ case 'Script':
58
+ return '#fbbc04';
59
+ case 'Image':
60
+ return '#ea4335';
61
+ case 'Font':
62
+ return '#9c27b0';
63
+ case 'XHR':
64
+ return '#ff9800';
65
+ case 'Fetch':
66
+ return '#2196f3';
67
+ case 'WebSocket':
68
+ return '#00bcd4';
69
+ case 'Media':
70
+ return '#e91e63';
71
+ default:
72
+ return '#757575';
73
+ }
74
+ };
75
+
76
+ const getStatusDisplay = (
77
+ entry: EnhancedNetworkEntry
78
+ ): { text: string; color: string } => {
79
+ if (entry.status === 'failed') {
80
+ return { text: 'Failed', color: '#d32f2f' };
81
+ }
82
+ if (entry.status === 'pending') {
83
+ return { text: 'Pending', color: '#ff9800' };
84
+ }
85
+ if (entry.status === 'loading') {
86
+ return { text: 'Loading', color: '#2196f3' };
87
+ }
88
+ if (entry.status === 'finished') {
89
+ const status = entry.response?.status || 0;
90
+ if (status >= 400) {
91
+ return { text: status.toString(), color: '#d32f2f' };
92
+ }
93
+ if (status >= 300) {
94
+ return { text: status.toString(), color: '#ff9800' };
95
+ }
96
+ return { text: status.toString(), color: '#4caf50' };
97
+ }
98
+ return { text: '...', color: '#757575' };
99
+ };
100
+
17
101
  export const NetworkList: React.FC<NetworkListProps> = ({
18
102
  entries,
19
103
  selectedRequestId,
@@ -29,16 +113,18 @@ export const NetworkList: React.FC<NetworkListProps> = ({
29
113
  overscan: 5,
30
114
  });
31
115
 
32
- const NetworkListItem: React.FC<{ entry: NetworkEntry; index: number }> = ({ entry, index }) => {
33
- const status = entry.response?.response.status || 0;
34
- const method = entry.request.request.method;
35
- const url = entry.request.request.url;
116
+ const NetworkListItem: React.FC<{
117
+ entry: EnhancedNetworkEntry;
118
+ index: number;
119
+ }> = ({ entry, index }) => {
120
+ const method = entry.method;
121
+ const url = entry.url;
36
122
  const { domain, path } = parseUrl(url);
37
-
38
- // Get size information
39
- const encodedSize = entry.response?.response.encodedDataLength || 0;
40
- const decodedSize = entry.response?.response.decodedBodySize || 0;
41
- const displaySize = decodedSize > 0 ? decodedSize : encodedSize;
123
+ const resourceType = entry.type || 'Other';
124
+
125
+ // Get size information - prefer dataLength over encodedDataLength for display
126
+ const displaySize = entry.dataLength || entry.encodedDataLength || 0;
127
+ const statusDisplay = getStatusDisplay(entry);
42
128
 
43
129
  const isSelected = selectedRequestId === entry.requestId;
44
130
 
@@ -48,53 +134,70 @@ export const NetworkList: React.FC<NetworkListProps> = ({
48
134
  onClick={() => onSelect(entry.requestId)}
49
135
  >
50
136
  <div className={styles.statusColumn}>
51
- <Tooltip
52
- content={`Status: ${status || 'Pending'}`}
137
+ <Tooltip
138
+ content={`Status: ${statusDisplay.text}`}
53
139
  showOnlyWhenTruncated
54
- variant={status >= 400 ? 'error' : status >= 300 ? 'warning' : 'info'}
140
+ variant={
141
+ statusDisplay.color === '#d32f2f'
142
+ ? 'error'
143
+ : statusDisplay.color === '#ff9800'
144
+ ? 'warning'
145
+ : 'info'
146
+ }
55
147
  >
56
- <Badge color={getStatusColor(status)}>
57
- {status || '...'}
58
- </Badge>
148
+ <Badge color={statusDisplay.color}>{statusDisplay.text}</Badge>
59
149
  </Tooltip>
60
150
  </div>
61
151
  <div className={styles.methodColumn}>
62
- <Tooltip
63
- content={`Method: ${method}`}
152
+ <Tooltip
153
+ content={`Method: ${method}`}
64
154
  showOnlyWhenTruncated
65
155
  variant="info"
66
156
  >
67
- <Badge color={getMethodColor(method)}>
68
- {method}
69
- </Badge>
157
+ <Badge color={getMethodColor(method)}>{method}</Badge>
70
158
  </Tooltip>
71
159
  </div>
72
160
  <div className={styles.urlColumn}>
73
161
  <Tooltip content={domain} showOnlyWhenTruncated>
74
- <div className={styles.domainText}>
75
- {domain}
76
- </div>
162
+ <div className={styles.domainText}>{domain}</div>
77
163
  </Tooltip>
78
164
  <Tooltip content={path} showOnlyWhenTruncated>
79
- <div className={styles.pathText}>
80
- {path}
81
- </div>
165
+ <div className={styles.pathText}>{path}</div>
82
166
  </Tooltip>
83
167
  <Tooltip content={url} showOnlyWhenTruncated>
84
- <div className={styles.fullUrlText}>
85
- {url}
86
- </div>
168
+ <div className={styles.fullUrlText}>{url}</div>
169
+ </Tooltip>
170
+ </div>
171
+ <div className={styles.typeColumn}>
172
+ <Tooltip
173
+ content={`Resource Type: ${resourceType}`}
174
+ showOnlyWhenTruncated
175
+ variant="info"
176
+ >
177
+ <Badge color={getResourceTypeColor(resourceType)}>
178
+ {resourceType}
179
+ </Badge>
87
180
  </Tooltip>
88
181
  </div>
89
182
  <div className={styles.durationColumn}>
90
- <Tooltip content={`Duration: ${entry.duration ? formatDuration(entry.duration) : 'Pending'}`} showOnlyWhenTruncated>
183
+ <Tooltip
184
+ content={`Duration: ${
185
+ entry.duration ? formatDuration(entry.duration) : 'Pending'
186
+ }`}
187
+ showOnlyWhenTruncated
188
+ >
91
189
  <span className={styles.columnText}>
92
190
  {entry.duration ? formatDuration(entry.duration) : '...'}
93
191
  </span>
94
192
  </Tooltip>
95
193
  </div>
96
194
  <div className={styles.sizeColumn}>
97
- <Tooltip content={`Size: ${displaySize > 0 ? formatFileSize(displaySize) : 'Unknown'}`} showOnlyWhenTruncated>
195
+ <Tooltip
196
+ content={`Size: ${
197
+ displaySize > 0 ? formatFileSize(displaySize) : 'Unknown'
198
+ }`}
199
+ showOnlyWhenTruncated
200
+ >
98
201
  <span className={styles.columnText}>
99
202
  {displaySize > 0 ? formatFileSize(displaySize) : '...'}
100
203
  </span>
@@ -104,31 +207,23 @@ export const NetworkList: React.FC<NetworkListProps> = ({
104
207
  );
105
208
  };
106
209
 
107
- if (entries.length === 0) {
108
- return (
109
- <div className={styles.emptyContainer} style={{ height }}>
110
- <div className={styles.emptyText}>
111
- No network requests recorded
112
- </div>
113
- </div>
114
- );
115
- }
116
-
117
210
  return (
118
- <div
119
- ref={parentRef}
120
- className={styles.container}
121
- style={{ height }}
122
- >
211
+ <div ref={parentRef} className={styles.networkList} style={{ height }}>
123
212
  <div
124
- className={styles.virtualContainer}
125
- style={{ height: `${virtualizer.getTotalSize()}px` }}
213
+ style={{
214
+ height: `${virtualizer.getTotalSize()}px`,
215
+ width: '100%',
216
+ position: 'relative',
217
+ }}
126
218
  >
127
- {virtualizer.getVirtualItems().map((virtualItem: any) => (
219
+ {virtualizer.getVirtualItems().map((virtualItem) => (
128
220
  <div
129
221
  key={virtualItem.key}
130
- className={styles.virtualItem}
131
222
  style={{
223
+ position: 'absolute',
224
+ top: 0,
225
+ left: 0,
226
+ width: '100%',
132
227
  height: `${virtualItem.size}px`,
133
228
  transform: `translateY(${virtualItem.start}px)`,
134
229
  }}
@@ -142,4 +237,4 @@ export const NetworkList: React.FC<NetworkListProps> = ({
142
237
  </div>
143
238
  </div>
144
239
  );
145
- };
240
+ };
@@ -25,16 +25,10 @@ export const NetworkToolbar: React.FC<NetworkToolbarProps> = ({
25
25
  >
26
26
  {isRecording ? 'Stop' : 'Start'} Recording
27
27
  </Button>
28
- <Button
29
- onClick={onClear}
30
- variant="secondary"
31
- size="small"
32
- >
28
+ <Button onClick={onClear} variant="secondary" size="small">
33
29
  Clear
34
30
  </Button>
35
- <div className={styles.requestCount}>
36
- {requestCount} requests
37
- </div>
31
+ <div className={styles.requestCount}>{requestCount} requests</div>
38
32
  </Toolbar>
39
33
  );
40
- };
34
+ };