@rozenite/network-activity-plugin 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/devtools/App.html +2 -2
- package/dist/devtools/assets/{App-DsimzJvx.js → App-CEESZAW_.js} +985 -259
- package/dist/devtools/assets/{App-CUXU0mup.css → App-xppYUJvX.css} +94 -0
- package/dist/rozenite.json +1 -1
- package/package.json +6 -6
- package/src/ui/components/FilterBar.tsx +13 -45
- package/src/ui/components/NetworkTimeline.tsx +422 -0
- package/src/ui/components/RequestList.tsx +6 -185
- package/src/ui/components/Toolbar.tsx +13 -1
- package/src/ui/hooks/useNetworkActivitySessionExport.ts +39 -0
- package/src/ui/state/__tests__/store.test.ts +77 -0
- package/src/ui/state/derived.ts +2 -0
- package/src/ui/state/filter.ts +49 -0
- package/src/ui/state/hooks.ts +2 -2
- package/src/ui/state/model.ts +1 -0
- package/src/ui/state/store.ts +24 -2
- package/src/ui/utils/__tests__/requestFilters.test.ts +32 -0
- package/src/ui/utils/__tests__/sessionExport.test.ts +174 -0
- package/src/ui/utils/__tests__/timelineModel.test.ts +170 -0
- package/src/ui/utils/download.ts +7 -0
- package/src/ui/utils/requestFilters.ts +183 -0
- package/src/ui/utils/sessionExport.ts +185 -0
- package/src/ui/utils/timelineModel.ts +352 -0
- package/src/ui/views/InspectorView.tsx +40 -8
|
@@ -20975,6 +20975,9 @@ const symbolicateInitiator = async (initiator, symbolicateStackTrace = symbolica
|
|
|
20975
20975
|
}
|
|
20976
20976
|
};
|
|
20977
20977
|
const STORE_VERSION = 1;
|
|
20978
|
+
const getElapsedDuration = (endTimestamp, startTimestamp) => {
|
|
20979
|
+
return Math.max(endTimestamp - startTimestamp, 0);
|
|
20980
|
+
};
|
|
20978
20981
|
const createNetworkActivityStore = () => createStore()(
|
|
20979
20982
|
persist(
|
|
20980
20983
|
(set, get) => ({
|
|
@@ -21169,6 +21172,10 @@ const createNetworkActivityStore = () => createStore()(
|
|
|
21169
21172
|
const updatedEntry = {
|
|
21170
21173
|
...httpEntry,
|
|
21171
21174
|
status: "failed",
|
|
21175
|
+
duration: getElapsedDuration(
|
|
21176
|
+
eventData.timestamp,
|
|
21177
|
+
httpEntry.timestamp
|
|
21178
|
+
),
|
|
21172
21179
|
error: eventData.error
|
|
21173
21180
|
};
|
|
21174
21181
|
const newEntries = new Map(state.networkEntries);
|
|
@@ -21259,7 +21266,10 @@ const createNetworkActivityStore = () => createStore()(
|
|
|
21259
21266
|
status: "closed",
|
|
21260
21267
|
closeCode: eventData.code,
|
|
21261
21268
|
closeReason: eventData.reason,
|
|
21262
|
-
duration:
|
|
21269
|
+
duration: getElapsedDuration(
|
|
21270
|
+
eventData.timestamp,
|
|
21271
|
+
wsEntry.timestamp
|
|
21272
|
+
)
|
|
21263
21273
|
};
|
|
21264
21274
|
const newEntries = new Map(state.networkEntries);
|
|
21265
21275
|
newEntries.set(entry.id, updatedEntry);
|
|
@@ -21324,6 +21334,10 @@ const createNetworkActivityStore = () => createStore()(
|
|
|
21324
21334
|
const updatedEntry = {
|
|
21325
21335
|
...wsEntry,
|
|
21326
21336
|
status: "error",
|
|
21337
|
+
duration: getElapsedDuration(
|
|
21338
|
+
eventData.timestamp,
|
|
21339
|
+
wsEntry.timestamp
|
|
21340
|
+
),
|
|
21327
21341
|
error: eventData.error
|
|
21328
21342
|
};
|
|
21329
21343
|
const newEntries = new Map(state.networkEntries);
|
|
@@ -21408,6 +21422,10 @@ const createNetworkActivityStore = () => createStore()(
|
|
|
21408
21422
|
const updatedEntry = {
|
|
21409
21423
|
...sseEntry,
|
|
21410
21424
|
status: "error",
|
|
21425
|
+
duration: getElapsedDuration(
|
|
21426
|
+
eventData.timestamp,
|
|
21427
|
+
sseEntry.timestamp
|
|
21428
|
+
),
|
|
21411
21429
|
error: eventData.error.message
|
|
21412
21430
|
};
|
|
21413
21431
|
const newEntries = new Map(state.networkEntries);
|
|
@@ -21425,7 +21443,10 @@ const createNetworkActivityStore = () => createStore()(
|
|
|
21425
21443
|
const updatedEntry = {
|
|
21426
21444
|
...sseEntry,
|
|
21427
21445
|
status: "closed",
|
|
21428
|
-
duration:
|
|
21446
|
+
duration: getElapsedDuration(
|
|
21447
|
+
eventData.timestamp,
|
|
21448
|
+
sseEntry.timestamp
|
|
21449
|
+
)
|
|
21429
21450
|
};
|
|
21430
21451
|
const newEntries = new Map(state.networkEntries);
|
|
21431
21452
|
newEntries.set(eventData.requestId, updatedEntry);
|
|
@@ -21862,6 +21883,7 @@ const getProcessedRequests = memoize((state) => {
|
|
|
21862
21883
|
method: httpEntry.request.method,
|
|
21863
21884
|
httpStatus: httpEntry.response?.status,
|
|
21864
21885
|
contentType: httpEntry.response?.contentType,
|
|
21886
|
+
ttfb: httpEntry.ttfb,
|
|
21865
21887
|
progress: httpEntry.progress
|
|
21866
21888
|
});
|
|
21867
21889
|
} else if (entry.type === "websocket") {
|
|
@@ -21939,9 +21961,232 @@ const useOverrides = () => {
|
|
|
21939
21961
|
const useClientUISettings = () => {
|
|
21940
21962
|
return useNetworkActivityStore((state) => state.clientUISettings);
|
|
21941
21963
|
};
|
|
21964
|
+
const base64ToBytes = (base64) => {
|
|
21965
|
+
const binary = atob(base64);
|
|
21966
|
+
const bytes = new Uint8Array(binary.length);
|
|
21967
|
+
for (let i = 0; i < binary.length; i++) {
|
|
21968
|
+
bytes[i] = binary.charCodeAt(i);
|
|
21969
|
+
}
|
|
21970
|
+
return bytes;
|
|
21971
|
+
};
|
|
21972
|
+
const base64ToBlob = (base64, contentType) => {
|
|
21973
|
+
const binary = atob(base64);
|
|
21974
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
21975
|
+
const view = new Uint8Array(buffer);
|
|
21976
|
+
for (let i = 0; i < binary.length; i++) {
|
|
21977
|
+
view[i] = binary.charCodeAt(i);
|
|
21978
|
+
}
|
|
21979
|
+
return new Blob([buffer], {
|
|
21980
|
+
type: contentType || "application/octet-stream"
|
|
21981
|
+
});
|
|
21982
|
+
};
|
|
21983
|
+
const CONTENT_TYPE_EXTENSIONS = {
|
|
21984
|
+
"application/pdf": "pdf",
|
|
21985
|
+
"application/zip": "zip",
|
|
21986
|
+
"application/gzip": "gz",
|
|
21987
|
+
"application/json": "json",
|
|
21988
|
+
"application/xml": "xml",
|
|
21989
|
+
"application/javascript": "js",
|
|
21990
|
+
"application/octet-stream": "bin",
|
|
21991
|
+
"image/png": "png",
|
|
21992
|
+
"image/jpeg": "jpg",
|
|
21993
|
+
"image/gif": "gif",
|
|
21994
|
+
"image/webp": "webp",
|
|
21995
|
+
"image/svg+xml": "svg",
|
|
21996
|
+
"image/bmp": "bmp",
|
|
21997
|
+
"image/x-icon": "ico",
|
|
21998
|
+
"audio/mpeg": "mp3",
|
|
21999
|
+
"audio/ogg": "ogg",
|
|
22000
|
+
"audio/wav": "wav",
|
|
22001
|
+
"video/mp4": "mp4",
|
|
22002
|
+
"video/webm": "webm",
|
|
22003
|
+
"font/woff": "woff",
|
|
22004
|
+
"font/woff2": "woff2",
|
|
22005
|
+
"font/ttf": "ttf",
|
|
22006
|
+
"font/otf": "otf",
|
|
22007
|
+
"text/html": "html",
|
|
22008
|
+
"text/plain": "txt",
|
|
22009
|
+
"text/css": "css",
|
|
22010
|
+
"text/csv": "csv"
|
|
22011
|
+
};
|
|
22012
|
+
const extensionForContentType = (contentType) => {
|
|
22013
|
+
const bare = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
22014
|
+
return CONTENT_TYPE_EXTENSIONS[bare] ?? "bin";
|
|
22015
|
+
};
|
|
22016
|
+
const readHeader = (headers, name) => {
|
|
22017
|
+
if (!headers) return void 0;
|
|
22018
|
+
const lowerTarget = name.toLowerCase();
|
|
22019
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
22020
|
+
if (key.toLowerCase() === lowerTarget) {
|
|
22021
|
+
return Array.isArray(value) ? value[0] : value;
|
|
22022
|
+
}
|
|
22023
|
+
}
|
|
22024
|
+
return void 0;
|
|
22025
|
+
};
|
|
22026
|
+
const parseContentDispositionFilename = (header) => {
|
|
22027
|
+
if (!header) return void 0;
|
|
22028
|
+
const extended = /filename\*\s*=\s*[^']*''([^;]+)/i.exec(header);
|
|
22029
|
+
if (extended?.[1]) {
|
|
22030
|
+
try {
|
|
22031
|
+
return decodeURIComponent(extended[1].trim()) || void 0;
|
|
22032
|
+
} catch {
|
|
22033
|
+
}
|
|
22034
|
+
}
|
|
22035
|
+
const basic2 = /filename\s*=\s*("([^"]*)"|([^;]+))/i.exec(header);
|
|
22036
|
+
const value = basic2?.[2] ?? basic2?.[3];
|
|
22037
|
+
return value?.trim() || void 0;
|
|
22038
|
+
};
|
|
22039
|
+
const filenameFromUrl = (url) => {
|
|
22040
|
+
try {
|
|
22041
|
+
const parsed = new URL(url);
|
|
22042
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
22043
|
+
const last = segments[segments.length - 1];
|
|
22044
|
+
return last && last.length > 0 ? last : void 0;
|
|
22045
|
+
} catch {
|
|
22046
|
+
return void 0;
|
|
22047
|
+
}
|
|
22048
|
+
};
|
|
22049
|
+
const deriveFilename = ({
|
|
22050
|
+
headers,
|
|
22051
|
+
url,
|
|
22052
|
+
contentType
|
|
22053
|
+
}) => {
|
|
22054
|
+
const fromDisposition = parseContentDispositionFilename(
|
|
22055
|
+
readHeader(headers, "Content-Disposition")
|
|
22056
|
+
);
|
|
22057
|
+
if (fromDisposition) return fromDisposition;
|
|
22058
|
+
const fromUrl = filenameFromUrl(url);
|
|
22059
|
+
if (fromUrl) return fromUrl;
|
|
22060
|
+
return `response.${extensionForContentType(contentType)}`;
|
|
22061
|
+
};
|
|
22062
|
+
const downloadBlob = (blob, filename) => {
|
|
22063
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
22064
|
+
const anchor = document.createElement("a");
|
|
22065
|
+
anchor.href = objectUrl;
|
|
22066
|
+
anchor.download = filename;
|
|
22067
|
+
document.body.appendChild(anchor);
|
|
22068
|
+
anchor.click();
|
|
22069
|
+
document.body.removeChild(anchor);
|
|
22070
|
+
setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
|
|
22071
|
+
};
|
|
22072
|
+
const downloadJson = (data, filename) => {
|
|
22073
|
+
downloadBlob(
|
|
22074
|
+
new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }),
|
|
22075
|
+
filename
|
|
22076
|
+
);
|
|
22077
|
+
};
|
|
22078
|
+
const EXPORT_SCHEMA_VERSION = 1;
|
|
22079
|
+
const getDuration = (duration) => duration ?? null;
|
|
22080
|
+
const serializeHttpEntry = (entry) => ({
|
|
22081
|
+
id: entry.id,
|
|
22082
|
+
type: "http",
|
|
22083
|
+
source: entry.source,
|
|
22084
|
+
timestamp: entry.timestamp,
|
|
22085
|
+
duration: getDuration(entry.duration),
|
|
22086
|
+
status: entry.status,
|
|
22087
|
+
error: entry.error,
|
|
22088
|
+
canceled: entry.canceled,
|
|
22089
|
+
request: entry.request,
|
|
22090
|
+
response: entry.response ?? null,
|
|
22091
|
+
size: entry.size ?? null,
|
|
22092
|
+
ttfb: entry.ttfb ?? null,
|
|
22093
|
+
initiator: entry.initiator,
|
|
22094
|
+
resourceType: entry.resourceType,
|
|
22095
|
+
progress: entry.progress
|
|
22096
|
+
});
|
|
22097
|
+
const serializeWebSocketEntry = (entry, websocketMessages) => ({
|
|
22098
|
+
id: entry.id,
|
|
22099
|
+
type: "websocket",
|
|
22100
|
+
source: entry.source,
|
|
22101
|
+
timestamp: entry.timestamp,
|
|
22102
|
+
duration: getDuration(entry.duration),
|
|
22103
|
+
status: entry.status,
|
|
22104
|
+
connection: entry.connection,
|
|
22105
|
+
error: entry.error,
|
|
22106
|
+
closeCode: entry.closeCode,
|
|
22107
|
+
closeReason: entry.closeReason,
|
|
22108
|
+
messages: websocketMessages.get(entry.id) ?? []
|
|
22109
|
+
});
|
|
22110
|
+
const serializeSSEEntry = (entry) => ({
|
|
22111
|
+
id: entry.id,
|
|
22112
|
+
type: "sse",
|
|
22113
|
+
source: entry.source,
|
|
22114
|
+
timestamp: entry.timestamp,
|
|
22115
|
+
duration: getDuration(entry.duration),
|
|
22116
|
+
status: entry.status,
|
|
22117
|
+
error: entry.error,
|
|
22118
|
+
request: entry.request,
|
|
22119
|
+
response: entry.response ?? null,
|
|
22120
|
+
initiator: entry.initiator,
|
|
22121
|
+
resourceType: entry.resourceType,
|
|
22122
|
+
messages: entry.messages
|
|
22123
|
+
});
|
|
22124
|
+
const serializeEntry = (entry, websocketMessages) => {
|
|
22125
|
+
switch (entry.type) {
|
|
22126
|
+
case "http":
|
|
22127
|
+
return serializeHttpEntry(entry);
|
|
22128
|
+
case "websocket":
|
|
22129
|
+
return serializeWebSocketEntry(entry, websocketMessages);
|
|
22130
|
+
case "sse":
|
|
22131
|
+
return serializeSSEEntry(entry);
|
|
22132
|
+
}
|
|
22133
|
+
};
|
|
22134
|
+
const createNetworkActivitySessionExport = (networkEntries, websocketMessages, exportedAt = /* @__PURE__ */ new Date()) => {
|
|
22135
|
+
const entries = Array.from(networkEntries.values()).sort((a, b2) => a.timestamp - b2.timestamp).map((entry) => serializeEntry(entry, websocketMessages));
|
|
22136
|
+
return {
|
|
22137
|
+
schemaVersion: EXPORT_SCHEMA_VERSION,
|
|
22138
|
+
tool: "rozenite-network-activity",
|
|
22139
|
+
exportedAt: exportedAt.toISOString(),
|
|
22140
|
+
summary: {
|
|
22141
|
+
totalEntries: entries.length,
|
|
22142
|
+
httpRequests: entries.filter((entry) => entry.type === "http").length,
|
|
22143
|
+
webSocketConnections: entries.filter(
|
|
22144
|
+
(entry) => entry.type === "websocket"
|
|
22145
|
+
).length,
|
|
22146
|
+
sseConnections: entries.filter((entry) => entry.type === "sse").length,
|
|
22147
|
+
realtimeMessages: entries.reduce((count2, entry) => {
|
|
22148
|
+
if (entry.type === "websocket" || entry.type === "sse") {
|
|
22149
|
+
return count2 + entry.messages.length;
|
|
22150
|
+
}
|
|
22151
|
+
return count2;
|
|
22152
|
+
}, 0)
|
|
22153
|
+
},
|
|
22154
|
+
entries
|
|
22155
|
+
};
|
|
22156
|
+
};
|
|
22157
|
+
const getNetworkActivitySessionExportFileName = (exportedAt = /* @__PURE__ */ new Date()) => {
|
|
22158
|
+
const timestamp = exportedAt.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:]/g, "-");
|
|
22159
|
+
return `rozenite-network-session-${timestamp}.json`;
|
|
22160
|
+
};
|
|
22161
|
+
const useNetworkActivitySessionExport = () => {
|
|
22162
|
+
const canExportSession = useNetworkActivityStore(
|
|
22163
|
+
(state) => state.networkEntries.size > 0
|
|
22164
|
+
);
|
|
22165
|
+
const exportSession = reactExports.useCallback(() => {
|
|
22166
|
+
const { networkEntries, websocketMessages } = store.getState();
|
|
22167
|
+
if (networkEntries.size === 0) {
|
|
22168
|
+
return;
|
|
22169
|
+
}
|
|
22170
|
+
const exportedAt = /* @__PURE__ */ new Date();
|
|
22171
|
+
const exportData = createNetworkActivitySessionExport(
|
|
22172
|
+
networkEntries,
|
|
22173
|
+
websocketMessages,
|
|
22174
|
+
exportedAt
|
|
22175
|
+
);
|
|
22176
|
+
downloadJson(
|
|
22177
|
+
exportData,
|
|
22178
|
+
getNetworkActivitySessionExportFileName(exportedAt)
|
|
22179
|
+
);
|
|
22180
|
+
}, []);
|
|
22181
|
+
return {
|
|
22182
|
+
canExportSession,
|
|
22183
|
+
exportSession
|
|
22184
|
+
};
|
|
22185
|
+
};
|
|
21942
22186
|
const Toolbar = () => {
|
|
21943
22187
|
const actions = useNetworkActivityActions();
|
|
21944
22188
|
const isRecording = useIsRecording();
|
|
22189
|
+
const { canExportSession, exportSession } = useNetworkActivitySessionExport();
|
|
21945
22190
|
const onToggleRecording = () => {
|
|
21946
22191
|
actions.setRecording(!isRecording);
|
|
21947
22192
|
};
|
|
@@ -21969,6 +22214,18 @@ const Toolbar = () => {
|
|
|
21969
22214
|
className: "h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
|
|
21970
22215
|
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { className: "h-4 w-4" })
|
|
21971
22216
|
}
|
|
22217
|
+
),
|
|
22218
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22219
|
+
Button,
|
|
22220
|
+
{
|
|
22221
|
+
variant: "ghost",
|
|
22222
|
+
size: "sm",
|
|
22223
|
+
onClick: exportSession,
|
|
22224
|
+
disabled: !canExportSession,
|
|
22225
|
+
className: "ml-auto h-8 w-8 p-0 text-gray-400 hover:text-blue-400",
|
|
22226
|
+
title: "Export session",
|
|
22227
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "h-4 w-4" })
|
|
22228
|
+
}
|
|
21972
22229
|
)
|
|
21973
22230
|
] });
|
|
21974
22231
|
};
|
|
@@ -24755,7 +25012,7 @@ const formatStartTime = (startTime) => {
|
|
|
24755
25012
|
const milliseconds = date.getMilliseconds().toString().padStart(3, "0");
|
|
24756
25013
|
return `${timeString}.${milliseconds}`;
|
|
24757
25014
|
};
|
|
24758
|
-
const extractDomainAndPath = (url) => {
|
|
25015
|
+
const extractDomainAndPath$1 = (url) => {
|
|
24759
25016
|
try {
|
|
24760
25017
|
const { hostname, pathname, search, hash: hash2, port } = new URL(url);
|
|
24761
25018
|
return {
|
|
@@ -24806,122 +25063,9 @@ const sortTime = (rowA, rowB, columnId) => {
|
|
|
24806
25063
|
};
|
|
24807
25064
|
return getNumericValue(a) - getNumericValue(b2);
|
|
24808
25065
|
};
|
|
24809
|
-
const parseThreshold = (value) => {
|
|
24810
|
-
const normalizedValue = value.trim();
|
|
24811
|
-
if (!normalizedValue) {
|
|
24812
|
-
return null;
|
|
24813
|
-
}
|
|
24814
|
-
const parsedValue = Number(normalizedValue);
|
|
24815
|
-
return Number.isFinite(parsedValue) ? parsedValue : null;
|
|
24816
|
-
};
|
|
24817
|
-
const matchesStatusFilter = (statusCode, statusFilter) => {
|
|
24818
|
-
const normalizedFilter = statusFilter.trim().toLowerCase();
|
|
24819
|
-
if (!normalizedFilter) {
|
|
24820
|
-
return true;
|
|
24821
|
-
}
|
|
24822
|
-
if (statusCode === void 0) {
|
|
24823
|
-
return false;
|
|
24824
|
-
}
|
|
24825
|
-
const statusRangeMatch = normalizedFilter.match(/^(\d{3})\s*-\s*(\d{3})$/);
|
|
24826
|
-
if (statusRangeMatch) {
|
|
24827
|
-
const min2 = Number(statusRangeMatch[1]);
|
|
24828
|
-
const max2 = Number(statusRangeMatch[2]);
|
|
24829
|
-
return statusCode >= min2 && statusCode <= max2;
|
|
24830
|
-
}
|
|
24831
|
-
const statusClassMatch = normalizedFilter.match(/^([1-5])xx$/);
|
|
24832
|
-
if (statusClassMatch) {
|
|
24833
|
-
return Math.floor(statusCode / 100) === Number(statusClassMatch[1]);
|
|
24834
|
-
}
|
|
24835
|
-
const comparisonMatch = normalizedFilter.match(/^(>=|<=|>|<)\s*(\d{3})$/);
|
|
24836
|
-
if (comparisonMatch) {
|
|
24837
|
-
const value = Number(comparisonMatch[2]);
|
|
24838
|
-
switch (comparisonMatch[1]) {
|
|
24839
|
-
case ">=":
|
|
24840
|
-
return statusCode >= value;
|
|
24841
|
-
case "<=":
|
|
24842
|
-
return statusCode <= value;
|
|
24843
|
-
case ">":
|
|
24844
|
-
return statusCode > value;
|
|
24845
|
-
case "<":
|
|
24846
|
-
return statusCode < value;
|
|
24847
|
-
}
|
|
24848
|
-
}
|
|
24849
|
-
return statusCode === Number(normalizedFilter);
|
|
24850
|
-
};
|
|
24851
|
-
const isInFlightStatus = (status) => {
|
|
24852
|
-
return ["pending", "loading", "connecting", "open"].includes(status);
|
|
24853
|
-
};
|
|
24854
|
-
const isFailedStatus = (status) => {
|
|
24855
|
-
return ["failed", "error"].includes(status);
|
|
24856
|
-
};
|
|
24857
|
-
const isHttpMethod = (method) => method !== "WS" && method !== "SSE";
|
|
24858
|
-
const filterNetworkRequests = (requests, filter) => {
|
|
24859
|
-
const searchText = filter.text.trim().toLowerCase();
|
|
24860
|
-
const domainFilter = filter.advanced.domain.trim().toLowerCase();
|
|
24861
|
-
const contentTypeFilter = filter.advanced.contentType.trim().toLowerCase();
|
|
24862
|
-
const minSize = parseThreshold(filter.advanced.minSize);
|
|
24863
|
-
const maxSize = parseThreshold(filter.advanced.maxSize);
|
|
24864
|
-
const minDuration = parseThreshold(filter.advanced.minDuration);
|
|
24865
|
-
const maxDuration = parseThreshold(filter.advanced.maxDuration);
|
|
24866
|
-
return requests.filter((request) => {
|
|
24867
|
-
if (filter.types.size > 0 && !filter.types.has(request.type)) {
|
|
24868
|
-
return false;
|
|
24869
|
-
}
|
|
24870
|
-
if (filter.advanced.methods.size > 0 && (!isHttpMethod(request.method) || !filter.advanced.methods.has(request.method))) {
|
|
24871
|
-
return false;
|
|
24872
|
-
}
|
|
24873
|
-
if (filter.advanced.sources.size > 0 && (!request.source || !filter.advanced.sources.has(request.source))) {
|
|
24874
|
-
return false;
|
|
24875
|
-
}
|
|
24876
|
-
if (!matchesStatusFilter(request.statusCode, filter.advanced.status)) {
|
|
24877
|
-
return false;
|
|
24878
|
-
}
|
|
24879
|
-
if (domainFilter && !request.domain.toLowerCase().includes(domainFilter)) {
|
|
24880
|
-
return false;
|
|
24881
|
-
}
|
|
24882
|
-
if (contentTypeFilter && !request.contentType?.toLowerCase().includes(contentTypeFilter)) {
|
|
24883
|
-
return false;
|
|
24884
|
-
}
|
|
24885
|
-
if (filter.advanced.failedOnly && !isFailedStatus(request.statusState)) {
|
|
24886
|
-
return false;
|
|
24887
|
-
}
|
|
24888
|
-
if (filter.advanced.inFlightOnly && !isInFlightStatus(request.statusState)) {
|
|
24889
|
-
return false;
|
|
24890
|
-
}
|
|
24891
|
-
if (filter.advanced.overriddenOnly && !request.hasOverride) {
|
|
24892
|
-
return false;
|
|
24893
|
-
}
|
|
24894
|
-
if (minSize !== null && (request.sizeBytes === null || request.sizeBytes < minSize)) {
|
|
24895
|
-
return false;
|
|
24896
|
-
}
|
|
24897
|
-
if (maxSize !== null && (request.sizeBytes === null || request.sizeBytes > maxSize)) {
|
|
24898
|
-
return false;
|
|
24899
|
-
}
|
|
24900
|
-
if (minDuration !== null && request.durationMs < minDuration) {
|
|
24901
|
-
return false;
|
|
24902
|
-
}
|
|
24903
|
-
if (maxDuration !== null && request.durationMs > maxDuration) {
|
|
24904
|
-
return false;
|
|
24905
|
-
}
|
|
24906
|
-
if (searchText) {
|
|
24907
|
-
const searchableFields = [
|
|
24908
|
-
request.name,
|
|
24909
|
-
request.method,
|
|
24910
|
-
request.status,
|
|
24911
|
-
request.domain,
|
|
24912
|
-
request.path,
|
|
24913
|
-
request.source,
|
|
24914
|
-
request.type,
|
|
24915
|
-
request.contentType
|
|
24916
|
-
].join(" ").toLowerCase();
|
|
24917
|
-
return searchableFields.includes(searchText);
|
|
24918
|
-
}
|
|
24919
|
-
return true;
|
|
24920
|
-
});
|
|
24921
|
-
};
|
|
24922
25066
|
const processNetworkRequests = (processedRequests, overrides, showEntirePathAsName = false) => {
|
|
24923
25067
|
return processedRequests.map((request) => {
|
|
24924
|
-
const { domain, path } = extractDomainAndPath(request.name);
|
|
25068
|
+
const { domain, path } = extractDomainAndPath$1(request.name);
|
|
24925
25069
|
const duration = request.duration || 0;
|
|
24926
25070
|
const hasOverride = overrides.has(request.name);
|
|
24927
25071
|
let statusDisplay = request.httpStatus || request.status;
|
|
@@ -24936,7 +25080,6 @@ const processNetworkRequests = (processedRequests, overrides, showEntirePathAsNa
|
|
|
24936
25080
|
name: generateName(request.name, showEntirePathAsName),
|
|
24937
25081
|
status: statusDisplay,
|
|
24938
25082
|
statusCode: request.httpStatus || void 0,
|
|
24939
|
-
statusState: request.status,
|
|
24940
25083
|
method: request.method,
|
|
24941
25084
|
domain,
|
|
24942
25085
|
path,
|
|
@@ -25002,21 +25145,19 @@ const columns$1 = [
|
|
|
25002
25145
|
sortingFn: sortTime
|
|
25003
25146
|
})
|
|
25004
25147
|
];
|
|
25005
|
-
const RequestList = ({
|
|
25148
|
+
const RequestList = ({ requests: filteredRequests }) => {
|
|
25006
25149
|
const actions = useNetworkActivityActions();
|
|
25007
|
-
const processedRequests = useProcessedRequests();
|
|
25008
25150
|
const selectedRequestId = useSelectedRequestId();
|
|
25009
25151
|
const [sorting, setSorting] = reactExports.useState([]);
|
|
25010
25152
|
const overrides = useOverrides();
|
|
25011
25153
|
const clientUISettings = useClientUISettings();
|
|
25012
25154
|
const requests = reactExports.useMemo(() => {
|
|
25013
|
-
|
|
25014
|
-
|
|
25155
|
+
return processNetworkRequests(
|
|
25156
|
+
filteredRequests,
|
|
25015
25157
|
overrides,
|
|
25016
25158
|
clientUISettings?.showUrlAsName
|
|
25017
25159
|
);
|
|
25018
|
-
|
|
25019
|
-
}, [processedRequests, overrides, clientUISettings?.showUrlAsName, filter]);
|
|
25160
|
+
}, [filteredRequests, overrides, clientUISettings?.showUrlAsName]);
|
|
25020
25161
|
const table = useReactTable({
|
|
25021
25162
|
data: requests,
|
|
25022
25163
|
columns: columns$1,
|
|
@@ -25879,7 +26020,7 @@ const TabsContent = reactExports.forwardRef(({ className, ...props }, ref) => /*
|
|
|
25879
26020
|
}
|
|
25880
26021
|
));
|
|
25881
26022
|
TabsContent.displayName = Content$1.displayName;
|
|
25882
|
-
function clamp$
|
|
26023
|
+
function clamp$2(value, [min2, max2]) {
|
|
25883
26024
|
return Math.min(max2, Math.max(min2, value));
|
|
25884
26025
|
}
|
|
25885
26026
|
function useStateMachine(initialState, machine) {
|
|
@@ -26529,7 +26670,7 @@ function getThumbOffsetFromScroll(scrollPos, sizes, dir = "ltr") {
|
|
|
26529
26670
|
const maxScrollPos = sizes.content - sizes.viewport;
|
|
26530
26671
|
const maxThumbPos = scrollbar - thumbSizePx;
|
|
26531
26672
|
const scrollClampRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0];
|
|
26532
|
-
const scrollWithoutMomentum = clamp$
|
|
26673
|
+
const scrollWithoutMomentum = clamp$2(scrollPos, scrollClampRange);
|
|
26533
26674
|
const interpolate = linearScale([0, maxScrollPos], [0, maxThumbPos]);
|
|
26534
26675
|
return interpolate(scrollWithoutMomentum);
|
|
26535
26676
|
}
|
|
@@ -27272,7 +27413,7 @@ const oppositeAlignmentMap = {
|
|
|
27272
27413
|
start: "end",
|
|
27273
27414
|
end: "start"
|
|
27274
27415
|
};
|
|
27275
|
-
function clamp(start, value, end) {
|
|
27416
|
+
function clamp$1(start, value, end) {
|
|
27276
27417
|
return max(start, min(value, end));
|
|
27277
27418
|
}
|
|
27278
27419
|
function evaluate(value, param) {
|
|
@@ -27623,7 +27764,7 @@ const arrow$3 = (options) => ({
|
|
|
27623
27764
|
const min$12 = minPadding;
|
|
27624
27765
|
const max2 = clientSize - arrowDimensions[length] - maxPadding;
|
|
27625
27766
|
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
|
|
27626
|
-
const offset2 = clamp(min$12, center, max2);
|
|
27767
|
+
const offset2 = clamp$1(min$12, center, max2);
|
|
27627
27768
|
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset2 && rects.reference[length] / 2 - (center < min$12 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
27628
27769
|
const alignmentOffset = shouldAddOffset ? center < min$12 ? center - min$12 : center - max2 : 0;
|
|
27629
27770
|
return {
|
|
@@ -27920,14 +28061,14 @@ const shift$2 = function(options) {
|
|
|
27920
28061
|
const maxSide = mainAxis === "y" ? "bottom" : "right";
|
|
27921
28062
|
const min2 = mainAxisCoord + overflow[minSide];
|
|
27922
28063
|
const max2 = mainAxisCoord - overflow[maxSide];
|
|
27923
|
-
mainAxisCoord = clamp(min2, mainAxisCoord, max2);
|
|
28064
|
+
mainAxisCoord = clamp$1(min2, mainAxisCoord, max2);
|
|
27924
28065
|
}
|
|
27925
28066
|
if (checkCrossAxis) {
|
|
27926
28067
|
const minSide = crossAxis === "y" ? "top" : "left";
|
|
27927
28068
|
const maxSide = crossAxis === "y" ? "bottom" : "right";
|
|
27928
28069
|
const min2 = crossAxisCoord + overflow[minSide];
|
|
27929
28070
|
const max2 = crossAxisCoord - overflow[maxSide];
|
|
27930
|
-
crossAxisCoord = clamp(min2, crossAxisCoord, max2);
|
|
28071
|
+
crossAxisCoord = clamp$1(min2, crossAxisCoord, max2);
|
|
27931
28072
|
}
|
|
27932
28073
|
const limitedCoords = limiter.fn({
|
|
27933
28074
|
...state,
|
|
@@ -38796,122 +38937,14 @@ const HexView = ({ bytes }) => {
|
|
|
38796
38937
|
}
|
|
38797
38938
|
) });
|
|
38798
38939
|
};
|
|
38799
|
-
const
|
|
38800
|
-
|
|
38801
|
-
|
|
38802
|
-
for (let i = 0; i < binary.length; i++) {
|
|
38803
|
-
bytes[i] = binary.charCodeAt(i);
|
|
38940
|
+
const formatBytes$1 = (bytes) => {
|
|
38941
|
+
if (bytes >= 1024 * 1024) {
|
|
38942
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
38804
38943
|
}
|
|
38805
|
-
|
|
38806
|
-
}
|
|
38807
|
-
const base64ToBlob = (base64, contentType) => {
|
|
38808
|
-
const binary = atob(base64);
|
|
38809
|
-
const buffer = new ArrayBuffer(binary.length);
|
|
38810
|
-
const view = new Uint8Array(buffer);
|
|
38811
|
-
for (let i = 0; i < binary.length; i++) {
|
|
38812
|
-
view[i] = binary.charCodeAt(i);
|
|
38944
|
+
if (bytes >= 1024) {
|
|
38945
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
38813
38946
|
}
|
|
38814
|
-
return
|
|
38815
|
-
type: contentType || "application/octet-stream"
|
|
38816
|
-
});
|
|
38817
|
-
};
|
|
38818
|
-
const CONTENT_TYPE_EXTENSIONS = {
|
|
38819
|
-
"application/pdf": "pdf",
|
|
38820
|
-
"application/zip": "zip",
|
|
38821
|
-
"application/gzip": "gz",
|
|
38822
|
-
"application/json": "json",
|
|
38823
|
-
"application/xml": "xml",
|
|
38824
|
-
"application/javascript": "js",
|
|
38825
|
-
"application/octet-stream": "bin",
|
|
38826
|
-
"image/png": "png",
|
|
38827
|
-
"image/jpeg": "jpg",
|
|
38828
|
-
"image/gif": "gif",
|
|
38829
|
-
"image/webp": "webp",
|
|
38830
|
-
"image/svg+xml": "svg",
|
|
38831
|
-
"image/bmp": "bmp",
|
|
38832
|
-
"image/x-icon": "ico",
|
|
38833
|
-
"audio/mpeg": "mp3",
|
|
38834
|
-
"audio/ogg": "ogg",
|
|
38835
|
-
"audio/wav": "wav",
|
|
38836
|
-
"video/mp4": "mp4",
|
|
38837
|
-
"video/webm": "webm",
|
|
38838
|
-
"font/woff": "woff",
|
|
38839
|
-
"font/woff2": "woff2",
|
|
38840
|
-
"font/ttf": "ttf",
|
|
38841
|
-
"font/otf": "otf",
|
|
38842
|
-
"text/html": "html",
|
|
38843
|
-
"text/plain": "txt",
|
|
38844
|
-
"text/css": "css",
|
|
38845
|
-
"text/csv": "csv"
|
|
38846
|
-
};
|
|
38847
|
-
const extensionForContentType = (contentType) => {
|
|
38848
|
-
const bare = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
38849
|
-
return CONTENT_TYPE_EXTENSIONS[bare] ?? "bin";
|
|
38850
|
-
};
|
|
38851
|
-
const readHeader = (headers, name) => {
|
|
38852
|
-
if (!headers) return void 0;
|
|
38853
|
-
const lowerTarget = name.toLowerCase();
|
|
38854
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
38855
|
-
if (key.toLowerCase() === lowerTarget) {
|
|
38856
|
-
return Array.isArray(value) ? value[0] : value;
|
|
38857
|
-
}
|
|
38858
|
-
}
|
|
38859
|
-
return void 0;
|
|
38860
|
-
};
|
|
38861
|
-
const parseContentDispositionFilename = (header) => {
|
|
38862
|
-
if (!header) return void 0;
|
|
38863
|
-
const extended = /filename\*\s*=\s*[^']*''([^;]+)/i.exec(header);
|
|
38864
|
-
if (extended?.[1]) {
|
|
38865
|
-
try {
|
|
38866
|
-
return decodeURIComponent(extended[1].trim()) || void 0;
|
|
38867
|
-
} catch {
|
|
38868
|
-
}
|
|
38869
|
-
}
|
|
38870
|
-
const basic2 = /filename\s*=\s*("([^"]*)"|([^;]+))/i.exec(header);
|
|
38871
|
-
const value = basic2?.[2] ?? basic2?.[3];
|
|
38872
|
-
return value?.trim() || void 0;
|
|
38873
|
-
};
|
|
38874
|
-
const filenameFromUrl = (url) => {
|
|
38875
|
-
try {
|
|
38876
|
-
const parsed = new URL(url);
|
|
38877
|
-
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
38878
|
-
const last = segments[segments.length - 1];
|
|
38879
|
-
return last && last.length > 0 ? last : void 0;
|
|
38880
|
-
} catch {
|
|
38881
|
-
return void 0;
|
|
38882
|
-
}
|
|
38883
|
-
};
|
|
38884
|
-
const deriveFilename = ({
|
|
38885
|
-
headers,
|
|
38886
|
-
url,
|
|
38887
|
-
contentType
|
|
38888
|
-
}) => {
|
|
38889
|
-
const fromDisposition = parseContentDispositionFilename(
|
|
38890
|
-
readHeader(headers, "Content-Disposition")
|
|
38891
|
-
);
|
|
38892
|
-
if (fromDisposition) return fromDisposition;
|
|
38893
|
-
const fromUrl = filenameFromUrl(url);
|
|
38894
|
-
if (fromUrl) return fromUrl;
|
|
38895
|
-
return `response.${extensionForContentType(contentType)}`;
|
|
38896
|
-
};
|
|
38897
|
-
const downloadBlob = (blob, filename) => {
|
|
38898
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
38899
|
-
const anchor = document.createElement("a");
|
|
38900
|
-
anchor.href = objectUrl;
|
|
38901
|
-
anchor.download = filename;
|
|
38902
|
-
document.body.appendChild(anchor);
|
|
38903
|
-
anchor.click();
|
|
38904
|
-
document.body.removeChild(anchor);
|
|
38905
|
-
setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
|
|
38906
|
-
};
|
|
38907
|
-
const formatBytes$1 = (bytes) => {
|
|
38908
|
-
if (bytes >= 1024 * 1024) {
|
|
38909
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
38910
|
-
}
|
|
38911
|
-
if (bytes >= 1024) {
|
|
38912
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
38913
|
-
}
|
|
38914
|
-
return `${bytes} bytes`;
|
|
38947
|
+
return `${bytes} bytes`;
|
|
38915
38948
|
};
|
|
38916
38949
|
const decodedByteCount = (base64) => {
|
|
38917
38950
|
const padding = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
|
|
@@ -41730,19 +41763,14 @@ const Input = reactExports.forwardRef(
|
|
|
41730
41763
|
}
|
|
41731
41764
|
);
|
|
41732
41765
|
Input.displayName = "Input";
|
|
41733
|
-
const
|
|
41734
|
-
|
|
41735
|
-
"
|
|
41736
|
-
"
|
|
41737
|
-
"PUT",
|
|
41738
|
-
"PATCH",
|
|
41739
|
-
"DELETE",
|
|
41740
|
-
"HEAD"
|
|
41766
|
+
const DEFAULT_REQUEST_TYPES = [
|
|
41767
|
+
"http",
|
|
41768
|
+
"websocket",
|
|
41769
|
+
"sse"
|
|
41741
41770
|
];
|
|
41742
|
-
const SOURCES = ["builtin", "nitro"];
|
|
41743
41771
|
const createDefaultFilter = () => ({
|
|
41744
41772
|
text: "",
|
|
41745
|
-
types:
|
|
41773
|
+
types: new Set(DEFAULT_REQUEST_TYPES),
|
|
41746
41774
|
advanced: {
|
|
41747
41775
|
methods: /* @__PURE__ */ new Set(),
|
|
41748
41776
|
sources: /* @__PURE__ */ new Set(),
|
|
@@ -41758,6 +41786,15 @@ const createDefaultFilter = () => ({
|
|
|
41758
41786
|
maxDuration: ""
|
|
41759
41787
|
}
|
|
41760
41788
|
});
|
|
41789
|
+
const HTTP_METHODS = [
|
|
41790
|
+
"GET",
|
|
41791
|
+
"POST",
|
|
41792
|
+
"PUT",
|
|
41793
|
+
"PATCH",
|
|
41794
|
+
"DELETE",
|
|
41795
|
+
"HEAD"
|
|
41796
|
+
];
|
|
41797
|
+
const SOURCES = ["builtin", "nitro"];
|
|
41761
41798
|
const getTypeLabel = (type) => {
|
|
41762
41799
|
switch (type) {
|
|
41763
41800
|
case "http":
|
|
@@ -41793,7 +41830,7 @@ const getAdvancedFilterCount = (advanced) => {
|
|
|
41793
41830
|
].filter(Boolean).length;
|
|
41794
41831
|
};
|
|
41795
41832
|
const getActiveFilterCount = (filter) => {
|
|
41796
|
-
const typeFilterCount = filter.types.size
|
|
41833
|
+
const typeFilterCount = filter.types.size < DEFAULT_REQUEST_TYPES.length ? 1 : 0;
|
|
41797
41834
|
return typeFilterCount + getAdvancedFilterCount(filter.advanced);
|
|
41798
41835
|
};
|
|
41799
41836
|
const FilterField = ({
|
|
@@ -41904,7 +41941,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
|
|
|
41904
41941
|
onFilterChange(createDefaultFilter());
|
|
41905
41942
|
};
|
|
41906
41943
|
const activeFilterCount = getActiveFilterCount(filter);
|
|
41907
|
-
const hasActiveFilters = filter.text !== "" || activeFilterCount > 0;
|
|
41944
|
+
const hasActiveFilters = filter.text.trim() !== "" || activeFilterCount > 0;
|
|
41908
41945
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 p-2 border-b border-gray-700 bg-gray-800", children: [
|
|
41909
41946
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
41910
41947
|
Input,
|
|
@@ -41940,7 +41977,7 @@ const FilterBar = ({ filter, onFilterChange }) => {
|
|
|
41940
41977
|
...getFloatingProps(),
|
|
41941
41978
|
children: [
|
|
41942
41979
|
/* @__PURE__ */ jsxRuntimeExports.jsx(FilterPanelLabel, { children: "Request Type" }),
|
|
41943
|
-
|
|
41980
|
+
DEFAULT_REQUEST_TYPES.map((type) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
41944
41981
|
FilterCheckbox,
|
|
41945
41982
|
{
|
|
41946
41983
|
checked: filter.types.has(type),
|
|
@@ -42077,14 +42114,692 @@ const FilterBar = ({ filter, onFilterChange }) => {
|
|
|
42077
42114
|
)
|
|
42078
42115
|
] });
|
|
42079
42116
|
};
|
|
42117
|
+
const TIMELINE_LAYOUT = {
|
|
42118
|
+
minVisibleBarPercent: 0.65,
|
|
42119
|
+
minRangeMs: 1e3,
|
|
42120
|
+
liveRefreshMs: 1e3,
|
|
42121
|
+
maxRenderedRequests: 1e3,
|
|
42122
|
+
laneCount: 8,
|
|
42123
|
+
laneHeightPx: 2,
|
|
42124
|
+
laneGapPx: 6,
|
|
42125
|
+
laneHitTargetHeightPx: 8,
|
|
42126
|
+
rulerHeightPx: 22,
|
|
42127
|
+
laneTopPx: 32,
|
|
42128
|
+
laneBottomPaddingPx: 18,
|
|
42129
|
+
tickTargetCount: 7,
|
|
42130
|
+
minTickLabelGapPercent: 6,
|
|
42131
|
+
rangePaddingRatio: 0.025,
|
|
42132
|
+
minRangePaddingMs: 25,
|
|
42133
|
+
streamingRequestMaxDurationMs: 5e3
|
|
42134
|
+
};
|
|
42135
|
+
const NICE_TICK_FACTORS = [1, 2, 2.5, 5, 10];
|
|
42136
|
+
const ACTIVE_HTTP_STATUSES = /* @__PURE__ */ new Set([
|
|
42137
|
+
"pending",
|
|
42138
|
+
"loading"
|
|
42139
|
+
]);
|
|
42140
|
+
const ACTIVE_WEBSOCKET_STATUSES = /* @__PURE__ */ new Set([
|
|
42141
|
+
"connecting",
|
|
42142
|
+
"open",
|
|
42143
|
+
"closing"
|
|
42144
|
+
]);
|
|
42145
|
+
const ACTIVE_SSE_STATUSES = /* @__PURE__ */ new Set([
|
|
42146
|
+
"connecting",
|
|
42147
|
+
"open"
|
|
42148
|
+
]);
|
|
42149
|
+
const clamp = (value, minimum, maximum) => {
|
|
42150
|
+
return Math.min(Math.max(value, minimum), maximum);
|
|
42151
|
+
};
|
|
42152
|
+
const getTimelineChartHeight = (layout = TIMELINE_LAYOUT) => {
|
|
42153
|
+
return layout.laneTopPx + layout.laneCount * layout.laneHeightPx + (layout.laneCount - 1) * layout.laneGapPx + layout.laneBottomPaddingPx;
|
|
42154
|
+
};
|
|
42155
|
+
const getTimelineLaneTop = (lane, layout = TIMELINE_LAYOUT) => {
|
|
42156
|
+
return lane * (layout.laneHeightPx + layout.laneGapPx) + layout.laneTopPx;
|
|
42157
|
+
};
|
|
42158
|
+
const getTimelineTrackTop = (lane, layout = TIMELINE_LAYOUT) => {
|
|
42159
|
+
const visualBarTop = getTimelineLaneTop(lane, layout);
|
|
42160
|
+
return visualBarTop - (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
|
|
42161
|
+
};
|
|
42162
|
+
const getTimelineBarTopOffset = (layout = TIMELINE_LAYOUT) => {
|
|
42163
|
+
return (layout.laneHitTargetHeightPx - layout.laneHeightPx) / 2;
|
|
42164
|
+
};
|
|
42165
|
+
const isRequestActive = (request) => {
|
|
42166
|
+
switch (request.type) {
|
|
42167
|
+
case "http":
|
|
42168
|
+
return ACTIVE_HTTP_STATUSES.has(request.status);
|
|
42169
|
+
case "websocket":
|
|
42170
|
+
return ACTIVE_WEBSOCKET_STATUSES.has(request.status);
|
|
42171
|
+
case "sse":
|
|
42172
|
+
return ACTIVE_SSE_STATUSES.has(request.status);
|
|
42173
|
+
}
|
|
42174
|
+
};
|
|
42175
|
+
const formatTimelineOffset = (milliseconds) => {
|
|
42176
|
+
if (milliseconds < 1e3) {
|
|
42177
|
+
return `${Math.round(milliseconds)} ms`;
|
|
42178
|
+
}
|
|
42179
|
+
if (milliseconds < 6e4) {
|
|
42180
|
+
return `${(milliseconds / 1e3).toFixed(milliseconds < 1e4 ? 1 : 0)} s`;
|
|
42181
|
+
}
|
|
42182
|
+
const totalSeconds = Math.round(milliseconds / 1e3);
|
|
42183
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
42184
|
+
const seconds = totalSeconds % 60;
|
|
42185
|
+
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
42186
|
+
};
|
|
42187
|
+
const getRequestEndTime = (request, now) => {
|
|
42188
|
+
if (typeof request.duration === "number") {
|
|
42189
|
+
return request.timestamp + Math.max(request.duration, 0);
|
|
42190
|
+
}
|
|
42191
|
+
if (isRequestActive(request)) {
|
|
42192
|
+
return Math.max(now, request.timestamp);
|
|
42193
|
+
}
|
|
42194
|
+
return request.timestamp;
|
|
42195
|
+
};
|
|
42196
|
+
const getTimelineRequestEndTime = (request, now, layout = TIMELINE_LAYOUT) => {
|
|
42197
|
+
const endTime = getRequestEndTime(request, now);
|
|
42198
|
+
if (request.type !== "websocket" && request.type !== "sse") {
|
|
42199
|
+
return endTime;
|
|
42200
|
+
}
|
|
42201
|
+
return Math.min(
|
|
42202
|
+
endTime,
|
|
42203
|
+
request.timestamp + layout.streamingRequestMaxDurationMs
|
|
42204
|
+
);
|
|
42205
|
+
};
|
|
42206
|
+
const requestOverlapsTimelineRange = (request, range, now, layout = TIMELINE_LAYOUT) => {
|
|
42207
|
+
const rangeStart = Math.min(range.startTime, range.endTime);
|
|
42208
|
+
const rangeEnd = Math.max(range.startTime, range.endTime);
|
|
42209
|
+
const requestStart = request.timestamp;
|
|
42210
|
+
const requestEnd = getTimelineRequestEndTime(request, now, layout);
|
|
42211
|
+
return requestStart <= rangeEnd && requestEnd >= rangeStart;
|
|
42212
|
+
};
|
|
42213
|
+
const getNiceTickStep = (rangeDuration, targetTickCount) => {
|
|
42214
|
+
const targetStep = rangeDuration / targetTickCount;
|
|
42215
|
+
const exponent = Math.floor(Math.log10(targetStep));
|
|
42216
|
+
const magnitude = 10 ** exponent;
|
|
42217
|
+
const normalizedStep = targetStep / magnitude;
|
|
42218
|
+
const factor = NICE_TICK_FACTORS.find((candidate) => candidate >= normalizedStep) ?? NICE_TICK_FACTORS[NICE_TICK_FACTORS.length - 1];
|
|
42219
|
+
return factor * magnitude;
|
|
42220
|
+
};
|
|
42221
|
+
const getTimelineTicks = (rangeDuration, layout = TIMELINE_LAYOUT) => {
|
|
42222
|
+
const step = getNiceTickStep(rangeDuration, layout.tickTargetCount);
|
|
42223
|
+
const ticks = [];
|
|
42224
|
+
for (let value = 0; value <= rangeDuration; value += step) {
|
|
42225
|
+
ticks.push({
|
|
42226
|
+
label: formatTimelineOffset(value),
|
|
42227
|
+
offsetPercent: value / rangeDuration * 100
|
|
42228
|
+
});
|
|
42229
|
+
}
|
|
42230
|
+
if (ticks.length === 0 || ticks[ticks.length - 1].offsetPercent < 100 - Number.EPSILON) {
|
|
42231
|
+
const finalTick = {
|
|
42232
|
+
label: formatTimelineOffset(rangeDuration),
|
|
42233
|
+
offsetPercent: 100
|
|
42234
|
+
};
|
|
42235
|
+
const previousTick = ticks[ticks.length - 1];
|
|
42236
|
+
if (!previousTick || finalTick.label !== previousTick.label && finalTick.offsetPercent - previousTick.offsetPercent >= layout.minTickLabelGapPercent) {
|
|
42237
|
+
ticks.push(finalTick);
|
|
42238
|
+
}
|
|
42239
|
+
}
|
|
42240
|
+
return ticks;
|
|
42241
|
+
};
|
|
42242
|
+
const getTimelineBounds = (requests, now, layout) => {
|
|
42243
|
+
return requests.reduce(
|
|
42244
|
+
(result, request) => {
|
|
42245
|
+
const endTime = getTimelineRequestEndTime(request, now, layout);
|
|
42246
|
+
return {
|
|
42247
|
+
start: Math.min(result.start, request.timestamp),
|
|
42248
|
+
end: Math.max(result.end, endTime)
|
|
42249
|
+
};
|
|
42250
|
+
},
|
|
42251
|
+
{
|
|
42252
|
+
start: Number.POSITIVE_INFINITY,
|
|
42253
|
+
end: Number.NEGATIVE_INFINITY
|
|
42254
|
+
}
|
|
42255
|
+
);
|
|
42256
|
+
};
|
|
42257
|
+
const getEarliestLaneIndex = (laneEndTimes) => {
|
|
42258
|
+
return laneEndTimes.reduce((earliestIndex, laneEndTime, index2) => {
|
|
42259
|
+
return laneEndTime < laneEndTimes[earliestIndex] ? index2 : earliestIndex;
|
|
42260
|
+
}, 0);
|
|
42261
|
+
};
|
|
42262
|
+
const getRenderableRequests = (requests, layout) => {
|
|
42263
|
+
if (requests.length <= layout.maxRenderedRequests) {
|
|
42264
|
+
return requests;
|
|
42265
|
+
}
|
|
42266
|
+
return [...requests].sort((a, b2) => b2.timestamp - a.timestamp).slice(0, layout.maxRenderedRequests);
|
|
42267
|
+
};
|
|
42268
|
+
const getTimelineModel = (requests, now, layout = TIMELINE_LAYOUT) => {
|
|
42269
|
+
const renderableRequests = getRenderableRequests(requests, layout);
|
|
42270
|
+
const hiddenRequestCount = requests.length - renderableRequests.length;
|
|
42271
|
+
if (renderableRequests.length === 0) {
|
|
42272
|
+
return {
|
|
42273
|
+
rows: [],
|
|
42274
|
+
ticks: getTimelineTicks(layout.minRangeMs, layout),
|
|
42275
|
+
rangeStart: 0,
|
|
42276
|
+
rangeDuration: layout.minRangeMs,
|
|
42277
|
+
chartHeight: getTimelineChartHeight(layout),
|
|
42278
|
+
totalRequestCount: requests.length,
|
|
42279
|
+
hiddenRequestCount
|
|
42280
|
+
};
|
|
42281
|
+
}
|
|
42282
|
+
const bounds = getTimelineBounds(renderableRequests, now, layout);
|
|
42283
|
+
const rawRangeDuration = Math.max(
|
|
42284
|
+
bounds.end - bounds.start,
|
|
42285
|
+
layout.minRangeMs
|
|
42286
|
+
);
|
|
42287
|
+
const rangePadding = Math.max(
|
|
42288
|
+
rawRangeDuration * layout.rangePaddingRatio,
|
|
42289
|
+
layout.minRangePaddingMs
|
|
42290
|
+
);
|
|
42291
|
+
const rangeStart = bounds.start - rangePadding;
|
|
42292
|
+
const rangeDuration = rawRangeDuration + rangePadding * 2;
|
|
42293
|
+
const laneEndTimes = Array.from({ length: layout.laneCount }, () => 0);
|
|
42294
|
+
const rows = [...renderableRequests].sort((a, b2) => a.timestamp - b2.timestamp).map((request) => {
|
|
42295
|
+
const startTime = request.timestamp;
|
|
42296
|
+
const endTime = getTimelineRequestEndTime(request, now, layout);
|
|
42297
|
+
const duration = Math.max(endTime - startTime, 0);
|
|
42298
|
+
const offsetPercent = clamp(
|
|
42299
|
+
(startTime - rangeStart) / rangeDuration * 100,
|
|
42300
|
+
0,
|
|
42301
|
+
100 - layout.minVisibleBarPercent
|
|
42302
|
+
);
|
|
42303
|
+
const widthPercent = Math.min(
|
|
42304
|
+
Math.max(duration / rangeDuration * 100, layout.minVisibleBarPercent),
|
|
42305
|
+
100 - offsetPercent
|
|
42306
|
+
);
|
|
42307
|
+
const ttfb = clamp(request.ttfb ?? 0, 0, duration);
|
|
42308
|
+
const ttfbPercent = duration === 0 ? 0 : ttfb / duration * 100;
|
|
42309
|
+
const receivePercent = Math.max(100 - ttfbPercent, 0);
|
|
42310
|
+
const availableLane = laneEndTimes.findIndex(
|
|
42311
|
+
(laneEndTime) => laneEndTime <= startTime
|
|
42312
|
+
);
|
|
42313
|
+
const isOverflowingLane = availableLane === -1;
|
|
42314
|
+
const lane = isOverflowingLane ? getEarliestLaneIndex(laneEndTimes) : availableLane;
|
|
42315
|
+
laneEndTimes[lane] = Math.max(laneEndTimes[lane], endTime);
|
|
42316
|
+
return {
|
|
42317
|
+
request,
|
|
42318
|
+
offsetPercent,
|
|
42319
|
+
widthPercent,
|
|
42320
|
+
duration,
|
|
42321
|
+
ttfbPercent,
|
|
42322
|
+
receivePercent,
|
|
42323
|
+
isActive: isRequestActive(request),
|
|
42324
|
+
lane,
|
|
42325
|
+
isOverflowingLane
|
|
42326
|
+
};
|
|
42327
|
+
});
|
|
42328
|
+
return {
|
|
42329
|
+
rows,
|
|
42330
|
+
ticks: getTimelineTicks(rangeDuration, layout),
|
|
42331
|
+
rangeStart,
|
|
42332
|
+
rangeDuration,
|
|
42333
|
+
chartHeight: getTimelineChartHeight(layout),
|
|
42334
|
+
totalRequestCount: requests.length,
|
|
42335
|
+
hiddenRequestCount
|
|
42336
|
+
};
|
|
42337
|
+
};
|
|
42338
|
+
const REQUEST_TIMELINE_COLORS = {
|
|
42339
|
+
error: "bg-red-400",
|
|
42340
|
+
primary: "bg-gray-400",
|
|
42341
|
+
active: "bg-gray-500",
|
|
42342
|
+
httpTtfb: "bg-gray-200"
|
|
42343
|
+
};
|
|
42344
|
+
const getPrimaryBarClassName = (request) => {
|
|
42345
|
+
if (request.status === "failed" || request.status === "error") {
|
|
42346
|
+
return REQUEST_TIMELINE_COLORS.error;
|
|
42347
|
+
}
|
|
42348
|
+
return REQUEST_TIMELINE_COLORS.primary;
|
|
42349
|
+
};
|
|
42350
|
+
const getStyle = (offsetPercent, widthPercent) => ({
|
|
42351
|
+
left: `${offsetPercent}%`,
|
|
42352
|
+
width: `${widthPercent}%`
|
|
42353
|
+
});
|
|
42354
|
+
const GridLines = ({ ticks }) => {
|
|
42355
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pointer-events-none absolute inset-0", children: ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42356
|
+
"div",
|
|
42357
|
+
{
|
|
42358
|
+
className: "absolute inset-y-0 border-l border-gray-800",
|
|
42359
|
+
style: { left: `${tick.offsetPercent}%` }
|
|
42360
|
+
},
|
|
42361
|
+
`${tick.label}-${tick.offsetPercent}`
|
|
42362
|
+
)) });
|
|
42363
|
+
};
|
|
42364
|
+
const getTickLabelStyle = (tick) => {
|
|
42365
|
+
if (tick.offsetPercent === 0) {
|
|
42366
|
+
return {
|
|
42367
|
+
left: 4
|
|
42368
|
+
};
|
|
42369
|
+
}
|
|
42370
|
+
if (tick.offsetPercent === 100) {
|
|
42371
|
+
return {
|
|
42372
|
+
right: 4
|
|
42373
|
+
};
|
|
42374
|
+
}
|
|
42375
|
+
return {
|
|
42376
|
+
left: `${tick.offsetPercent}%`
|
|
42377
|
+
};
|
|
42378
|
+
};
|
|
42379
|
+
const TimelineTrack = ({
|
|
42380
|
+
row,
|
|
42381
|
+
isSelected,
|
|
42382
|
+
onSelect,
|
|
42383
|
+
shouldSuppressSelect
|
|
42384
|
+
}) => {
|
|
42385
|
+
const primaryBarClassName = row.isActive ? REQUEST_TIMELINE_COLORS.active : getPrimaryBarClassName(row.request);
|
|
42386
|
+
const isSplitHttpBar = row.request.type === "http" && row.ttfbPercent > 0 && row.receivePercent > 0;
|
|
42387
|
+
const trackTop = getTimelineTrackTop(row.lane);
|
|
42388
|
+
const barTop = getTimelineBarTopOffset();
|
|
42389
|
+
const positionStyle = {
|
|
42390
|
+
...getStyle(row.offsetPercent, row.widthPercent),
|
|
42391
|
+
top: trackTop
|
|
42392
|
+
};
|
|
42393
|
+
const durationLabel = row.isActive ? `${formatTimelineOffset(row.duration)}+` : formatTimelineOffset(row.duration);
|
|
42394
|
+
const label = `${row.request.method} ${row.request.name} - ${durationLabel}`;
|
|
42395
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42396
|
+
"button",
|
|
42397
|
+
{
|
|
42398
|
+
type: "button",
|
|
42399
|
+
"aria-label": label,
|
|
42400
|
+
title: label,
|
|
42401
|
+
"data-timeline-track": "true",
|
|
42402
|
+
className: "absolute rounded-sm text-left transition-opacity hover:opacity-80",
|
|
42403
|
+
style: {
|
|
42404
|
+
...positionStyle,
|
|
42405
|
+
height: TIMELINE_LAYOUT.laneHitTargetHeightPx
|
|
42406
|
+
},
|
|
42407
|
+
onClick: (event) => {
|
|
42408
|
+
if (shouldSuppressSelect()) {
|
|
42409
|
+
event.preventDefault();
|
|
42410
|
+
event.stopPropagation();
|
|
42411
|
+
return;
|
|
42412
|
+
}
|
|
42413
|
+
onSelect(row.request.id);
|
|
42414
|
+
},
|
|
42415
|
+
children: isSplitHttpBar ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
42416
|
+
"div",
|
|
42417
|
+
{
|
|
42418
|
+
className: `absolute flex w-full overflow-hidden rounded-sm ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
|
|
42419
|
+
style: {
|
|
42420
|
+
top: barTop,
|
|
42421
|
+
height: TIMELINE_LAYOUT.laneHeightPx
|
|
42422
|
+
},
|
|
42423
|
+
children: [
|
|
42424
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42425
|
+
"div",
|
|
42426
|
+
{
|
|
42427
|
+
className: `h-full ${REQUEST_TIMELINE_COLORS.httpTtfb}`,
|
|
42428
|
+
style: { width: `${row.ttfbPercent}%` }
|
|
42429
|
+
}
|
|
42430
|
+
),
|
|
42431
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42432
|
+
"div",
|
|
42433
|
+
{
|
|
42434
|
+
className: `h-full ${REQUEST_TIMELINE_COLORS.primary}`,
|
|
42435
|
+
style: { width: `${row.receivePercent}%` }
|
|
42436
|
+
}
|
|
42437
|
+
)
|
|
42438
|
+
]
|
|
42439
|
+
}
|
|
42440
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42441
|
+
"div",
|
|
42442
|
+
{
|
|
42443
|
+
className: `absolute w-full rounded-sm ${primaryBarClassName} ${isSelected ? "ring-1 ring-blue-300 ring-offset-1 ring-offset-gray-950" : ""}`,
|
|
42444
|
+
style: {
|
|
42445
|
+
top: barTop,
|
|
42446
|
+
height: TIMELINE_LAYOUT.laneHeightPx
|
|
42447
|
+
}
|
|
42448
|
+
}
|
|
42449
|
+
)
|
|
42450
|
+
}
|
|
42451
|
+
);
|
|
42452
|
+
};
|
|
42453
|
+
const clampPercent = (value) => Math.min(Math.max(value, 0), 100);
|
|
42454
|
+
const getPointerPercent = (event, element) => {
|
|
42455
|
+
const rect = element.getBoundingClientRect();
|
|
42456
|
+
if (rect.width === 0) {
|
|
42457
|
+
return 0;
|
|
42458
|
+
}
|
|
42459
|
+
return clampPercent((event.clientX - rect.left) / rect.width * 100);
|
|
42460
|
+
};
|
|
42461
|
+
const getSelectionStyle = (range, timeline) => {
|
|
42462
|
+
const startPercent = clampPercent(
|
|
42463
|
+
(range.startTime - timeline.rangeStart) / timeline.rangeDuration * 100
|
|
42464
|
+
);
|
|
42465
|
+
const endPercent = clampPercent(
|
|
42466
|
+
(range.endTime - timeline.rangeStart) / timeline.rangeDuration * 100
|
|
42467
|
+
);
|
|
42468
|
+
const left2 = Math.min(startPercent, endPercent);
|
|
42469
|
+
const width = Math.abs(endPercent - startPercent);
|
|
42470
|
+
return {
|
|
42471
|
+
left: `${left2}%`,
|
|
42472
|
+
width: `${width}%`,
|
|
42473
|
+
top: TIMELINE_LAYOUT.rulerHeightPx
|
|
42474
|
+
};
|
|
42475
|
+
};
|
|
42476
|
+
const getDraftSelectionStyle = (draft) => {
|
|
42477
|
+
const left2 = Math.min(draft.anchorPercent, draft.currentPercent);
|
|
42478
|
+
const width = Math.abs(draft.currentPercent - draft.anchorPercent);
|
|
42479
|
+
return {
|
|
42480
|
+
left: `${left2}%`,
|
|
42481
|
+
width: `${width}%`,
|
|
42482
|
+
top: TIMELINE_LAYOUT.rulerHeightPx
|
|
42483
|
+
};
|
|
42484
|
+
};
|
|
42485
|
+
const NetworkTimeline = ({
|
|
42486
|
+
requests,
|
|
42487
|
+
selection,
|
|
42488
|
+
filteredRequestCount,
|
|
42489
|
+
onSelectionChange
|
|
42490
|
+
}) => {
|
|
42491
|
+
const actions = useNetworkActivityActions();
|
|
42492
|
+
const selectedRequestId = useSelectedRequestId();
|
|
42493
|
+
const [now, setNow] = reactExports.useState(() => Date.now());
|
|
42494
|
+
const [draftSelection, setDraftSelection] = reactExports.useState(
|
|
42495
|
+
null
|
|
42496
|
+
);
|
|
42497
|
+
const chartRef = reactExports.useRef(null);
|
|
42498
|
+
const suppressTrackClickRef = reactExports.useRef(false);
|
|
42499
|
+
const hasActiveRequests = requests.some(isRequestActive);
|
|
42500
|
+
reactExports.useEffect(() => {
|
|
42501
|
+
if (!hasActiveRequests) {
|
|
42502
|
+
return;
|
|
42503
|
+
}
|
|
42504
|
+
const interval = window.setInterval(() => {
|
|
42505
|
+
setNow(Date.now());
|
|
42506
|
+
}, TIMELINE_LAYOUT.liveRefreshMs);
|
|
42507
|
+
return () => window.clearInterval(interval);
|
|
42508
|
+
}, [hasActiveRequests]);
|
|
42509
|
+
const timeline = reactExports.useMemo(() => {
|
|
42510
|
+
return getTimelineModel(requests, now);
|
|
42511
|
+
}, [requests, now]);
|
|
42512
|
+
const onRequestSelect = (requestId) => {
|
|
42513
|
+
actions.setSelectedRequest(requestId);
|
|
42514
|
+
};
|
|
42515
|
+
const onPointerDown = (event) => {
|
|
42516
|
+
if (event.button !== 0 || requests.length === 0) {
|
|
42517
|
+
return;
|
|
42518
|
+
}
|
|
42519
|
+
const chartElement = chartRef.current;
|
|
42520
|
+
if (!chartElement) {
|
|
42521
|
+
return;
|
|
42522
|
+
}
|
|
42523
|
+
const percent = getPointerPercent(event, chartElement);
|
|
42524
|
+
const target = event.target;
|
|
42525
|
+
const startedOnTrack = target instanceof Element && target.closest('[data-timeline-track="true"]') !== null;
|
|
42526
|
+
setDraftSelection({
|
|
42527
|
+
anchorPercent: percent,
|
|
42528
|
+
currentPercent: percent,
|
|
42529
|
+
startedOnTrack
|
|
42530
|
+
});
|
|
42531
|
+
chartElement.setPointerCapture(event.pointerId);
|
|
42532
|
+
};
|
|
42533
|
+
const onPointerMove = (event) => {
|
|
42534
|
+
if (!draftSelection) {
|
|
42535
|
+
return;
|
|
42536
|
+
}
|
|
42537
|
+
const chartElement = chartRef.current;
|
|
42538
|
+
if (!chartElement) {
|
|
42539
|
+
return;
|
|
42540
|
+
}
|
|
42541
|
+
event.preventDefault();
|
|
42542
|
+
const percent = getPointerPercent(event, chartElement);
|
|
42543
|
+
setDraftSelection(
|
|
42544
|
+
(current) => current ? { ...current, currentPercent: percent } : current
|
|
42545
|
+
);
|
|
42546
|
+
};
|
|
42547
|
+
const onPointerUp = (event) => {
|
|
42548
|
+
if (!draftSelection) {
|
|
42549
|
+
return;
|
|
42550
|
+
}
|
|
42551
|
+
const chartElement = chartRef.current;
|
|
42552
|
+
const currentPercent = chartElement ? getPointerPercent(event, chartElement) : draftSelection.currentPercent;
|
|
42553
|
+
const distance = Math.abs(currentPercent - draftSelection.anchorPercent);
|
|
42554
|
+
if (distance > 1) {
|
|
42555
|
+
const startOffset = Math.min(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
|
|
42556
|
+
const endOffset = Math.max(draftSelection.anchorPercent, currentPercent) / 100 * timeline.rangeDuration;
|
|
42557
|
+
onSelectionChange({
|
|
42558
|
+
startTime: timeline.rangeStart + startOffset,
|
|
42559
|
+
endTime: timeline.rangeStart + endOffset
|
|
42560
|
+
});
|
|
42561
|
+
suppressTrackClickRef.current = true;
|
|
42562
|
+
window.setTimeout(() => {
|
|
42563
|
+
suppressTrackClickRef.current = false;
|
|
42564
|
+
}, 0);
|
|
42565
|
+
} else if (!draftSelection.startedOnTrack) {
|
|
42566
|
+
onSelectionChange(null);
|
|
42567
|
+
}
|
|
42568
|
+
setDraftSelection(null);
|
|
42569
|
+
if (chartElement?.hasPointerCapture(event.pointerId)) {
|
|
42570
|
+
chartElement.releasePointerCapture(event.pointerId);
|
|
42571
|
+
}
|
|
42572
|
+
};
|
|
42573
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-b border-gray-700 bg-gray-900 p-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
42574
|
+
"div",
|
|
42575
|
+
{
|
|
42576
|
+
ref: chartRef,
|
|
42577
|
+
className: "relative overflow-hidden border border-gray-800 bg-gray-950",
|
|
42578
|
+
style: { height: timeline.chartHeight },
|
|
42579
|
+
onPointerDown,
|
|
42580
|
+
onPointerMove,
|
|
42581
|
+
onPointerUp,
|
|
42582
|
+
children: [
|
|
42583
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(GridLines, { ticks: timeline.ticks }),
|
|
42584
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42585
|
+
"div",
|
|
42586
|
+
{
|
|
42587
|
+
className: "pointer-events-none absolute inset-x-0 border-b border-gray-800",
|
|
42588
|
+
style: { top: TIMELINE_LAYOUT.rulerHeightPx }
|
|
42589
|
+
}
|
|
42590
|
+
),
|
|
42591
|
+
timeline.ticks.map((tick) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42592
|
+
"div",
|
|
42593
|
+
{
|
|
42594
|
+
className: "absolute top-1 whitespace-nowrap tabular-nums text-xs text-gray-200",
|
|
42595
|
+
style: getTickLabelStyle(tick),
|
|
42596
|
+
children: tick.label
|
|
42597
|
+
},
|
|
42598
|
+
`${tick.label}-${tick.offsetPercent}`
|
|
42599
|
+
)),
|
|
42600
|
+
selection && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42601
|
+
"div",
|
|
42602
|
+
{
|
|
42603
|
+
className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/10",
|
|
42604
|
+
style: getSelectionStyle(selection, timeline)
|
|
42605
|
+
}
|
|
42606
|
+
),
|
|
42607
|
+
draftSelection && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42608
|
+
"div",
|
|
42609
|
+
{
|
|
42610
|
+
className: "pointer-events-none absolute bottom-0 border-x border-blue-300/70 bg-blue-400/15",
|
|
42611
|
+
style: getDraftSelectionStyle(draftSelection)
|
|
42612
|
+
}
|
|
42613
|
+
),
|
|
42614
|
+
timeline.rows.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42615
|
+
TimelineTrack,
|
|
42616
|
+
{
|
|
42617
|
+
row,
|
|
42618
|
+
isSelected: selectedRequestId === row.request.id,
|
|
42619
|
+
onSelect: onRequestSelect,
|
|
42620
|
+
shouldSuppressSelect: () => suppressTrackClickRef.current
|
|
42621
|
+
},
|
|
42622
|
+
row.request.id
|
|
42623
|
+
)),
|
|
42624
|
+
selection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-1 right-1 flex items-center gap-1 rounded border border-gray-700 bg-gray-900/95 px-1.5 py-0.5 text-xs text-gray-400", children: [
|
|
42625
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [
|
|
42626
|
+
filteredRequestCount,
|
|
42627
|
+
" in range"
|
|
42628
|
+
] }),
|
|
42629
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42630
|
+
"button",
|
|
42631
|
+
{
|
|
42632
|
+
type: "button",
|
|
42633
|
+
title: "Clear timeline selection",
|
|
42634
|
+
"aria-label": "Clear timeline selection",
|
|
42635
|
+
className: "rounded p-0.5 text-gray-400 hover:bg-gray-800 hover:text-gray-100",
|
|
42636
|
+
onClick: () => onSelectionChange(null),
|
|
42637
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsx(X$1, { className: "h-3 w-3" })
|
|
42638
|
+
}
|
|
42639
|
+
)
|
|
42640
|
+
] }),
|
|
42641
|
+
timeline.hiddenRequestCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute bottom-1 left-1 rounded border border-gray-700 bg-gray-900/95 px-1.5 py-0.5 text-xs text-gray-400", children: [
|
|
42642
|
+
"Showing latest ",
|
|
42643
|
+
timeline.rows.length,
|
|
42644
|
+
" of",
|
|
42645
|
+
" ",
|
|
42646
|
+
timeline.totalRequestCount
|
|
42647
|
+
] })
|
|
42648
|
+
]
|
|
42649
|
+
}
|
|
42650
|
+
) });
|
|
42651
|
+
};
|
|
42652
|
+
const parseThreshold = (value) => {
|
|
42653
|
+
const normalizedValue = value.trim();
|
|
42654
|
+
if (!normalizedValue) {
|
|
42655
|
+
return null;
|
|
42656
|
+
}
|
|
42657
|
+
const parsedValue = Number(normalizedValue);
|
|
42658
|
+
return Number.isFinite(parsedValue) ? parsedValue : null;
|
|
42659
|
+
};
|
|
42660
|
+
const matchesStatusFilter = (statusCode, statusFilter) => {
|
|
42661
|
+
const normalizedFilter = statusFilter.trim().toLowerCase();
|
|
42662
|
+
if (!normalizedFilter) {
|
|
42663
|
+
return true;
|
|
42664
|
+
}
|
|
42665
|
+
if (statusCode === void 0) {
|
|
42666
|
+
return false;
|
|
42667
|
+
}
|
|
42668
|
+
const statusRangeMatch = normalizedFilter.match(/^(\d{3})\s*-\s*(\d{3})$/);
|
|
42669
|
+
if (statusRangeMatch) {
|
|
42670
|
+
const min2 = Number(statusRangeMatch[1]);
|
|
42671
|
+
const max2 = Number(statusRangeMatch[2]);
|
|
42672
|
+
return statusCode >= min2 && statusCode <= max2;
|
|
42673
|
+
}
|
|
42674
|
+
const statusClassMatch = normalizedFilter.match(/^([1-5])xx$/);
|
|
42675
|
+
if (statusClassMatch) {
|
|
42676
|
+
return Math.floor(statusCode / 100) === Number(statusClassMatch[1]);
|
|
42677
|
+
}
|
|
42678
|
+
const comparisonMatch = normalizedFilter.match(/^(>=|<=|>|<)\s*(\d{3})$/);
|
|
42679
|
+
if (comparisonMatch) {
|
|
42680
|
+
const value = Number(comparisonMatch[2]);
|
|
42681
|
+
switch (comparisonMatch[1]) {
|
|
42682
|
+
case ">=":
|
|
42683
|
+
return statusCode >= value;
|
|
42684
|
+
case "<=":
|
|
42685
|
+
return statusCode <= value;
|
|
42686
|
+
case ">":
|
|
42687
|
+
return statusCode > value;
|
|
42688
|
+
case "<":
|
|
42689
|
+
return statusCode < value;
|
|
42690
|
+
}
|
|
42691
|
+
}
|
|
42692
|
+
return statusCode === Number(normalizedFilter);
|
|
42693
|
+
};
|
|
42694
|
+
const isInFlightStatus = (status) => {
|
|
42695
|
+
return ["pending", "loading", "connecting", "open"].includes(status);
|
|
42696
|
+
};
|
|
42697
|
+
const isFailedStatus = (status) => {
|
|
42698
|
+
return ["failed", "error"].includes(status);
|
|
42699
|
+
};
|
|
42700
|
+
const isHttpMethod = (method) => method !== "WS" && method !== "SSE";
|
|
42701
|
+
const extractDomainAndPath = (url) => {
|
|
42702
|
+
try {
|
|
42703
|
+
const { hostname, pathname, search, hash: hash2, port } = new URL(url);
|
|
42704
|
+
return {
|
|
42705
|
+
domain: `${hostname}${port ? `:${port}` : ""}`,
|
|
42706
|
+
path: `${pathname}${search}${hash2}`
|
|
42707
|
+
};
|
|
42708
|
+
} catch {
|
|
42709
|
+
return { domain: "unknown", path: url };
|
|
42710
|
+
}
|
|
42711
|
+
};
|
|
42712
|
+
const matchesRequestFilter = (request, filter, options = {}) => {
|
|
42713
|
+
if (filter.types.size > 0 && !filter.types.has(request.type)) {
|
|
42714
|
+
return false;
|
|
42715
|
+
}
|
|
42716
|
+
if (filter.advanced.methods.size > 0 && (!isHttpMethod(request.method) || !filter.advanced.methods.has(request.method))) {
|
|
42717
|
+
return false;
|
|
42718
|
+
}
|
|
42719
|
+
if (filter.advanced.sources.size > 0 && (!request.source || !filter.advanced.sources.has(request.source))) {
|
|
42720
|
+
return false;
|
|
42721
|
+
}
|
|
42722
|
+
if (!matchesStatusFilter(request.httpStatus, filter.advanced.status)) {
|
|
42723
|
+
return false;
|
|
42724
|
+
}
|
|
42725
|
+
const { domain, path } = extractDomainAndPath(request.name);
|
|
42726
|
+
const domainFilter = filter.advanced.domain.trim().toLowerCase();
|
|
42727
|
+
if (domainFilter && !domain.toLowerCase().includes(domainFilter)) {
|
|
42728
|
+
return false;
|
|
42729
|
+
}
|
|
42730
|
+
const contentTypeFilter = filter.advanced.contentType.trim().toLowerCase();
|
|
42731
|
+
if (contentTypeFilter && !request.contentType?.toLowerCase().includes(contentTypeFilter)) {
|
|
42732
|
+
return false;
|
|
42733
|
+
}
|
|
42734
|
+
if (filter.advanced.failedOnly && !isFailedStatus(request.status)) {
|
|
42735
|
+
return false;
|
|
42736
|
+
}
|
|
42737
|
+
if (filter.advanced.inFlightOnly && !isInFlightStatus(request.status)) {
|
|
42738
|
+
return false;
|
|
42739
|
+
}
|
|
42740
|
+
if (filter.advanced.overriddenOnly && !options.hasOverride) {
|
|
42741
|
+
return false;
|
|
42742
|
+
}
|
|
42743
|
+
const minSize = parseThreshold(filter.advanced.minSize);
|
|
42744
|
+
if (minSize !== null && (request.size === null || request.size < minSize)) {
|
|
42745
|
+
return false;
|
|
42746
|
+
}
|
|
42747
|
+
const maxSize = parseThreshold(filter.advanced.maxSize);
|
|
42748
|
+
if (maxSize !== null && (request.size === null || request.size > maxSize)) {
|
|
42749
|
+
return false;
|
|
42750
|
+
}
|
|
42751
|
+
const duration = request.duration || 0;
|
|
42752
|
+
const minDuration = parseThreshold(filter.advanced.minDuration);
|
|
42753
|
+
if (minDuration !== null && duration < minDuration) {
|
|
42754
|
+
return false;
|
|
42755
|
+
}
|
|
42756
|
+
const maxDuration = parseThreshold(filter.advanced.maxDuration);
|
|
42757
|
+
if (maxDuration !== null && duration > maxDuration) {
|
|
42758
|
+
return false;
|
|
42759
|
+
}
|
|
42760
|
+
const searchText = filter.text.trim().toLowerCase();
|
|
42761
|
+
if (!searchText) {
|
|
42762
|
+
return true;
|
|
42763
|
+
}
|
|
42764
|
+
const searchableFields = [
|
|
42765
|
+
request.name,
|
|
42766
|
+
request.method,
|
|
42767
|
+
request.status,
|
|
42768
|
+
request.httpStatus,
|
|
42769
|
+
request.source,
|
|
42770
|
+
request.type,
|
|
42771
|
+
request.contentType,
|
|
42772
|
+
domain,
|
|
42773
|
+
path
|
|
42774
|
+
].filter((value) => value !== void 0 && value !== null).join(" ").toLowerCase();
|
|
42775
|
+
return searchableFields.includes(searchText);
|
|
42776
|
+
};
|
|
42080
42777
|
const InspectorView = ({ client: client2 }) => {
|
|
42081
42778
|
const actions = useNetworkActivityActions();
|
|
42082
42779
|
const clientManagement = useNetworkActivityClientManagement();
|
|
42083
42780
|
const hasSelectedRequest = useHasSelectedRequest();
|
|
42084
42781
|
const overrides = useOverrides();
|
|
42782
|
+
const processedRequests = useProcessedRequests();
|
|
42085
42783
|
const [filter, setFilter] = reactExports.useState(
|
|
42086
42784
|
() => createDefaultFilter()
|
|
42087
42785
|
);
|
|
42786
|
+
const [timelineSelection, setTimelineSelection] = reactExports.useState(null);
|
|
42787
|
+
const filteredRequests = reactExports.useMemo(() => {
|
|
42788
|
+
return processedRequests.filter(
|
|
42789
|
+
(request) => matchesRequestFilter(request, filter, {
|
|
42790
|
+
hasOverride: overrides.has(request.name)
|
|
42791
|
+
})
|
|
42792
|
+
);
|
|
42793
|
+
}, [processedRequests, filter, overrides]);
|
|
42794
|
+
const visibleRequests = reactExports.useMemo(() => {
|
|
42795
|
+
if (!timelineSelection) {
|
|
42796
|
+
return filteredRequests;
|
|
42797
|
+
}
|
|
42798
|
+
const now = Date.now();
|
|
42799
|
+
return filteredRequests.filter(
|
|
42800
|
+
(request) => requestOverlapsTimelineRange(request, timelineSelection, now)
|
|
42801
|
+
);
|
|
42802
|
+
}, [filteredRequests, timelineSelection]);
|
|
42088
42803
|
reactExports.useEffect(() => {
|
|
42089
42804
|
if (!client2) {
|
|
42090
42805
|
return;
|
|
@@ -42103,11 +42818,22 @@ const InspectorView = ({ client: client2 }) => {
|
|
|
42103
42818
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Toolbar, {}),
|
|
42104
42819
|
/* @__PURE__ */ jsxRuntimeExports.jsx(FilterBar, { filter, onFilterChange: setFilter }),
|
|
42105
42820
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
42106
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
42821
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
42107
42822
|
"div",
|
|
42108
42823
|
{
|
|
42109
42824
|
className: `flex flex-col ${hasSelectedRequest ? "w-1/2" : "w-full"} border-r border-gray-700 overflow-hidden`,
|
|
42110
|
-
children:
|
|
42825
|
+
children: [
|
|
42826
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
42827
|
+
NetworkTimeline,
|
|
42828
|
+
{
|
|
42829
|
+
requests: filteredRequests,
|
|
42830
|
+
selection: timelineSelection,
|
|
42831
|
+
filteredRequestCount: visibleRequests.length,
|
|
42832
|
+
onSelectionChange: setTimelineSelection
|
|
42833
|
+
}
|
|
42834
|
+
),
|
|
42835
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(RequestList, { requests: visibleRequests })
|
|
42836
|
+
]
|
|
42111
42837
|
}
|
|
42112
42838
|
),
|
|
42113
42839
|
hasSelectedRequest && /* @__PURE__ */ jsxRuntimeExports.jsx(SidePanel, {})
|