@flanksource/clicky-ui
Version:
Flanksource Clicky UI — React component library built on shadcn/ui with light/dark and density theming.
176 lines (175 loc) • 6.18 kB
JavaScript
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
import { useMemo } from "react";
import { DataTable } from "../DataTable.js";
import { JsonView } from "../JsonView.js";
const COLS = [
{
key: "request.method",
label: "Method",
shrink: true
},
{
key: "request.url",
label: "URL",
grow: true,
render: (value) => /* @__PURE__ */ jsx("span", { title: String(value ?? ""), children: String(value ?? "") })
},
{
key: "response.status",
label: "Status",
shrink: true,
render: (value) => /* @__PURE__ */ jsx("span", { className: statusColor(Number(value ?? 0)), children: String(value ?? "") }),
sortValue: (value) => Number(value ?? 0)
},
{
key: "time",
label: "Time",
align: "right",
shrink: true,
render: (value) => `${Number(value ?? 0).toFixed(0)}ms`,
sortValue: (value) => Number(value ?? 0)
},
{
key: "response.bodySize",
label: "Size",
align: "right",
shrink: true,
render: (value) => formatBytes(Number(value ?? 0)),
sortValue: (value) => Number(value ?? 0)
},
{
key: "response.content.mimeType",
label: "Type",
shrink: true
}
];
function HarPanel({
entries,
search,
emptyLabel = "No HTTP traffic captured",
className
}) {
const filteredEntries = useMemo(() => {
if (search === void 0 || search.trim() === "") return entries;
return entries.filter((entry) => matchesSearch(search.trim().toLowerCase(), entry));
}, [entries, search]);
if (!entries || entries.length === 0) {
return /* @__PURE__ */ jsx("div", { className: "p-density-6 text-center text-muted-foreground text-sm", children: emptyLabel });
}
return /* @__PURE__ */ jsx("div", { className: `h-full ${className ?? ""}`, children: /* @__PURE__ */ jsx(
DataTable,
{
data: filteredEntries,
columns: COLS,
autoFilter: true,
showGlobalFilter: search === void 0,
globalFilterPlaceholder: "Filter URL, method, or body…",
defaultSort: { key: "time", dir: "asc" },
emptyMessage: emptyLabel,
getRowId: (entry, index) => `${entry.startedDateTime ?? index}-${entry.request.method}-${entry.request.url}-${entry.time}`,
renderExpandedRow: (entry) => /* @__PURE__ */ jsx(HarRowDetails, { entry })
}
) });
}
function HarRowDetails({ entry }) {
var _a, _b;
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-density-4", children: [
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(
HeaderList,
{
title: "Request Headers",
...entry.request.headers ? { headers: entry.request.headers } : {}
}
),
((_a = entry.request.postData) == null ? void 0 : _a.text) && /* @__PURE__ */ jsxs("div", { className: "mt-density-2", children: [
/* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground mb-1", children: "Request Body" }),
/* @__PURE__ */ jsx("div", { className: "bg-background p-density-2 rounded border border-border overflow-auto max-h-48", children: /* @__PURE__ */ jsx(
BodyView,
{
text: entry.request.postData.text,
...entry.request.postData.mimeType ? { mimeType: entry.request.postData.mimeType } : {}
}
) })
] })
] }),
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
HeaderList,
{
title: "Response Headers",
...entry.response.headers ? { headers: entry.response.headers } : {}
}
) })
] }),
((_b = entry.response.content) == null ? void 0 : _b.text) && /* @__PURE__ */ jsxs("div", { className: "mt-density-3", children: [
/* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground mb-1", children: "Response Body" }),
/* @__PURE__ */ jsx("div", { className: "bg-background p-density-2 rounded border border-border overflow-auto max-h-64", children: /* @__PURE__ */ jsx(
BodyView,
{
text: entry.response.content.text,
...entry.response.content.mimeType ? { mimeType: entry.response.content.mimeType } : {}
}
) })
] })
] });
}
function HeaderList({
title,
headers
}) {
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { className: "font-semibold text-foreground mb-1", children: title }),
/* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: headers == null ? void 0 : headers.map((h, i) => /* @__PURE__ */ jsxs("div", { className: "whitespace-nowrap", children: [
/* @__PURE__ */ jsxs("span", { className: "text-purple-600 dark:text-purple-400", children: [
h.name,
":"
] }),
" ",
h.value
] }, i)) })
] });
}
function BodyView({ text, mimeType }) {
if (isJsonType(mimeType)) {
const parsed = tryParseJson(text);
if (parsed !== null) return /* @__PURE__ */ jsx(JsonView, { data: parsed });
}
return /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap break-all", children: text });
}
function tryParseJson(text) {
try {
return JSON.parse(text);
} catch {
return null;
}
}
function isJsonType(mime) {
return !!mime && (mime.includes("json") || mime.includes("javascript"));
}
function matchesSearch(needle, entry) {
var _a, _b;
return [
entry.request.method,
entry.request.url,
(_a = entry.request.postData) == null ? void 0 : _a.text,
(_b = entry.response.content) == null ? void 0 : _b.text
].some((value) => !!value && value.toLowerCase().includes(needle));
}
function formatBytes(bytes) {
if (bytes < 0) return "";
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
}
function statusColor(status) {
if (status >= 500) return "text-red-600";
if (status >= 400) return "text-amber-600";
if (status >= 300) return "text-blue-600";
if (status >= 200) return "text-green-600";
return "text-muted-foreground";
}
export {
HarPanel
};
//# sourceMappingURL=HarPanel.js.map