@flanksource/clicky-ui 0.1.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/LICENSE +17 -0
- package/README.md +97 -0
- package/dist/index.d.mts +834 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2918 -0
- package/dist/index.mjs.map +1 -0
- package/dist/tailwind-preset.cjs +65 -0
- package/dist/tailwind-preset.cjs.map +1 -0
- package/dist/tailwind-preset.d.cts +6 -0
- package/dist/tailwind-preset.d.cts.map +1 -0
- package/dist/tailwind-preset.d.mts +7 -0
- package/dist/tailwind-preset.d.mts.map +1 -0
- package/dist/tailwind-preset.mjs +64 -0
- package/dist/tailwind-preset.mjs.map +1 -0
- package/package.json +84 -0
- package/src/styles/tokens.css +78 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2918 @@
|
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
import { Fragment, createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
|
|
8
|
+
//#region src/lib/utils.ts
|
|
9
|
+
function cn(...inputs) {
|
|
10
|
+
return twMerge(clsx(inputs));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/lib/palette.ts
|
|
15
|
+
const AVATAR_PALETTE = [
|
|
16
|
+
"bg-rose-100 text-rose-700",
|
|
17
|
+
"bg-pink-100 text-pink-700",
|
|
18
|
+
"bg-fuchsia-100 text-fuchsia-700",
|
|
19
|
+
"bg-purple-100 text-purple-700",
|
|
20
|
+
"bg-violet-100 text-violet-700",
|
|
21
|
+
"bg-indigo-100 text-indigo-700",
|
|
22
|
+
"bg-blue-100 text-blue-700",
|
|
23
|
+
"bg-sky-100 text-sky-700",
|
|
24
|
+
"bg-cyan-100 text-cyan-700",
|
|
25
|
+
"bg-teal-100 text-teal-700",
|
|
26
|
+
"bg-emerald-100 text-emerald-700",
|
|
27
|
+
"bg-green-100 text-green-700",
|
|
28
|
+
"bg-lime-100 text-lime-800",
|
|
29
|
+
"bg-amber-100 text-amber-800",
|
|
30
|
+
"bg-orange-100 text-orange-700",
|
|
31
|
+
"bg-red-100 text-red-700"
|
|
32
|
+
];
|
|
33
|
+
function fnv1a32(s) {
|
|
34
|
+
let h = 2166136261;
|
|
35
|
+
for (let i = 0; i < s.length; i++) {
|
|
36
|
+
h ^= s.charCodeAt(i);
|
|
37
|
+
h = Math.imul(h, 16777619);
|
|
38
|
+
}
|
|
39
|
+
return h >>> 0;
|
|
40
|
+
}
|
|
41
|
+
function paletteClass(key) {
|
|
42
|
+
return AVATAR_PALETTE[fnv1a32(key) % AVATAR_PALETTE.length];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/hooks/use-theme.tsx
|
|
47
|
+
const STORAGE_KEY$1 = "clicky-ui-theme";
|
|
48
|
+
const DATA_ATTR$1 = "data-theme";
|
|
49
|
+
const ThemeContext = createContext(null);
|
|
50
|
+
function prefersDark() {
|
|
51
|
+
if (typeof window === "undefined") return false;
|
|
52
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
53
|
+
}
|
|
54
|
+
function readStored$1() {
|
|
55
|
+
if (typeof window === "undefined") return "system";
|
|
56
|
+
const raw = window.localStorage.getItem(STORAGE_KEY$1);
|
|
57
|
+
return raw === "light" || raw === "dark" || raw === "system" ? raw : "system";
|
|
58
|
+
}
|
|
59
|
+
function resolve(theme) {
|
|
60
|
+
if (theme === "system") return prefersDark() ? "dark" : "light";
|
|
61
|
+
return theme;
|
|
62
|
+
}
|
|
63
|
+
function apply$1(resolved) {
|
|
64
|
+
if (typeof document === "undefined") return;
|
|
65
|
+
document.documentElement.setAttribute(DATA_ATTR$1, resolved);
|
|
66
|
+
}
|
|
67
|
+
function ThemeProvider({ children, defaultTheme = "system", storageKey = STORAGE_KEY$1 }) {
|
|
68
|
+
const [theme, setThemeState] = useState(() => {
|
|
69
|
+
if (typeof window === "undefined") return defaultTheme;
|
|
70
|
+
return readStored$1() ?? defaultTheme;
|
|
71
|
+
});
|
|
72
|
+
const [resolvedTheme, setResolvedTheme] = useState(() => resolve(theme));
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const next = resolve(theme);
|
|
75
|
+
setResolvedTheme(next);
|
|
76
|
+
apply$1(next);
|
|
77
|
+
if (typeof window !== "undefined") window.localStorage.setItem(storageKey, theme);
|
|
78
|
+
}, [theme, storageKey]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (theme !== "system" || typeof window === "undefined") return;
|
|
81
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
82
|
+
const onChange = () => {
|
|
83
|
+
const next = mq.matches ? "dark" : "light";
|
|
84
|
+
setResolvedTheme(next);
|
|
85
|
+
apply$1(next);
|
|
86
|
+
};
|
|
87
|
+
mq.addEventListener("change", onChange);
|
|
88
|
+
return () => mq.removeEventListener("change", onChange);
|
|
89
|
+
}, [theme]);
|
|
90
|
+
const setTheme = useCallback((next) => setThemeState(next), []);
|
|
91
|
+
const value = useMemo(() => ({
|
|
92
|
+
theme,
|
|
93
|
+
resolvedTheme,
|
|
94
|
+
setTheme
|
|
95
|
+
}), [
|
|
96
|
+
theme,
|
|
97
|
+
resolvedTheme,
|
|
98
|
+
setTheme
|
|
99
|
+
]);
|
|
100
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, {
|
|
101
|
+
value,
|
|
102
|
+
children
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function useTheme() {
|
|
106
|
+
const ctx = useContext(ThemeContext);
|
|
107
|
+
if (!ctx) throw new Error("useTheme must be used inside <ThemeProvider>");
|
|
108
|
+
return ctx;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/hooks/use-density.tsx
|
|
113
|
+
const STORAGE_KEY = "clicky-ui-density";
|
|
114
|
+
const DATA_ATTR = "data-density";
|
|
115
|
+
const DensityContext = createContext(null);
|
|
116
|
+
function readStored() {
|
|
117
|
+
if (typeof window === "undefined") return null;
|
|
118
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
119
|
+
return raw === "compact" || raw === "comfortable" || raw === "spacious" ? raw : null;
|
|
120
|
+
}
|
|
121
|
+
function apply(density) {
|
|
122
|
+
if (typeof document === "undefined") return;
|
|
123
|
+
document.documentElement.setAttribute(DATA_ATTR, density);
|
|
124
|
+
}
|
|
125
|
+
function DensityProvider({ children, defaultDensity = "comfortable", storageKey = STORAGE_KEY }) {
|
|
126
|
+
const [density, setDensityState] = useState(() => {
|
|
127
|
+
if (typeof window === "undefined") return defaultDensity;
|
|
128
|
+
return readStored() ?? defaultDensity;
|
|
129
|
+
});
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
apply(density);
|
|
132
|
+
if (typeof window !== "undefined") window.localStorage.setItem(storageKey, density);
|
|
133
|
+
}, [density, storageKey]);
|
|
134
|
+
const setDensity = useCallback((next) => setDensityState(next), []);
|
|
135
|
+
const value = useMemo(() => ({
|
|
136
|
+
density,
|
|
137
|
+
setDensity
|
|
138
|
+
}), [density, setDensity]);
|
|
139
|
+
return /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
140
|
+
value,
|
|
141
|
+
children
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function useDensity() {
|
|
145
|
+
const ctx = useContext(DensityContext);
|
|
146
|
+
if (!ctx) throw new Error("useDensity must be used inside <DensityProvider>");
|
|
147
|
+
return ctx;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/hooks/use-sort.ts
|
|
152
|
+
function resolvePath(obj, path) {
|
|
153
|
+
return path.split(".").reduce((o, k) => {
|
|
154
|
+
if (o && typeof o === "object") return o[k];
|
|
155
|
+
}, obj);
|
|
156
|
+
}
|
|
157
|
+
function useSort(items, options = {}) {
|
|
158
|
+
const { defaultKey, defaultDir = "asc", resolvers } = options;
|
|
159
|
+
const [sort, setSort] = useState(defaultKey ? {
|
|
160
|
+
key: defaultKey,
|
|
161
|
+
dir: defaultDir
|
|
162
|
+
} : null);
|
|
163
|
+
function toggle(key) {
|
|
164
|
+
setSort((prev) => {
|
|
165
|
+
if (prev?.key === key) return prev.dir === "asc" ? {
|
|
166
|
+
key,
|
|
167
|
+
dir: "desc"
|
|
168
|
+
} : null;
|
|
169
|
+
return {
|
|
170
|
+
key,
|
|
171
|
+
dir: "asc"
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
sorted: useMemo(() => {
|
|
177
|
+
if (!items) return [];
|
|
178
|
+
if (!sort) return items;
|
|
179
|
+
const { key, dir } = sort;
|
|
180
|
+
const resolver = resolvers?.[key];
|
|
181
|
+
const get = (item) => resolver ? resolver(item) : resolvePath(item, key);
|
|
182
|
+
return [...items].sort((a, b) => {
|
|
183
|
+
const av = get(a);
|
|
184
|
+
const bv = get(b);
|
|
185
|
+
if (av == null && bv == null) return 0;
|
|
186
|
+
if (av == null) return 1;
|
|
187
|
+
if (bv == null) return -1;
|
|
188
|
+
const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av).localeCompare(String(bv));
|
|
189
|
+
return dir === "asc" ? cmp : -cmp;
|
|
190
|
+
});
|
|
191
|
+
}, [
|
|
192
|
+
items,
|
|
193
|
+
sort,
|
|
194
|
+
resolvers
|
|
195
|
+
]),
|
|
196
|
+
sort,
|
|
197
|
+
toggle
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/hooks/use-history-route.ts
|
|
203
|
+
function useHistoryRoute(options) {
|
|
204
|
+
const { parse, build } = options;
|
|
205
|
+
const [route, setRoute] = useState(() => typeof window === "undefined" ? parse("/", "") : parse(window.location.pathname, window.location.search));
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
const onPop = () => setRoute(parse(window.location.pathname, window.location.search));
|
|
208
|
+
window.addEventListener("popstate", onPop);
|
|
209
|
+
return () => window.removeEventListener("popstate", onPop);
|
|
210
|
+
}, [parse]);
|
|
211
|
+
return [route, useCallback((next) => {
|
|
212
|
+
setRoute((prev) => {
|
|
213
|
+
const merged = typeof next === "function" ? next(prev) : {
|
|
214
|
+
...prev,
|
|
215
|
+
...next
|
|
216
|
+
};
|
|
217
|
+
const path = build(merged);
|
|
218
|
+
if (typeof window !== "undefined") {
|
|
219
|
+
if (window.location.pathname + window.location.search !== path) window.history.pushState(null, "", path);
|
|
220
|
+
}
|
|
221
|
+
return merged;
|
|
222
|
+
});
|
|
223
|
+
}, [build])];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/components/button.tsx
|
|
228
|
+
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
|
|
229
|
+
variants: {
|
|
230
|
+
variant: {
|
|
231
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
232
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
233
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
234
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
235
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
236
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
237
|
+
},
|
|
238
|
+
size: {
|
|
239
|
+
default: "h-control-h px-control-px py-2",
|
|
240
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
241
|
+
lg: "h-10 rounded-md px-8",
|
|
242
|
+
icon: "h-control-h w-control-h"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
defaultVariants: {
|
|
246
|
+
variant: "default",
|
|
247
|
+
size: "default"
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const Button = forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
251
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
252
|
+
ref,
|
|
253
|
+
className: cn(buttonVariants({
|
|
254
|
+
variant,
|
|
255
|
+
size
|
|
256
|
+
}), className),
|
|
257
|
+
...props
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
Button.displayName = "Button";
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/data/Icon.tsx
|
|
264
|
+
function Icon({ name, className, width, height, rotate, flip, inline, title }) {
|
|
265
|
+
return /* @__PURE__ */ jsx("iconify-icon", {
|
|
266
|
+
icon: name,
|
|
267
|
+
className: cn("shrink-0", className),
|
|
268
|
+
width,
|
|
269
|
+
height,
|
|
270
|
+
rotate,
|
|
271
|
+
flip,
|
|
272
|
+
inline,
|
|
273
|
+
title,
|
|
274
|
+
"aria-hidden": title ? void 0 : true
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/components/select.tsx
|
|
280
|
+
const Select = forwardRef(({ className, options, placeholder, children, ...props }, ref) => {
|
|
281
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
282
|
+
className: "relative inline-flex w-full items-center",
|
|
283
|
+
children: [/* @__PURE__ */ jsxs("select", {
|
|
284
|
+
ref,
|
|
285
|
+
className: cn("h-control-h w-full appearance-none rounded-md border border-input bg-background px-control-px pr-8 text-sm text-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1", "disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
286
|
+
...props,
|
|
287
|
+
children: [placeholder !== void 0 && /* @__PURE__ */ jsx("option", {
|
|
288
|
+
value: "",
|
|
289
|
+
disabled: true,
|
|
290
|
+
children: placeholder
|
|
291
|
+
}), options ? options.map((opt) => /* @__PURE__ */ jsx("option", {
|
|
292
|
+
value: opt.value,
|
|
293
|
+
disabled: opt.disabled,
|
|
294
|
+
children: opt.label
|
|
295
|
+
}, opt.value)) : children]
|
|
296
|
+
}), /* @__PURE__ */ jsx(Icon, {
|
|
297
|
+
name: "codicon:chevron-down",
|
|
298
|
+
className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-xs text-muted-foreground"
|
|
299
|
+
})]
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
Select.displayName = "Select";
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/components/theme-switcher.tsx
|
|
306
|
+
const THEMES = [
|
|
307
|
+
"light",
|
|
308
|
+
"dark",
|
|
309
|
+
"system"
|
|
310
|
+
];
|
|
311
|
+
const ThemeSwitcher = forwardRef(({ className, ...props }, ref) => {
|
|
312
|
+
const { theme, setTheme, resolvedTheme } = useTheme();
|
|
313
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
314
|
+
ref,
|
|
315
|
+
role: "radiogroup",
|
|
316
|
+
"aria-label": "Theme",
|
|
317
|
+
className: cn("inline-flex items-center gap-density-1 rounded-md border border-input bg-background p-density-1", className),
|
|
318
|
+
...props,
|
|
319
|
+
children: [THEMES.map((t) => {
|
|
320
|
+
const active = theme === t;
|
|
321
|
+
return /* @__PURE__ */ jsx("button", {
|
|
322
|
+
type: "button",
|
|
323
|
+
role: "radio",
|
|
324
|
+
"aria-checked": active,
|
|
325
|
+
"data-active": active || void 0,
|
|
326
|
+
onClick: () => setTheme(t),
|
|
327
|
+
className: cn("inline-flex h-control-h items-center justify-center rounded-sm px-density-3 text-sm capitalize transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", active ? "bg-primary text-primary-foreground" : "text-foreground hover:bg-accent hover:text-accent-foreground"),
|
|
328
|
+
children: t
|
|
329
|
+
}, t);
|
|
330
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
331
|
+
className: "ml-density-2 text-xs text-muted-foreground",
|
|
332
|
+
children: ["resolved: ", /* @__PURE__ */ jsx("span", {
|
|
333
|
+
"data-testid": "resolved-theme",
|
|
334
|
+
children: resolvedTheme
|
|
335
|
+
})]
|
|
336
|
+
})]
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
ThemeSwitcher.displayName = "ThemeSwitcher";
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/components/density-switcher.tsx
|
|
343
|
+
const DENSITIES = [
|
|
344
|
+
"compact",
|
|
345
|
+
"comfortable",
|
|
346
|
+
"spacious"
|
|
347
|
+
];
|
|
348
|
+
const DensitySwitcher = forwardRef(({ className, ...props }, ref) => {
|
|
349
|
+
const { density, setDensity } = useDensity();
|
|
350
|
+
return /* @__PURE__ */ jsx("div", {
|
|
351
|
+
ref,
|
|
352
|
+
role: "radiogroup",
|
|
353
|
+
"aria-label": "Density",
|
|
354
|
+
className: cn("inline-flex items-center gap-density-1 rounded-md border border-input bg-background p-density-1", className),
|
|
355
|
+
...props,
|
|
356
|
+
children: DENSITIES.map((d) => {
|
|
357
|
+
const active = density === d;
|
|
358
|
+
return /* @__PURE__ */ jsx("button", {
|
|
359
|
+
type: "button",
|
|
360
|
+
role: "radio",
|
|
361
|
+
"aria-checked": active,
|
|
362
|
+
"data-active": active || void 0,
|
|
363
|
+
onClick: () => setDensity(d),
|
|
364
|
+
className: cn("inline-flex h-control-h items-center justify-center rounded-sm px-density-3 text-sm capitalize transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", active ? "bg-primary text-primary-foreground" : "text-foreground hover:bg-accent hover:text-accent-foreground"),
|
|
365
|
+
children: d
|
|
366
|
+
}, d);
|
|
367
|
+
})
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
DensitySwitcher.displayName = "DensitySwitcher";
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/layout/SplitPane.tsx
|
|
374
|
+
function SplitPane({ left, right, defaultSplit = 50, minLeft = 20, minRight = 20, leftClass, rightClass, className }) {
|
|
375
|
+
const [split, setSplit] = useState(defaultSplit);
|
|
376
|
+
const dragging = useRef(false);
|
|
377
|
+
const container = useRef(null);
|
|
378
|
+
const onMouseDown = useCallback((e) => {
|
|
379
|
+
e.preventDefault();
|
|
380
|
+
dragging.current = true;
|
|
381
|
+
const onMove = (ev) => {
|
|
382
|
+
if (!dragging.current || !container.current) return;
|
|
383
|
+
const rect = container.current.getBoundingClientRect();
|
|
384
|
+
const pct = (ev.clientX - rect.left) / rect.width * 100;
|
|
385
|
+
setSplit(Math.max(minLeft, Math.min(100 - minRight, pct)));
|
|
386
|
+
};
|
|
387
|
+
const onUp = () => {
|
|
388
|
+
dragging.current = false;
|
|
389
|
+
document.removeEventListener("mousemove", onMove);
|
|
390
|
+
document.removeEventListener("mouseup", onUp);
|
|
391
|
+
document.body.style.cursor = "";
|
|
392
|
+
document.body.style.userSelect = "";
|
|
393
|
+
};
|
|
394
|
+
document.addEventListener("mousemove", onMove);
|
|
395
|
+
document.addEventListener("mouseup", onUp);
|
|
396
|
+
document.body.style.cursor = "col-resize";
|
|
397
|
+
document.body.style.userSelect = "none";
|
|
398
|
+
}, [minLeft, minRight]);
|
|
399
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
400
|
+
ref: container,
|
|
401
|
+
className: cn("flex flex-1 overflow-hidden min-h-0", className),
|
|
402
|
+
children: [
|
|
403
|
+
/* @__PURE__ */ jsx("div", {
|
|
404
|
+
style: { width: `${split}%` },
|
|
405
|
+
className: cn("overflow-y-auto bg-background min-h-0", leftClass),
|
|
406
|
+
children: left
|
|
407
|
+
}),
|
|
408
|
+
/* @__PURE__ */ jsx("div", {
|
|
409
|
+
role: "separator",
|
|
410
|
+
"aria-orientation": "vertical",
|
|
411
|
+
className: "w-1 bg-border hover:bg-primary cursor-col-resize shrink-0 transition-colors",
|
|
412
|
+
onMouseDown
|
|
413
|
+
}),
|
|
414
|
+
/* @__PURE__ */ jsx("div", {
|
|
415
|
+
style: { width: `${100 - split}%` },
|
|
416
|
+
className: cn("overflow-hidden bg-background min-h-0", rightClass),
|
|
417
|
+
children: right
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/layout/Section.tsx
|
|
425
|
+
const toneRing = {
|
|
426
|
+
default: "",
|
|
427
|
+
danger: "border-l-2 border-red-500",
|
|
428
|
+
warning: "border-l-2 border-yellow-500",
|
|
429
|
+
success: "border-l-2 border-green-500",
|
|
430
|
+
info: "border-l-2 border-blue-500"
|
|
431
|
+
};
|
|
432
|
+
function Section({ title, summary, defaultOpen = false, open: openProp, onToggle, icon, tone = "default", className, headerClassName, bodyClassName, children }) {
|
|
433
|
+
const isControlled = openProp !== void 0;
|
|
434
|
+
const [innerOpen, setInnerOpen] = useState(defaultOpen);
|
|
435
|
+
const open = isControlled ? openProp : innerOpen;
|
|
436
|
+
function toggle() {
|
|
437
|
+
const next = !open;
|
|
438
|
+
if (!isControlled) setInnerOpen(next);
|
|
439
|
+
onToggle?.(next);
|
|
440
|
+
}
|
|
441
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
442
|
+
className: cn("rounded-md border border-border bg-background", toneRing[tone], className),
|
|
443
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
444
|
+
type: "button",
|
|
445
|
+
onClick: toggle,
|
|
446
|
+
"aria-expanded": open,
|
|
447
|
+
className: cn("w-full flex items-center gap-2 px-density-3 py-density-2 text-left", "hover:bg-accent/50 transition-colors", headerClassName),
|
|
448
|
+
children: [
|
|
449
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
450
|
+
name: open ? "codicon:chevron-down" : "codicon:chevron-right",
|
|
451
|
+
className: "text-muted-foreground text-xs"
|
|
452
|
+
}),
|
|
453
|
+
icon && /* @__PURE__ */ jsx(Icon, {
|
|
454
|
+
name: icon,
|
|
455
|
+
className: "text-base"
|
|
456
|
+
}),
|
|
457
|
+
/* @__PURE__ */ jsx("span", {
|
|
458
|
+
className: "font-medium text-sm flex-1 truncate",
|
|
459
|
+
children: title
|
|
460
|
+
}),
|
|
461
|
+
summary && /* @__PURE__ */ jsx("span", {
|
|
462
|
+
className: "text-xs text-muted-foreground",
|
|
463
|
+
children: summary
|
|
464
|
+
})
|
|
465
|
+
]
|
|
466
|
+
}), open && /* @__PURE__ */ jsx("div", {
|
|
467
|
+
className: cn("px-density-3 py-density-2 border-t border-border", bodyClassName),
|
|
468
|
+
children
|
|
469
|
+
})]
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function DetailEmptyState({ icon, label, description, className }) {
|
|
473
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
474
|
+
className: cn("p-density-6 text-center text-muted-foreground", className),
|
|
475
|
+
children: [
|
|
476
|
+
icon && /* @__PURE__ */ jsx(Icon, {
|
|
477
|
+
name: icon,
|
|
478
|
+
className: "text-3xl mb-density-2"
|
|
479
|
+
}),
|
|
480
|
+
/* @__PURE__ */ jsx("p", {
|
|
481
|
+
className: "text-sm",
|
|
482
|
+
children: label
|
|
483
|
+
}),
|
|
484
|
+
description && /* @__PURE__ */ jsx("p", {
|
|
485
|
+
className: "text-xs mt-density-1 opacity-70",
|
|
486
|
+
children: description
|
|
487
|
+
})
|
|
488
|
+
]
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/data/AnsiHtml.tsx
|
|
494
|
+
const ANSI_COLORS = {
|
|
495
|
+
"30": "color:#1e1e1e",
|
|
496
|
+
"31": "color:#cd3131",
|
|
497
|
+
"32": "color:#0dbc79",
|
|
498
|
+
"33": "color:#e5e510",
|
|
499
|
+
"34": "color:#2472c8",
|
|
500
|
+
"35": "color:#bc3fbc",
|
|
501
|
+
"36": "color:#11a8cd",
|
|
502
|
+
"37": "color:#e5e5e5",
|
|
503
|
+
"90": "color:#666",
|
|
504
|
+
"91": "color:#f14c4c",
|
|
505
|
+
"92": "color:#23d18b",
|
|
506
|
+
"93": "color:#f5f543",
|
|
507
|
+
"94": "color:#3b8eea",
|
|
508
|
+
"95": "color:#d670d6",
|
|
509
|
+
"96": "color:#29b8db",
|
|
510
|
+
"97": "color:#fff",
|
|
511
|
+
"1": "font-weight:bold",
|
|
512
|
+
"2": "opacity:0.7",
|
|
513
|
+
"3": "font-style:italic",
|
|
514
|
+
"4": "text-decoration:underline"
|
|
515
|
+
};
|
|
516
|
+
function parseAnsi(raw) {
|
|
517
|
+
const spans = [];
|
|
518
|
+
const re = /\x1b\[([0-9;]*)m/g;
|
|
519
|
+
let last = 0;
|
|
520
|
+
let styles = [];
|
|
521
|
+
let match;
|
|
522
|
+
while ((match = re.exec(raw)) !== null) {
|
|
523
|
+
if (match.index > last) spans.push({
|
|
524
|
+
text: raw.slice(last, match.index),
|
|
525
|
+
style: styles.join(";")
|
|
526
|
+
});
|
|
527
|
+
const codes = (match[1] ?? "").split(";").filter(Boolean);
|
|
528
|
+
for (const code of codes) if (code === "0" || code === "") styles = [];
|
|
529
|
+
else {
|
|
530
|
+
const rule = ANSI_COLORS[code];
|
|
531
|
+
if (rule) styles.push(rule);
|
|
532
|
+
}
|
|
533
|
+
last = match.index + match[0].length;
|
|
534
|
+
}
|
|
535
|
+
if (last < raw.length) spans.push({
|
|
536
|
+
text: raw.slice(last),
|
|
537
|
+
style: styles.join(";")
|
|
538
|
+
});
|
|
539
|
+
return spans;
|
|
540
|
+
}
|
|
541
|
+
function spanToStyle(style) {
|
|
542
|
+
const out = {};
|
|
543
|
+
for (const rule of style.split(";")) {
|
|
544
|
+
const [k, v] = rule.split(":");
|
|
545
|
+
if (!k || !v) continue;
|
|
546
|
+
const key = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
547
|
+
out[key] = v;
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function AnsiHtml({ text, className, as = "pre" }) {
|
|
552
|
+
const children = parseAnsi(text).map((s, i) => s.style ? /* @__PURE__ */ jsx("span", {
|
|
553
|
+
style: spanToStyle(s.style),
|
|
554
|
+
children: s.text
|
|
555
|
+
}, i) : /* @__PURE__ */ jsx("span", { children: s.text }, i));
|
|
556
|
+
if (as === "span") return /* @__PURE__ */ jsx("span", {
|
|
557
|
+
className,
|
|
558
|
+
children
|
|
559
|
+
});
|
|
560
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
561
|
+
className,
|
|
562
|
+
children
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/data/Avatar.tsx
|
|
568
|
+
function Avatar({ src, alt, size = 20, rounded = "full", title, href, colorKey, onError, className }) {
|
|
569
|
+
const shape = rounded === "full" ? "rounded-full" : "rounded";
|
|
570
|
+
const key = colorKey ?? alt;
|
|
571
|
+
const base = cn("inline-block shrink-0", shape);
|
|
572
|
+
const content = src ? /* @__PURE__ */ jsx("img", {
|
|
573
|
+
src,
|
|
574
|
+
alt,
|
|
575
|
+
title: title ?? alt,
|
|
576
|
+
width: size,
|
|
577
|
+
height: size,
|
|
578
|
+
className: cn(base, "bg-muted", className),
|
|
579
|
+
loading: "lazy",
|
|
580
|
+
onError
|
|
581
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
582
|
+
className: cn(base, paletteClass(key), "inline-flex items-center justify-center font-semibold", className),
|
|
583
|
+
style: {
|
|
584
|
+
width: size,
|
|
585
|
+
height: size,
|
|
586
|
+
fontSize: Math.max(9, Math.floor(size * .5))
|
|
587
|
+
},
|
|
588
|
+
title: title ?? alt,
|
|
589
|
+
children: (alt.replace(/^@/, "").charAt(0) || "?").toUpperCase()
|
|
590
|
+
});
|
|
591
|
+
if (href) return /* @__PURE__ */ jsx("a", {
|
|
592
|
+
href,
|
|
593
|
+
target: "_blank",
|
|
594
|
+
rel: "noopener noreferrer",
|
|
595
|
+
className: "inline-flex shrink-0",
|
|
596
|
+
onClick: (e) => e.stopPropagation(),
|
|
597
|
+
children: content
|
|
598
|
+
});
|
|
599
|
+
return content;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/data/Badge.tsx
|
|
604
|
+
const badgeVariants = cva("inline-flex items-center gap-1 rounded-full font-medium whitespace-nowrap", {
|
|
605
|
+
variants: {
|
|
606
|
+
tone: {
|
|
607
|
+
neutral: "",
|
|
608
|
+
success: "",
|
|
609
|
+
danger: "",
|
|
610
|
+
warning: "",
|
|
611
|
+
info: ""
|
|
612
|
+
},
|
|
613
|
+
variant: {
|
|
614
|
+
soft: "",
|
|
615
|
+
solid: "",
|
|
616
|
+
outline: "border bg-transparent"
|
|
617
|
+
},
|
|
618
|
+
size: {
|
|
619
|
+
sm: "text-[10px] px-1.5 py-0 h-4",
|
|
620
|
+
md: "text-xs px-2 py-0.5",
|
|
621
|
+
lg: "text-sm px-2.5 py-1"
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
compoundVariants: [
|
|
625
|
+
{
|
|
626
|
+
tone: "neutral",
|
|
627
|
+
variant: "soft",
|
|
628
|
+
class: "bg-muted text-foreground"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
tone: "neutral",
|
|
632
|
+
variant: "solid",
|
|
633
|
+
class: "bg-foreground text-background"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
tone: "neutral",
|
|
637
|
+
variant: "outline",
|
|
638
|
+
class: "border-border text-foreground"
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
tone: "success",
|
|
642
|
+
variant: "soft",
|
|
643
|
+
class: "bg-green-100 text-green-800 dark:bg-green-500/20 dark:text-green-300"
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
tone: "success",
|
|
647
|
+
variant: "solid",
|
|
648
|
+
class: "bg-green-500 text-white"
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
tone: "success",
|
|
652
|
+
variant: "outline",
|
|
653
|
+
class: "border-green-500 text-green-700 dark:text-green-400"
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
tone: "danger",
|
|
657
|
+
variant: "soft",
|
|
658
|
+
class: "bg-red-100 text-red-800 dark:bg-red-500/20 dark:text-red-300"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
tone: "danger",
|
|
662
|
+
variant: "solid",
|
|
663
|
+
class: "bg-red-500 text-white"
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
tone: "danger",
|
|
667
|
+
variant: "outline",
|
|
668
|
+
class: "border-red-500 text-red-700 dark:text-red-400"
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
tone: "warning",
|
|
672
|
+
variant: "soft",
|
|
673
|
+
class: "bg-yellow-100 text-yellow-800 dark:bg-yellow-500/20 dark:text-yellow-300"
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
tone: "warning",
|
|
677
|
+
variant: "solid",
|
|
678
|
+
class: "bg-yellow-400 text-yellow-950"
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
tone: "warning",
|
|
682
|
+
variant: "outline",
|
|
683
|
+
class: "border-yellow-500 text-yellow-700 dark:text-yellow-400"
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
tone: "info",
|
|
687
|
+
variant: "soft",
|
|
688
|
+
class: "bg-blue-100 text-blue-800 dark:bg-blue-500/20 dark:text-blue-300"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
tone: "info",
|
|
692
|
+
variant: "solid",
|
|
693
|
+
class: "bg-blue-500 text-white"
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
tone: "info",
|
|
697
|
+
variant: "outline",
|
|
698
|
+
class: "border-blue-500 text-blue-700 dark:text-blue-400"
|
|
699
|
+
}
|
|
700
|
+
],
|
|
701
|
+
defaultVariants: {
|
|
702
|
+
tone: "neutral",
|
|
703
|
+
variant: "soft",
|
|
704
|
+
size: "md"
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
function Badge({ tone, variant, size, icon, count, children, className }) {
|
|
708
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
709
|
+
className: cn(badgeVariants({
|
|
710
|
+
tone,
|
|
711
|
+
variant,
|
|
712
|
+
size
|
|
713
|
+
}), className),
|
|
714
|
+
children: [
|
|
715
|
+
icon && /* @__PURE__ */ jsx(Icon, {
|
|
716
|
+
name: icon,
|
|
717
|
+
className: "text-[1em]"
|
|
718
|
+
}),
|
|
719
|
+
count !== void 0 && /* @__PURE__ */ jsx("span", { children: count }),
|
|
720
|
+
children
|
|
721
|
+
]
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/data/SortableHeader.tsx
|
|
727
|
+
function SortableHeader({ active, dir, onClick, align = "left", className, children }) {
|
|
728
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
729
|
+
type: "button",
|
|
730
|
+
onClick,
|
|
731
|
+
className: cn("inline-flex items-center gap-1 cursor-pointer select-none hover:text-foreground", active ? "text-primary" : "text-muted-foreground", align === "right" ? "justify-end" : align === "center" ? "justify-center" : "justify-start", className),
|
|
732
|
+
children: [children, active ? /* @__PURE__ */ jsx("span", {
|
|
733
|
+
"aria-hidden": true,
|
|
734
|
+
children: dir === "asc" ? "↑" : "↓"
|
|
735
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
736
|
+
"aria-hidden": true,
|
|
737
|
+
className: "opacity-40",
|
|
738
|
+
children: "↕"
|
|
739
|
+
})]
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/data/TreeNode.tsx
|
|
745
|
+
function TreeNode({ node, depth = 0, expandAll = null, selected = null, defaultOpen, getChildren, getKey, onSelect, renderRow, rowClass, indentPx = 16, basePaddingPx = 8 }) {
|
|
746
|
+
const children = getChildren(node);
|
|
747
|
+
const hasChildren = (children?.length ?? 0) > 0;
|
|
748
|
+
const [open, setOpen] = useState(defaultOpen ? defaultOpen(node, depth) : depth < 1);
|
|
749
|
+
const prevExpandAll = useRef(expandAll);
|
|
750
|
+
const isSelected = selected === node;
|
|
751
|
+
useEffect(() => {
|
|
752
|
+
if (expandAll !== null && expandAll !== prevExpandAll.current) setOpen(expandAll);
|
|
753
|
+
prevExpandAll.current = expandAll;
|
|
754
|
+
}, [expandAll]);
|
|
755
|
+
function toggle() {
|
|
756
|
+
if (hasChildren) setOpen((o) => !o);
|
|
757
|
+
}
|
|
758
|
+
const rowClassName = rowClass ? rowClass(node, isSelected) : isSelected ? "bg-primary/10 border-l-2 border-primary" : "hover:bg-accent";
|
|
759
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
760
|
+
role: "treeitem",
|
|
761
|
+
"aria-expanded": hasChildren ? open : void 0,
|
|
762
|
+
"aria-selected": isSelected,
|
|
763
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
764
|
+
className: cn("flex items-center gap-1.5 py-1 px-2 cursor-pointer text-sm", rowClassName),
|
|
765
|
+
style: { paddingLeft: `${depth * indentPx + basePaddingPx}px` },
|
|
766
|
+
onClick: (e) => {
|
|
767
|
+
e.stopPropagation();
|
|
768
|
+
onSelect?.(node);
|
|
769
|
+
toggle();
|
|
770
|
+
},
|
|
771
|
+
children: [hasChildren ? /* @__PURE__ */ jsx(Icon, {
|
|
772
|
+
name: open ? "codicon:chevron-down" : "codicon:chevron-right",
|
|
773
|
+
className: "text-muted-foreground text-xs w-3"
|
|
774
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
775
|
+
className: "w-3 shrink-0",
|
|
776
|
+
"aria-hidden": true
|
|
777
|
+
}), renderRow({
|
|
778
|
+
node,
|
|
779
|
+
depth,
|
|
780
|
+
open,
|
|
781
|
+
selected: isSelected,
|
|
782
|
+
hasChildren,
|
|
783
|
+
toggle
|
|
784
|
+
})]
|
|
785
|
+
}), open && hasChildren && /* @__PURE__ */ jsx("div", {
|
|
786
|
+
role: "group",
|
|
787
|
+
children: children.map((child) => /* @__PURE__ */ jsx(TreeNode, {
|
|
788
|
+
node: child,
|
|
789
|
+
depth: depth + 1,
|
|
790
|
+
expandAll,
|
|
791
|
+
selected,
|
|
792
|
+
defaultOpen,
|
|
793
|
+
getChildren,
|
|
794
|
+
getKey,
|
|
795
|
+
onSelect,
|
|
796
|
+
renderRow,
|
|
797
|
+
rowClass,
|
|
798
|
+
indentPx,
|
|
799
|
+
basePaddingPx
|
|
800
|
+
}, getKey(child)))
|
|
801
|
+
})]
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
//#endregion
|
|
806
|
+
//#region src/data/Tree.tsx
|
|
807
|
+
function Tree({ roots, empty, className, showControls = true, expandAll: controlledExpandAll, onExpandAllChange, toolbarClassName, ...nodeProps }) {
|
|
808
|
+
const [internalExpandAll, setInternalExpandAll] = useState(null);
|
|
809
|
+
const isControlled = onExpandAllChange !== void 0;
|
|
810
|
+
const expandAll = isControlled ? controlledExpandAll ?? null : internalExpandAll;
|
|
811
|
+
const setExpandAll = (next) => {
|
|
812
|
+
if (isControlled) onExpandAllChange?.(next);
|
|
813
|
+
else setInternalExpandAll(next);
|
|
814
|
+
};
|
|
815
|
+
if (roots.length === 0) return /* @__PURE__ */ jsx(Fragment$1, { children: empty ?? null });
|
|
816
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
817
|
+
className: cn("flex flex-col min-h-0", className),
|
|
818
|
+
children: [showControls && /* @__PURE__ */ jsxs("div", {
|
|
819
|
+
className: cn("flex items-center gap-1 border-b border-border px-2 py-1 text-xs text-muted-foreground", toolbarClassName),
|
|
820
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => setExpandAll(true),
|
|
823
|
+
"aria-pressed": expandAll === true,
|
|
824
|
+
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-accent hover:text-accent-foreground", expandAll === true && "bg-accent text-accent-foreground"),
|
|
825
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
826
|
+
name: "codicon:expand-all",
|
|
827
|
+
className: "text-xs"
|
|
828
|
+
}), "Expand all"]
|
|
829
|
+
}), /* @__PURE__ */ jsxs("button", {
|
|
830
|
+
type: "button",
|
|
831
|
+
onClick: () => setExpandAll(false),
|
|
832
|
+
"aria-pressed": expandAll === false,
|
|
833
|
+
className: cn("inline-flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-accent hover:text-accent-foreground", expandAll === false && "bg-accent text-accent-foreground"),
|
|
834
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
835
|
+
name: "codicon:collapse-all",
|
|
836
|
+
className: "text-xs"
|
|
837
|
+
}), "Collapse all"]
|
|
838
|
+
})]
|
|
839
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
840
|
+
role: "tree",
|
|
841
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
842
|
+
children: roots.map((root) => /* @__PURE__ */ jsx(TreeNode, {
|
|
843
|
+
node: root,
|
|
844
|
+
expandAll,
|
|
845
|
+
...nodeProps
|
|
846
|
+
}, nodeProps.getKey(root)))
|
|
847
|
+
})]
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
//#endregion
|
|
852
|
+
//#region src/data/Clicky.tsx
|
|
853
|
+
function Clicky({ data, className }) {
|
|
854
|
+
const parsed = parseClickyData(data);
|
|
855
|
+
if (!parsed.ok) return /* @__PURE__ */ jsxs("div", {
|
|
856
|
+
className: cn("rounded-md border border-destructive/30 bg-destructive/5 p-density-3", className),
|
|
857
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
858
|
+
className: "text-sm font-medium text-destructive",
|
|
859
|
+
children: "Invalid Clicky payload"
|
|
860
|
+
}), /* @__PURE__ */ jsxs("pre", {
|
|
861
|
+
className: "mt-2 whitespace-pre-wrap break-all text-xs text-muted-foreground",
|
|
862
|
+
children: [parsed.message, parsed.raw ? `\n\n${parsed.raw}` : ""]
|
|
863
|
+
})]
|
|
864
|
+
});
|
|
865
|
+
return /* @__PURE__ */ jsx("div", {
|
|
866
|
+
className,
|
|
867
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: parsed.document.node })
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
function parseClickyData(data) {
|
|
871
|
+
if (typeof data === "string") try {
|
|
872
|
+
return normalizeClickyDocument(JSON.parse(data));
|
|
873
|
+
} catch (error) {
|
|
874
|
+
return {
|
|
875
|
+
ok: false,
|
|
876
|
+
message: error instanceof Error ? error.message : "Failed to parse JSON",
|
|
877
|
+
raw: data
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
return normalizeClickyDocument(data);
|
|
881
|
+
}
|
|
882
|
+
function normalizeClickyDocument(data) {
|
|
883
|
+
if (!data || typeof data !== "object") return {
|
|
884
|
+
ok: false,
|
|
885
|
+
message: "Payload must be an object",
|
|
886
|
+
raw: String(data ?? "")
|
|
887
|
+
};
|
|
888
|
+
const candidate = data;
|
|
889
|
+
if ("version" in candidate && candidate.version === 1 && candidate.node && isClickyNode(candidate.node)) return {
|
|
890
|
+
ok: true,
|
|
891
|
+
document: candidate
|
|
892
|
+
};
|
|
893
|
+
if (isClickyNode(candidate)) return {
|
|
894
|
+
ok: true,
|
|
895
|
+
document: {
|
|
896
|
+
version: 1,
|
|
897
|
+
node: candidate
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
return {
|
|
901
|
+
ok: false,
|
|
902
|
+
message: "Payload is neither a Clicky document nor a Clicky node",
|
|
903
|
+
raw: JSON.stringify(data, null, 2)
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function isClickyNode(value) {
|
|
907
|
+
return !!value && typeof value === "object" && typeof value.kind === "string";
|
|
908
|
+
}
|
|
909
|
+
function ClickyNodeRenderer({ node }) {
|
|
910
|
+
if (!node) return null;
|
|
911
|
+
switch (node.kind) {
|
|
912
|
+
case "text": return /* @__PURE__ */ jsx(ClickyText, { node });
|
|
913
|
+
case "icon": return /* @__PURE__ */ jsx(ClickyIconNode, { node });
|
|
914
|
+
case "list": return /* @__PURE__ */ jsx(ClickyList, { node });
|
|
915
|
+
case "map": return /* @__PURE__ */ jsx(ClickyMap, { node });
|
|
916
|
+
case "table": return /* @__PURE__ */ jsx(ClickyTable, { node });
|
|
917
|
+
case "tree": return /* @__PURE__ */ jsx(ClickyTreeNode, { node });
|
|
918
|
+
case "code": return /* @__PURE__ */ jsx(ClickyCodeBlock, { node });
|
|
919
|
+
case "collapsed": return /* @__PURE__ */ jsx(ClickyCollapsed, { node });
|
|
920
|
+
case "button": return /* @__PURE__ */ jsx(ClickyButtonNode, { node });
|
|
921
|
+
case "button-group": return /* @__PURE__ */ jsx(ClickyButtonGroup, { node });
|
|
922
|
+
case "html": return /* @__PURE__ */ jsx(ClickyHtmlNode, { node });
|
|
923
|
+
case "comment": return null;
|
|
924
|
+
default: return /* @__PURE__ */ jsx("pre", {
|
|
925
|
+
className: "rounded-md border border-border bg-muted p-density-3 text-xs",
|
|
926
|
+
children: JSON.stringify(node, null, 2)
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function ClickyText({ node }) {
|
|
931
|
+
const inlineStyle = toInlineStyle(node.style, node.text ?? node.plain);
|
|
932
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [node.text, node.children?.map((child, index) => /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: child }) }, index))] });
|
|
933
|
+
if (!node.style && !node.tooltip) return /* @__PURE__ */ jsx(Fragment$1, { children: content });
|
|
934
|
+
return /* @__PURE__ */ jsx("span", {
|
|
935
|
+
style: inlineStyle,
|
|
936
|
+
title: node.tooltip?.plain,
|
|
937
|
+
className: cn(node.style?.className, (node.text ?? "").includes("\n") && "whitespace-pre-wrap"),
|
|
938
|
+
children: content
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function ClickyIconNode({ node }) {
|
|
942
|
+
return /* @__PURE__ */ jsx("span", {
|
|
943
|
+
style: toInlineStyle(node.style, node.plain ?? node.unicode),
|
|
944
|
+
title: node.tooltip?.plain,
|
|
945
|
+
className: "inline-flex items-center",
|
|
946
|
+
children: node.iconify ? /* @__PURE__ */ jsx(Icon, { name: node.iconify }) : /* @__PURE__ */ jsx("span", { children: node.unicode ?? node.plain })
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
function ClickyList({ node }) {
|
|
950
|
+
const items = node.items ?? [];
|
|
951
|
+
if (items.length === 0) return null;
|
|
952
|
+
if (node.inline) return /* @__PURE__ */ jsx("span", {
|
|
953
|
+
className: "inline-flex flex-wrap items-start gap-1",
|
|
954
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsx("span", {
|
|
955
|
+
className: "text-muted-foreground",
|
|
956
|
+
children: ","
|
|
957
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
958
|
+
className: "inline-flex items-center gap-1",
|
|
959
|
+
children: [
|
|
960
|
+
!node.ordered && node.bullet && /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.bullet }),
|
|
961
|
+
node.ordered && /* @__PURE__ */ jsxs("span", {
|
|
962
|
+
className: "text-muted-foreground",
|
|
963
|
+
children: [index + 1, "."]
|
|
964
|
+
}),
|
|
965
|
+
/* @__PURE__ */ jsx(ClickyNodeRenderer, { node: item })
|
|
966
|
+
]
|
|
967
|
+
})] }, index))
|
|
968
|
+
});
|
|
969
|
+
if (node.unstyled) return /* @__PURE__ */ jsx("div", {
|
|
970
|
+
className: "space-y-1",
|
|
971
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs("div", {
|
|
972
|
+
className: "flex items-start gap-2",
|
|
973
|
+
children: [
|
|
974
|
+
!node.ordered && node.bullet && /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.bullet }),
|
|
975
|
+
node.ordered && /* @__PURE__ */ jsxs("span", {
|
|
976
|
+
className: "text-muted-foreground",
|
|
977
|
+
children: [index + 1, "."]
|
|
978
|
+
}),
|
|
979
|
+
/* @__PURE__ */ jsx("div", {
|
|
980
|
+
className: "min-w-0 flex-1",
|
|
981
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: item })
|
|
982
|
+
})
|
|
983
|
+
]
|
|
984
|
+
}, index))
|
|
985
|
+
});
|
|
986
|
+
return /* @__PURE__ */ jsx(node.ordered ? "ol" : "ul", {
|
|
987
|
+
className: cn("ml-5 space-y-1 text-sm", node.ordered ? "list-decimal" : "list-disc"),
|
|
988
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: item }) }, index))
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
function ClickyMap({ node }) {
|
|
992
|
+
const fields = node.fields ?? [];
|
|
993
|
+
if (fields.length === 0) return null;
|
|
994
|
+
const inlineFields = fields.filter((field) => isInlineNode(field.value));
|
|
995
|
+
const blockFields = fields.filter((field) => !isInlineNode(field.value));
|
|
996
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
997
|
+
className: "space-y-density-4",
|
|
998
|
+
children: [inlineFields.length > 0 && /* @__PURE__ */ jsx("dl", {
|
|
999
|
+
className: "grid gap-density-3 md:grid-cols-2",
|
|
1000
|
+
children: inlineFields.map((field) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", {
|
|
1001
|
+
className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
|
|
1002
|
+
children: field.label || prettifyName(field.name)
|
|
1003
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
1004
|
+
className: "mt-1 text-sm text-foreground",
|
|
1005
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: field.value })
|
|
1006
|
+
})] }, field.name))
|
|
1007
|
+
}), blockFields.map((field) => /* @__PURE__ */ jsxs("section", {
|
|
1008
|
+
className: "space-y-2",
|
|
1009
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1010
|
+
className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground",
|
|
1011
|
+
children: field.label || prettifyName(field.name)
|
|
1012
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1013
|
+
className: "rounded-md border border-border bg-background p-density-3",
|
|
1014
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: field.value })
|
|
1015
|
+
})]
|
|
1016
|
+
}, field.name))]
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
function ClickyTable({ node }) {
|
|
1020
|
+
const columns = node.columns ?? [];
|
|
1021
|
+
const rows = node.rows ?? [];
|
|
1022
|
+
const hasDetail = rows.some((row) => !!row.detail);
|
|
1023
|
+
const [sortKey, setSortKey] = useState(columns[0]?.name ?? "");
|
|
1024
|
+
const [sortDir, setSortDir] = useState("asc");
|
|
1025
|
+
if (columns.length === 0 || rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1026
|
+
className: "text-sm text-muted-foreground",
|
|
1027
|
+
children: "No data"
|
|
1028
|
+
});
|
|
1029
|
+
const sortedRows = [...rows].sort((left, right) => {
|
|
1030
|
+
if (!sortKey) return 0;
|
|
1031
|
+
const leftValue = left.cells[sortKey]?.plain ?? left.cells[sortKey]?.text ?? "";
|
|
1032
|
+
const rightValue = right.cells[sortKey]?.plain ?? right.cells[sortKey]?.text ?? "";
|
|
1033
|
+
return sortDir === "asc" ? compareClickyValues(leftValue, rightValue) : compareClickyValues(rightValue, leftValue);
|
|
1034
|
+
});
|
|
1035
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1036
|
+
className: "overflow-auto rounded-md border border-border",
|
|
1037
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
1038
|
+
className: "w-full text-left text-sm table-fixed",
|
|
1039
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
1040
|
+
className: "sticky top-0 bg-muted/50",
|
|
1041
|
+
children: /* @__PURE__ */ jsxs("tr", {
|
|
1042
|
+
className: "border-b border-border text-xs text-muted-foreground",
|
|
1043
|
+
children: [hasDetail && /* @__PURE__ */ jsx("th", { className: "w-8 px-2 py-2" }), columns.map((column) => /* @__PURE__ */ jsx("th", {
|
|
1044
|
+
className: "px-2 py-2 font-medium",
|
|
1045
|
+
children: /* @__PURE__ */ jsx(SortableHeader, {
|
|
1046
|
+
active: sortKey === column.name,
|
|
1047
|
+
dir: sortKey === column.name ? sortDir : void 0,
|
|
1048
|
+
align: column.align ?? "left",
|
|
1049
|
+
onClick: () => {
|
|
1050
|
+
if (sortKey === column.name) setSortDir((current) => current === "asc" ? "desc" : "asc");
|
|
1051
|
+
else {
|
|
1052
|
+
setSortKey(column.name);
|
|
1053
|
+
setSortDir("asc");
|
|
1054
|
+
}
|
|
1055
|
+
},
|
|
1056
|
+
children: column.header ? /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: column.header }) : column.label || prettifyName(column.name)
|
|
1057
|
+
})
|
|
1058
|
+
}, column.name))]
|
|
1059
|
+
})
|
|
1060
|
+
}), /* @__PURE__ */ jsx("tbody", { children: sortedRows.map((row, index) => /* @__PURE__ */ jsx(ClickyTableRow, {
|
|
1061
|
+
row,
|
|
1062
|
+
columns,
|
|
1063
|
+
hasDetail
|
|
1064
|
+
}, `${index}-${row.cells[columns[0]?.name ?? ""]?.plain ?? ""}`)) })]
|
|
1065
|
+
})
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
function ClickyTableRow({ row, columns, hasDetail }) {
|
|
1069
|
+
const [open, setOpen] = useState(false);
|
|
1070
|
+
const expandable = !!row.detail;
|
|
1071
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("tr", {
|
|
1072
|
+
className: cn("border-b border-border align-top", expandable && "cursor-pointer hover:bg-accent/50"),
|
|
1073
|
+
onClick: expandable ? () => setOpen((current) => !current) : void 0,
|
|
1074
|
+
children: [hasDetail && /* @__PURE__ */ jsx("td", {
|
|
1075
|
+
className: "px-2 py-2 text-center text-muted-foreground",
|
|
1076
|
+
children: expandable ? open ? "▼" : "▶" : ""
|
|
1077
|
+
}), columns.map((column) => /* @__PURE__ */ jsx("td", {
|
|
1078
|
+
className: cn("px-2 py-2", alignmentClass(column.align)),
|
|
1079
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: row.cells[column.name] })
|
|
1080
|
+
}, column.name))]
|
|
1081
|
+
}), open && row.detail && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
1082
|
+
colSpan: columns.length + (hasDetail ? 1 : 0),
|
|
1083
|
+
className: "bg-muted/40 p-density-3",
|
|
1084
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1085
|
+
className: "rounded-md border border-border bg-background p-density-3",
|
|
1086
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: row.detail })
|
|
1087
|
+
})
|
|
1088
|
+
}) })] });
|
|
1089
|
+
}
|
|
1090
|
+
function ClickyTreeNode({ node }) {
|
|
1091
|
+
const roots = node.roots ?? [];
|
|
1092
|
+
if (roots.length === 0) return null;
|
|
1093
|
+
return /* @__PURE__ */ jsx(Tree, {
|
|
1094
|
+
roots,
|
|
1095
|
+
getChildren: (item) => item.children,
|
|
1096
|
+
getKey: (item) => item.id,
|
|
1097
|
+
defaultOpen: (_, depth) => depth < 1,
|
|
1098
|
+
renderRow: ({ node: item }) => /* @__PURE__ */ jsx("div", {
|
|
1099
|
+
className: "min-w-0 flex-1 truncate",
|
|
1100
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: item.label })
|
|
1101
|
+
})
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
function ClickyCodeBlock({ node }) {
|
|
1105
|
+
const html = node.highlightedHtml ? sanitizeHtml(node.highlightedHtml) : "";
|
|
1106
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1107
|
+
className: "overflow-hidden rounded-md border border-border bg-muted/40",
|
|
1108
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1109
|
+
className: "border-b border-border px-3 py-1.5 text-[11px] uppercase tracking-wide text-muted-foreground",
|
|
1110
|
+
children: node.language || "text"
|
|
1111
|
+
}), html ? /* @__PURE__ */ jsx("div", {
|
|
1112
|
+
className: "overflow-auto p-3 text-xs font-mono [&_.chroma]:bg-transparent [&_pre]:m-0 [&_pre]:whitespace-pre-wrap [&_pre]:bg-transparent",
|
|
1113
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1114
|
+
}) : /* @__PURE__ */ jsx("pre", {
|
|
1115
|
+
className: "overflow-auto whitespace-pre-wrap break-words p-3 text-xs font-mono text-foreground",
|
|
1116
|
+
children: node.source ?? node.plain
|
|
1117
|
+
})]
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
function ClickyCollapsed({ node }) {
|
|
1121
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
1122
|
+
className: "rounded-md border border-border bg-background",
|
|
1123
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
1124
|
+
className: "cursor-pointer px-3 py-2 text-sm font-medium text-foreground",
|
|
1125
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.label })
|
|
1126
|
+
}), node.content && /* @__PURE__ */ jsx("div", {
|
|
1127
|
+
className: "border-t border-border px-3 py-3",
|
|
1128
|
+
children: /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.content })
|
|
1129
|
+
})]
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
function ClickyButtonNode({ node }) {
|
|
1133
|
+
const title = [node.id, node.payload].filter(Boolean).join("\n") || void 0;
|
|
1134
|
+
const content = node.label ? /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: node.label }) : node.text;
|
|
1135
|
+
if (node.href) return /* @__PURE__ */ jsx("a", {
|
|
1136
|
+
href: node.href,
|
|
1137
|
+
target: "_blank",
|
|
1138
|
+
rel: "noopener noreferrer",
|
|
1139
|
+
title,
|
|
1140
|
+
className: "inline-flex items-center rounded-md border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground hover:bg-accent",
|
|
1141
|
+
children: content
|
|
1142
|
+
});
|
|
1143
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1144
|
+
type: "button",
|
|
1145
|
+
title,
|
|
1146
|
+
className: "inline-flex items-center rounded-md border border-border bg-muted px-3 py-1.5 text-sm font-medium text-foreground",
|
|
1147
|
+
children: content
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
function ClickyButtonGroup({ node }) {
|
|
1151
|
+
const items = node.items ?? [];
|
|
1152
|
+
if (items.length === 0) return null;
|
|
1153
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1154
|
+
className: "flex flex-wrap gap-2",
|
|
1155
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx(ClickyNodeRenderer, { node: item }, index))
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
function ClickyHtmlNode({ node }) {
|
|
1159
|
+
const sanitized = sanitizeHtml(node.html ?? "");
|
|
1160
|
+
if (!sanitized) return null;
|
|
1161
|
+
return /* @__PURE__ */ jsx(isBlockHtml(sanitized) ? "div" : "span", { dangerouslySetInnerHTML: { __html: sanitized } });
|
|
1162
|
+
}
|
|
1163
|
+
function toInlineStyle(style, text) {
|
|
1164
|
+
if (!style) return text?.includes("\n") ? { whiteSpace: "pre-wrap" } : void 0;
|
|
1165
|
+
const inlineStyle = {};
|
|
1166
|
+
if (style.color) inlineStyle.color = style.color;
|
|
1167
|
+
if (style.backgroundColor) inlineStyle.backgroundColor = style.backgroundColor;
|
|
1168
|
+
if (style.bold) inlineStyle.fontWeight = 700;
|
|
1169
|
+
if (style.faint) inlineStyle.opacity = .7;
|
|
1170
|
+
if (style.italic) inlineStyle.fontStyle = "italic";
|
|
1171
|
+
const decorations = [];
|
|
1172
|
+
if (style.underline) decorations.push("underline");
|
|
1173
|
+
if (style.strikethrough) decorations.push("line-through");
|
|
1174
|
+
if (decorations.length > 0) inlineStyle.textDecoration = decorations.join(" ");
|
|
1175
|
+
if (style.textTransform === "uppercase" || style.textTransform === "lowercase" || style.textTransform === "capitalize") inlineStyle.textTransform = style.textTransform;
|
|
1176
|
+
if (style.maxWidth && style.maxWidth > 0) inlineStyle.maxWidth = `${style.maxWidth}ch`;
|
|
1177
|
+
if (style.maxLines && style.maxLines > 0) {
|
|
1178
|
+
inlineStyle.display = "-webkit-box";
|
|
1179
|
+
inlineStyle.overflow = "hidden";
|
|
1180
|
+
inlineStyle.WebkitLineClamp = style.maxLines;
|
|
1181
|
+
inlineStyle.WebkitBoxOrient = "vertical";
|
|
1182
|
+
inlineStyle.whiteSpace = "pre-wrap";
|
|
1183
|
+
}
|
|
1184
|
+
if (style.truncateMode === "suffix" && style.maxWidth && (!style.maxLines || style.maxLines <= 1)) {
|
|
1185
|
+
inlineStyle.overflow = "hidden";
|
|
1186
|
+
inlineStyle.textOverflow = "ellipsis";
|
|
1187
|
+
inlineStyle.whiteSpace = "nowrap";
|
|
1188
|
+
inlineStyle.display = "inline-block";
|
|
1189
|
+
inlineStyle.verticalAlign = "bottom";
|
|
1190
|
+
}
|
|
1191
|
+
if (style.monospace) inlineStyle.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace";
|
|
1192
|
+
if (!inlineStyle.whiteSpace && text?.includes("\n")) inlineStyle.whiteSpace = "pre-wrap";
|
|
1193
|
+
return Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0;
|
|
1194
|
+
}
|
|
1195
|
+
function sanitizeHtml(raw) {
|
|
1196
|
+
if (!raw) return "";
|
|
1197
|
+
if (typeof DOMParser === "undefined") return raw.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "").replace(/\son\w+="[^"]*"/gi, "").replace(/\son\w+='[^']*'/gi, "");
|
|
1198
|
+
const doc = new DOMParser().parseFromString(raw, "text/html");
|
|
1199
|
+
doc.querySelectorAll("script,iframe,object,embed,form").forEach((element) => element.remove());
|
|
1200
|
+
doc.querySelectorAll("*").forEach((element) => {
|
|
1201
|
+
Array.from(element.attributes).forEach((attribute) => {
|
|
1202
|
+
const name = attribute.name.toLowerCase();
|
|
1203
|
+
const value = attribute.value.trim().toLowerCase();
|
|
1204
|
+
if (name.startsWith("on") || name === "srcdoc") element.removeAttribute(attribute.name);
|
|
1205
|
+
if ((name === "href" || name === "src") && value.startsWith("javascript:")) element.removeAttribute(attribute.name);
|
|
1206
|
+
});
|
|
1207
|
+
});
|
|
1208
|
+
return doc.body.innerHTML;
|
|
1209
|
+
}
|
|
1210
|
+
function prettifyName(name) {
|
|
1211
|
+
return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").replace(/\b\w/g, (value) => value.toUpperCase());
|
|
1212
|
+
}
|
|
1213
|
+
function compareClickyValues(left, right) {
|
|
1214
|
+
const leftNumber = Number(left);
|
|
1215
|
+
const rightNumber = Number(right);
|
|
1216
|
+
if (!Number.isNaN(leftNumber) && !Number.isNaN(rightNumber)) return leftNumber - rightNumber;
|
|
1217
|
+
return left.localeCompare(right, void 0, {
|
|
1218
|
+
numeric: true,
|
|
1219
|
+
sensitivity: "base"
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function isInlineNode(node) {
|
|
1223
|
+
return node.kind === "text" || node.kind === "icon" || node.kind === "html" || node.kind === "button" || node.kind === "button-group";
|
|
1224
|
+
}
|
|
1225
|
+
function alignmentClass(align) {
|
|
1226
|
+
if (align === "right") return "text-right";
|
|
1227
|
+
if (align === "center") return "text-center";
|
|
1228
|
+
}
|
|
1229
|
+
function isBlockHtml(html) {
|
|
1230
|
+
return /<(div|p|pre|table|ul|ol|li|details|blockquote|h[1-6])/i.test(html);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/data/FilterPill.tsx
|
|
1235
|
+
function bodyClasses(mode) {
|
|
1236
|
+
switch (mode) {
|
|
1237
|
+
case "active": return "bg-primary/10 border-primary/40 text-primary font-medium";
|
|
1238
|
+
case "include": return "bg-green-500/10 border-green-500/50 text-green-700 dark:text-green-400 font-medium";
|
|
1239
|
+
case "exclude": return "bg-red-500/10 border-red-500/50 text-red-700 dark:text-red-400 font-medium";
|
|
1240
|
+
default: return "border-border text-muted-foreground hover:bg-accent";
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
const SLOT_TRANSLATE = {
|
|
1244
|
+
exclude: "-translate-x-[21px]",
|
|
1245
|
+
neutral: "translate-x-0",
|
|
1246
|
+
include: "translate-x-[21px]"
|
|
1247
|
+
};
|
|
1248
|
+
function TristateSwitch({ mode, onChange, ariaLabel }) {
|
|
1249
|
+
const slot = mode === "exclude" ? "exclude" : mode === "include" ? "include" : "neutral";
|
|
1250
|
+
const bg = slot === "exclude" ? "bg-red-500" : slot === "include" ? "bg-green-500" : "bg-muted";
|
|
1251
|
+
const fg = slot === "neutral" ? "text-muted-foreground" : "text-white";
|
|
1252
|
+
const onSlot = (target) => {
|
|
1253
|
+
if (target === "neutral") {
|
|
1254
|
+
onChange("neutral");
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
onChange(slot === target ? "neutral" : target);
|
|
1258
|
+
};
|
|
1259
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1260
|
+
role: "radiogroup",
|
|
1261
|
+
"aria-label": ariaLabel,
|
|
1262
|
+
className: cn("relative inline-flex items-center justify-center w-[66px] h-6 rounded-full overflow-hidden transition-colors duration-200 shrink-0", bg),
|
|
1263
|
+
children: [[
|
|
1264
|
+
"exclude",
|
|
1265
|
+
"neutral",
|
|
1266
|
+
"include"
|
|
1267
|
+
].map((target) => /* @__PURE__ */ jsx("button", {
|
|
1268
|
+
type: "button",
|
|
1269
|
+
role: "radio",
|
|
1270
|
+
"aria-checked": slot === target,
|
|
1271
|
+
title: target === "exclude" ? "Exclude" : target === "include" ? "Include" : "Do not filter",
|
|
1272
|
+
onClick: (e) => {
|
|
1273
|
+
e.preventDefault();
|
|
1274
|
+
e.stopPropagation();
|
|
1275
|
+
onSlot(target);
|
|
1276
|
+
},
|
|
1277
|
+
className: cn("relative z-10 flex h-full flex-1 items-center justify-center transition-colors duration-100", fg),
|
|
1278
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1279
|
+
name: target === "exclude" ? "codicon:close" : target === "include" ? "codicon:check" : "codicon:primitive-dot",
|
|
1280
|
+
className: "text-sm"
|
|
1281
|
+
})
|
|
1282
|
+
}, target)), /* @__PURE__ */ jsx("span", {
|
|
1283
|
+
"aria-hidden": true,
|
|
1284
|
+
className: cn("pointer-events-none absolute w-5 h-5 rounded-full bg-white shadow-md transition-transform duration-150", SLOT_TRANSLATE[slot])
|
|
1285
|
+
})]
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
function FilterPill({ mode = "neutral", label, count, icon, badge, onModeChange, onClick, title, className }) {
|
|
1289
|
+
const triState = !!onModeChange;
|
|
1290
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1291
|
+
count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
1292
|
+
className: cn("inline-flex items-center justify-center min-w-[16px] h-[16px] px-1 rounded-full text-[10px] font-bold text-white", badge ?? "bg-muted-foreground"),
|
|
1293
|
+
children: count
|
|
1294
|
+
}),
|
|
1295
|
+
icon && /* @__PURE__ */ jsx(Icon, {
|
|
1296
|
+
name: icon,
|
|
1297
|
+
className: "text-sm"
|
|
1298
|
+
}),
|
|
1299
|
+
/* @__PURE__ */ jsx("span", {
|
|
1300
|
+
className: "truncate",
|
|
1301
|
+
children: label
|
|
1302
|
+
})
|
|
1303
|
+
] });
|
|
1304
|
+
if (triState) {
|
|
1305
|
+
const labelTone = mode === "include" ? "text-green-700 dark:text-green-400" : mode === "exclude" ? "text-red-700 dark:text-red-400" : "text-foreground";
|
|
1306
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1307
|
+
className: cn("inline-flex items-center gap-2 select-none", className),
|
|
1308
|
+
title,
|
|
1309
|
+
children: [/* @__PURE__ */ jsx(TristateSwitch, {
|
|
1310
|
+
mode,
|
|
1311
|
+
onChange: onModeChange,
|
|
1312
|
+
ariaLabel: title
|
|
1313
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1314
|
+
className: cn("inline-flex items-center gap-1.5 text-xs", labelTone),
|
|
1315
|
+
children: content
|
|
1316
|
+
})]
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1320
|
+
type: "button",
|
|
1321
|
+
onClick,
|
|
1322
|
+
title,
|
|
1323
|
+
className: cn("inline-flex items-center gap-1.5 text-xs px-2 py-0.5 rounded-full border transition-colors", bodyClasses(mode), className),
|
|
1324
|
+
children: [/* @__PURE__ */ jsx(LegacyMarker, { mode }), content]
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
function LegacyMarker({ mode }) {
|
|
1328
|
+
if (mode === "include") return /* @__PURE__ */ jsx(Icon, {
|
|
1329
|
+
name: "codicon:add",
|
|
1330
|
+
className: "text-xs"
|
|
1331
|
+
});
|
|
1332
|
+
if (mode === "exclude") return /* @__PURE__ */ jsx(Icon, {
|
|
1333
|
+
name: "codicon:remove",
|
|
1334
|
+
className: "text-xs"
|
|
1335
|
+
});
|
|
1336
|
+
if (mode === "active") return /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-current" });
|
|
1337
|
+
return /* @__PURE__ */ jsx("span", { className: "w-2 h-2 rounded-full bg-current opacity-30" });
|
|
1338
|
+
}
|
|
1339
|
+
function FilterPillGroup({ children, className }) {
|
|
1340
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1341
|
+
className: cn("flex items-center gap-1.5 flex-wrap", className),
|
|
1342
|
+
children
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
function FilterSeparator() {
|
|
1346
|
+
return /* @__PURE__ */ jsx("span", {
|
|
1347
|
+
"aria-hidden": true,
|
|
1348
|
+
className: "text-border mx-0.5",
|
|
1349
|
+
children: "|"
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region src/data/Gauge.tsx
|
|
1355
|
+
const toneClass = {
|
|
1356
|
+
neutral: "text-foreground",
|
|
1357
|
+
success: "text-green-600 dark:text-green-400",
|
|
1358
|
+
warning: "text-yellow-600 dark:text-yellow-400",
|
|
1359
|
+
danger: "text-red-600 dark:text-red-400",
|
|
1360
|
+
info: "text-blue-600 dark:text-blue-400"
|
|
1361
|
+
};
|
|
1362
|
+
function Gauge({ label, value, max = 100, tone = "neutral", suffix = "%", className }) {
|
|
1363
|
+
const pct = max > 0 ? Math.round(value / max * 100) : 0;
|
|
1364
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1365
|
+
className: cn("flex flex-col items-start gap-0.5 rounded-md border border-border bg-background px-density-3 py-density-2", className),
|
|
1366
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1367
|
+
className: "text-[10px] uppercase tracking-wide text-muted-foreground",
|
|
1368
|
+
children: label
|
|
1369
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1370
|
+
className: cn("text-lg font-semibold tabular-nums", toneClass[tone]),
|
|
1371
|
+
children: [max === 100 ? value : pct, /* @__PURE__ */ jsx("span", {
|
|
1372
|
+
className: "text-xs ml-0.5 opacity-60",
|
|
1373
|
+
children: suffix
|
|
1374
|
+
})]
|
|
1375
|
+
})]
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/data/JsonView.tsx
|
|
1381
|
+
function JsonView({ data, name, depth = 0, defaultOpenDepth = 2 }) {
|
|
1382
|
+
const [open, setOpen] = useState(depth < defaultOpenDepth);
|
|
1383
|
+
if (data === null || data === void 0) return /* @__PURE__ */ jsx("span", {
|
|
1384
|
+
className: "text-muted-foreground italic",
|
|
1385
|
+
children: "null"
|
|
1386
|
+
});
|
|
1387
|
+
if (typeof data === "string") return /* @__PURE__ */ jsxs("span", {
|
|
1388
|
+
className: "text-green-700 dark:text-green-400",
|
|
1389
|
+
children: [
|
|
1390
|
+
"\"",
|
|
1391
|
+
data,
|
|
1392
|
+
"\""
|
|
1393
|
+
]
|
|
1394
|
+
});
|
|
1395
|
+
if (typeof data === "number" || typeof data === "boolean") return /* @__PURE__ */ jsx("span", {
|
|
1396
|
+
className: "text-blue-700 dark:text-blue-400",
|
|
1397
|
+
children: String(data)
|
|
1398
|
+
});
|
|
1399
|
+
if (typeof data !== "object") return /* @__PURE__ */ jsx("span", {
|
|
1400
|
+
className: "text-muted-foreground",
|
|
1401
|
+
children: String(data)
|
|
1402
|
+
});
|
|
1403
|
+
const isArray = Array.isArray(data);
|
|
1404
|
+
const entries = isArray ? data.map((v, i) => [i, v]) : Object.entries(data);
|
|
1405
|
+
const [openB, closeB] = isArray ? ["[", "]"] : ["{", "}"];
|
|
1406
|
+
if (entries.length === 0) return /* @__PURE__ */ jsxs("span", {
|
|
1407
|
+
className: "text-muted-foreground",
|
|
1408
|
+
children: [openB, closeB]
|
|
1409
|
+
});
|
|
1410
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1411
|
+
className: "text-sm font-mono",
|
|
1412
|
+
style: { paddingLeft: depth > 0 ? "12px" : "0" },
|
|
1413
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1414
|
+
className: "cursor-pointer hover:bg-accent rounded px-0.5 select-none",
|
|
1415
|
+
onClick: () => setOpen(!open),
|
|
1416
|
+
children: [
|
|
1417
|
+
/* @__PURE__ */ jsx("span", {
|
|
1418
|
+
className: "text-muted-foreground text-xs mr-1",
|
|
1419
|
+
children: open ? "▼" : "▶"
|
|
1420
|
+
}),
|
|
1421
|
+
name && /* @__PURE__ */ jsx("span", {
|
|
1422
|
+
className: "text-purple-600 dark:text-purple-400",
|
|
1423
|
+
children: name
|
|
1424
|
+
}),
|
|
1425
|
+
name && /* @__PURE__ */ jsx("span", {
|
|
1426
|
+
className: "text-muted-foreground",
|
|
1427
|
+
children: ": "
|
|
1428
|
+
}),
|
|
1429
|
+
!open && /* @__PURE__ */ jsxs("span", {
|
|
1430
|
+
className: "text-muted-foreground",
|
|
1431
|
+
children: [
|
|
1432
|
+
openB,
|
|
1433
|
+
" ",
|
|
1434
|
+
entries.length,
|
|
1435
|
+
" ",
|
|
1436
|
+
isArray ? "items" : "keys",
|
|
1437
|
+
" ",
|
|
1438
|
+
closeB
|
|
1439
|
+
]
|
|
1440
|
+
}),
|
|
1441
|
+
open && /* @__PURE__ */ jsx("span", {
|
|
1442
|
+
className: "text-muted-foreground",
|
|
1443
|
+
children: openB
|
|
1444
|
+
})
|
|
1445
|
+
]
|
|
1446
|
+
}), open && /* @__PURE__ */ jsxs(Fragment$1, { children: [entries.map(([key, val]) => /* @__PURE__ */ jsx("div", {
|
|
1447
|
+
className: "pl-3 border-l border-border ml-1",
|
|
1448
|
+
children: typeof val === "object" && val !== null ? /* @__PURE__ */ jsx(JsonView, {
|
|
1449
|
+
data: val,
|
|
1450
|
+
name: String(key),
|
|
1451
|
+
depth: depth + 1,
|
|
1452
|
+
defaultOpenDepth
|
|
1453
|
+
}) : /* @__PURE__ */ jsxs("div", { children: [
|
|
1454
|
+
/* @__PURE__ */ jsx("span", {
|
|
1455
|
+
className: "text-purple-600 dark:text-purple-400",
|
|
1456
|
+
children: isArray ? "" : String(key)
|
|
1457
|
+
}),
|
|
1458
|
+
!isArray && /* @__PURE__ */ jsx("span", {
|
|
1459
|
+
className: "text-muted-foreground",
|
|
1460
|
+
children: ": "
|
|
1461
|
+
}),
|
|
1462
|
+
/* @__PURE__ */ jsx(JsonView, {
|
|
1463
|
+
data: val,
|
|
1464
|
+
depth: depth + 1,
|
|
1465
|
+
defaultOpenDepth
|
|
1466
|
+
})
|
|
1467
|
+
] })
|
|
1468
|
+
}, key)), /* @__PURE__ */ jsx("span", {
|
|
1469
|
+
className: "text-muted-foreground",
|
|
1470
|
+
children: closeB
|
|
1471
|
+
})] })]
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
//#endregion
|
|
1476
|
+
//#region src/data/LogViewer.tsx
|
|
1477
|
+
function LogViewer({ logs, collapsedLines = 5, maxExpandedVh = 70, bgClass = "bg-muted", borderClass = "border-border", className }) {
|
|
1478
|
+
const [expanded, setExpanded] = useState(false);
|
|
1479
|
+
const lines = useMemo(() => logs.split("\n"), [logs]);
|
|
1480
|
+
const hasMore = lines.length > collapsedLines;
|
|
1481
|
+
const maxHeight = expanded ? `${maxExpandedVh}vh` : `${collapsedLines * 1.4}em`;
|
|
1482
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1483
|
+
className: cn("relative", className),
|
|
1484
|
+
children: [/* @__PURE__ */ jsx("pre", {
|
|
1485
|
+
className: cn("mt-0.5 text-[11px] text-muted-foreground rounded p-1.5 whitespace-pre-wrap overflow-y-auto border transition-all duration-200", bgClass, borderClass),
|
|
1486
|
+
style: { maxHeight },
|
|
1487
|
+
children: expanded ? logs : lines.slice(0, collapsedLines).join("\n")
|
|
1488
|
+
}), hasMore && /* @__PURE__ */ jsx("button", {
|
|
1489
|
+
type: "button",
|
|
1490
|
+
className: cn("text-[10px] mt-0.5", expanded ? "text-muted-foreground" : "text-primary hover:opacity-80"),
|
|
1491
|
+
onClick: () => setExpanded((e) => !e),
|
|
1492
|
+
children: expanded ? `▲ Collapse (${lines.length} lines)` : `▼ Show more (${lines.length} lines)`
|
|
1493
|
+
})]
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
//#endregion
|
|
1498
|
+
//#region src/data/Markdown.tsx
|
|
1499
|
+
let loadPromise = null;
|
|
1500
|
+
function loadMarked() {
|
|
1501
|
+
if (loadPromise) return loadPromise;
|
|
1502
|
+
loadPromise = import("marked").then(({ Marked, Renderer }) => {
|
|
1503
|
+
const renderer = new Renderer();
|
|
1504
|
+
renderer.code = ({ text, lang }) => `<pre class="bg-muted rounded p-2 text-xs overflow-x-auto border border-border my-1.5"><code${lang ? ` class="language-${lang}"` : ""}>${text}</code></pre>`;
|
|
1505
|
+
renderer.codespan = ({ text }) => `<code class="bg-muted rounded px-1 text-xs">${text}</code>`;
|
|
1506
|
+
renderer.heading = ({ text, depth }) => {
|
|
1507
|
+
return `<h${depth} class="${depth <= 1 ? "font-bold text-base mt-2 mb-1" : depth === 2 ? "font-semibold text-sm mt-2 mb-1" : "font-semibold text-sm mt-2 mb-1"}">${text}</h${depth}>`;
|
|
1508
|
+
};
|
|
1509
|
+
renderer.link = ({ href, text }) => `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">${text}</a>`;
|
|
1510
|
+
renderer.blockquote = ({ text }) => `<blockquote class="border-l-2 border-border pl-2 text-muted-foreground italic my-1">${text}</blockquote>`;
|
|
1511
|
+
renderer.tablerow = ({ text }) => `<tr class="border-b border-border">${text}</tr>`;
|
|
1512
|
+
renderer.list = function(token) {
|
|
1513
|
+
const tag = token.ordered ? "ol" : "ul";
|
|
1514
|
+
return `<${tag} class="${token.ordered ? "list-decimal ml-4 my-1" : "list-disc ml-4 my-1"}">${token.items.map((item) => this.listitem(item)).join("")}</${tag}>`;
|
|
1515
|
+
};
|
|
1516
|
+
renderer.listitem = ({ text }) => `<li class="my-0.5">${text}</li>`;
|
|
1517
|
+
renderer.image = ({ href, text }) => `<img src="${href}" alt="${text}" class="max-w-full rounded my-1" />`;
|
|
1518
|
+
renderer.hr = () => "<hr class=\"border-border my-2\" />";
|
|
1519
|
+
renderer.paragraph = ({ text }) => `<p class="mt-1.5">${text}</p>`;
|
|
1520
|
+
return new Marked({
|
|
1521
|
+
renderer,
|
|
1522
|
+
gfm: true,
|
|
1523
|
+
breaks: true
|
|
1524
|
+
});
|
|
1525
|
+
});
|
|
1526
|
+
return loadPromise;
|
|
1527
|
+
}
|
|
1528
|
+
function Markdown({ text, className }) {
|
|
1529
|
+
const [html, setHtml] = useState(null);
|
|
1530
|
+
useEffect(() => {
|
|
1531
|
+
let cancelled = false;
|
|
1532
|
+
loadMarked().then((m) => Promise.resolve(m.parse(text))).then((out) => {
|
|
1533
|
+
if (!cancelled) setHtml(out);
|
|
1534
|
+
}).catch(() => {
|
|
1535
|
+
if (!cancelled) setHtml(text);
|
|
1536
|
+
});
|
|
1537
|
+
return () => {
|
|
1538
|
+
cancelled = true;
|
|
1539
|
+
};
|
|
1540
|
+
}, [text]);
|
|
1541
|
+
if (html === null) return /* @__PURE__ */ jsx("pre", {
|
|
1542
|
+
className: cn("text-sm whitespace-pre-wrap", className),
|
|
1543
|
+
children: text
|
|
1544
|
+
});
|
|
1545
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1546
|
+
className: cn("prose prose-sm max-w-none dark:prose-invert", className),
|
|
1547
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/data/ProgressBar.tsx
|
|
1553
|
+
function ProgressBar({ segments, total, height = "h-2", className }) {
|
|
1554
|
+
if (total === 0) return null;
|
|
1555
|
+
const tooltip = segments.filter((s) => s.count > 0).map((s) => `${s.count} ${s.label}`).join(", ");
|
|
1556
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1557
|
+
className: cn("w-full bg-muted rounded-full flex overflow-hidden", height, className),
|
|
1558
|
+
title: tooltip,
|
|
1559
|
+
role: "progressbar",
|
|
1560
|
+
"aria-valuemin": 0,
|
|
1561
|
+
"aria-valuemax": total,
|
|
1562
|
+
"aria-valuenow": segments.reduce((acc, s) => acc + s.count, 0),
|
|
1563
|
+
children: segments.map((seg, i) => {
|
|
1564
|
+
if (seg.count === 0) return null;
|
|
1565
|
+
const pct = seg.count / total * 100;
|
|
1566
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1567
|
+
className: cn(seg.color, height, "transition-all duration-300"),
|
|
1568
|
+
style: { width: `${pct}%` },
|
|
1569
|
+
title: `${seg.count} ${seg.label}`
|
|
1570
|
+
}, i);
|
|
1571
|
+
})
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region src/data/TabButton.tsx
|
|
1577
|
+
function TabButton({ active, onClick, label, icon, count, countColor = "bg-muted-foreground", className }) {
|
|
1578
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1579
|
+
type: "button",
|
|
1580
|
+
role: "tab",
|
|
1581
|
+
"aria-selected": active,
|
|
1582
|
+
onClick,
|
|
1583
|
+
className: cn("inline-flex items-center gap-1.5 px-density-3 py-density-1.5 text-sm rounded-md transition-colors", active ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent hover:text-foreground", className),
|
|
1584
|
+
children: [
|
|
1585
|
+
icon && /* @__PURE__ */ jsx(Icon, { name: icon }),
|
|
1586
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
1587
|
+
count !== void 0 && count > 0 && /* @__PURE__ */ jsx("span", {
|
|
1588
|
+
className: cn("inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full text-[10px] font-bold text-white", countColor),
|
|
1589
|
+
children: count
|
|
1590
|
+
})
|
|
1591
|
+
]
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
//#endregion
|
|
1596
|
+
//#region src/data/TreeGroupHeader.tsx
|
|
1597
|
+
function TreeGroupHeader({ title, open, onToggle, icon, count, className }) {
|
|
1598
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1599
|
+
type: "button",
|
|
1600
|
+
onClick: onToggle,
|
|
1601
|
+
"aria-expanded": open,
|
|
1602
|
+
className: cn("w-full flex items-center gap-2 px-3 py-2 border-b border-border select-none", "hover:bg-accent transition-colors text-left", className),
|
|
1603
|
+
children: [
|
|
1604
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1605
|
+
name: open ? "codicon:chevron-down" : "codicon:chevron-right",
|
|
1606
|
+
className: "text-muted-foreground text-xs"
|
|
1607
|
+
}),
|
|
1608
|
+
icon && /* @__PURE__ */ jsx(Icon, {
|
|
1609
|
+
name: icon,
|
|
1610
|
+
className: "text-base"
|
|
1611
|
+
}),
|
|
1612
|
+
/* @__PURE__ */ jsx("span", {
|
|
1613
|
+
className: "font-medium text-sm flex-1 truncate",
|
|
1614
|
+
children: title
|
|
1615
|
+
}),
|
|
1616
|
+
count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
1617
|
+
className: "text-xs text-muted-foreground",
|
|
1618
|
+
children: count
|
|
1619
|
+
})
|
|
1620
|
+
]
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
//#endregion
|
|
1625
|
+
//#region src/data/diagnostics/utils.ts
|
|
1626
|
+
function countProcesses(node) {
|
|
1627
|
+
if (!node) return 0;
|
|
1628
|
+
return 1 + (node.children || []).reduce((sum, child) => sum + countProcesses(child), 0);
|
|
1629
|
+
}
|
|
1630
|
+
function findProcessByPID(node, pid) {
|
|
1631
|
+
if (!node) return null;
|
|
1632
|
+
if (node.pid === pid) return node;
|
|
1633
|
+
for (const child of node.children || []) {
|
|
1634
|
+
const found = findProcessByPID(child, pid);
|
|
1635
|
+
if (found) return found;
|
|
1636
|
+
}
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
function processStateIcon(status) {
|
|
1640
|
+
const value = (status || "").toLowerCase();
|
|
1641
|
+
if (value.includes("run")) return "codicon:play-circle";
|
|
1642
|
+
if (value.includes("sleep") || value.includes("idle")) return "codicon:clock";
|
|
1643
|
+
if (value.includes("stop") || value.includes("halt")) return "codicon:debug-pause";
|
|
1644
|
+
if (value.includes("zombie") || value.includes("dead")) return "codicon:error";
|
|
1645
|
+
if (value.includes("wait") || value.includes("block")) return "codicon:debug-step-over";
|
|
1646
|
+
return "codicon:circle-filled";
|
|
1647
|
+
}
|
|
1648
|
+
function processStateColor(status) {
|
|
1649
|
+
const value = (status || "").toLowerCase();
|
|
1650
|
+
if (value.includes("run")) return "text-green-600";
|
|
1651
|
+
if (value.includes("sleep") || value.includes("idle")) return "text-amber-500";
|
|
1652
|
+
if (value.includes("stop") || value.includes("halt")) return "text-orange-600";
|
|
1653
|
+
if (value.includes("zombie") || value.includes("dead")) return "text-red-600";
|
|
1654
|
+
if (value.includes("wait") || value.includes("block")) return "text-blue-600";
|
|
1655
|
+
return "text-muted-foreground";
|
|
1656
|
+
}
|
|
1657
|
+
function formatBytes$1(value) {
|
|
1658
|
+
if (!value || value <= 0) return "0 B";
|
|
1659
|
+
const units = [
|
|
1660
|
+
"B",
|
|
1661
|
+
"KB",
|
|
1662
|
+
"MB",
|
|
1663
|
+
"GB",
|
|
1664
|
+
"TB"
|
|
1665
|
+
];
|
|
1666
|
+
let size = value;
|
|
1667
|
+
let unit = 0;
|
|
1668
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
1669
|
+
size /= 1024;
|
|
1670
|
+
unit++;
|
|
1671
|
+
}
|
|
1672
|
+
return `${size >= 10 || unit === 0 ? size.toFixed(0) : size.toFixed(1)} ${units[unit]}`;
|
|
1673
|
+
}
|
|
1674
|
+
function processLabel(node) {
|
|
1675
|
+
if (node.name) return node.name;
|
|
1676
|
+
if (node.command) {
|
|
1677
|
+
const [first] = node.command.split(/\s+/, 1);
|
|
1678
|
+
return first || `pid ${node.pid}`;
|
|
1679
|
+
}
|
|
1680
|
+
return `pid ${node.pid}`;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
//#endregion
|
|
1684
|
+
//#region src/data/diagnostics/DiagnosticsTree.tsx
|
|
1685
|
+
function DiagnosticsTree({ root, selectedPid, expandAll = null, onSelect }) {
|
|
1686
|
+
if (!root) return /* @__PURE__ */ jsxs("div", {
|
|
1687
|
+
className: "p-density-6 text-center text-muted-foreground",
|
|
1688
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1689
|
+
name: "svg-spinners:ring-resize",
|
|
1690
|
+
className: "text-3xl text-blue-500"
|
|
1691
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1692
|
+
className: "mt-density-2",
|
|
1693
|
+
children: "Waiting for process diagnostics..."
|
|
1694
|
+
})]
|
|
1695
|
+
});
|
|
1696
|
+
if (countProcesses(root) === 0) return /* @__PURE__ */ jsx("div", {
|
|
1697
|
+
className: "p-density-6 text-center text-muted-foreground text-sm",
|
|
1698
|
+
children: "No processes available"
|
|
1699
|
+
});
|
|
1700
|
+
return /* @__PURE__ */ jsx(Tree, {
|
|
1701
|
+
roots: [root],
|
|
1702
|
+
getChildren: (n) => n.children,
|
|
1703
|
+
getKey: (n) => n.pid,
|
|
1704
|
+
expandAll,
|
|
1705
|
+
defaultOpen: (n, depth) => !!n.is_root || depth < 1,
|
|
1706
|
+
onSelect: (n) => onSelect(n.pid),
|
|
1707
|
+
renderRow: ({ node }) => {
|
|
1708
|
+
const selected = node.pid === selectedPid;
|
|
1709
|
+
const cpu = node.cpu_percent || 0;
|
|
1710
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1711
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
1712
|
+
name: node.is_root ? "codicon:server-process" : "codicon:debug-alt",
|
|
1713
|
+
className: node.is_root ? "text-base text-blue-600" : "text-base text-muted-foreground"
|
|
1714
|
+
}),
|
|
1715
|
+
/* @__PURE__ */ jsx("span", {
|
|
1716
|
+
className: `truncate ${selected ? "font-semibold text-primary" : "font-medium text-foreground"}`,
|
|
1717
|
+
children: processLabel(node)
|
|
1718
|
+
}),
|
|
1719
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1720
|
+
className: "text-xs text-muted-foreground shrink-0",
|
|
1721
|
+
children: ["pid ", node.pid]
|
|
1722
|
+
}),
|
|
1723
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1724
|
+
node.status && /* @__PURE__ */ jsxs("span", {
|
|
1725
|
+
className: `inline-flex items-center gap-1 text-xs shrink-0 ${processStateColor(node.status)}`,
|
|
1726
|
+
children: [/* @__PURE__ */ jsx(Icon, { name: processStateIcon(node.status) }), node.status]
|
|
1727
|
+
}),
|
|
1728
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1729
|
+
className: "text-xs text-muted-foreground shrink-0 tabular-nums",
|
|
1730
|
+
children: [cpu.toFixed(1), "%"]
|
|
1731
|
+
}),
|
|
1732
|
+
/* @__PURE__ */ jsx("span", {
|
|
1733
|
+
className: "text-xs text-muted-foreground shrink-0 tabular-nums",
|
|
1734
|
+
children: formatBytes$1(node.rss)
|
|
1735
|
+
})
|
|
1736
|
+
] });
|
|
1737
|
+
},
|
|
1738
|
+
rowClass: (node) => node.pid === selectedPid ? "bg-primary/10 border-l-2 border-primary" : "hover:bg-accent"
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
//#endregion
|
|
1743
|
+
//#region src/data/diagnostics/jvm-stacktrace.ts
|
|
1744
|
+
const headerRe$1 = /^"(?<name>[^"]*)"(?<rest>.*)$/;
|
|
1745
|
+
const stateRe = /^\s*java\.lang\.Thread\.State:\s+(?<state>[A-Z_]+)(?:\s+\((?<sub>[^)]+)\))?/;
|
|
1746
|
+
const frameRe = /^\s*at\s+(?<fn>[^\s(]+)\((?<src>[^)]+)\)\s*$/;
|
|
1747
|
+
const srcLineRe = /^(?<file>[^:]+):(?<line>\d+)$/;
|
|
1748
|
+
const annotationRe = /^\s*-\s+(?<kind>locked|waiting on|waiting to lock|parking to wait for)\b(?<rest>.*)$/;
|
|
1749
|
+
function parseJvmThreadDump(text) {
|
|
1750
|
+
const trimmed = text.trim();
|
|
1751
|
+
if (!trimmed) return [];
|
|
1752
|
+
const blocks = splitIntoThreadBlocks(trimmed);
|
|
1753
|
+
const threads = [];
|
|
1754
|
+
for (const block of blocks) {
|
|
1755
|
+
const parsed = parseThreadBlock(block);
|
|
1756
|
+
if (parsed) threads.push(parsed);
|
|
1757
|
+
}
|
|
1758
|
+
return threads;
|
|
1759
|
+
}
|
|
1760
|
+
function splitIntoThreadBlocks(text) {
|
|
1761
|
+
const out = [];
|
|
1762
|
+
let current = [];
|
|
1763
|
+
const lines = text.split("\n");
|
|
1764
|
+
for (const raw of lines) {
|
|
1765
|
+
const line = raw.replace(/\r$/, "");
|
|
1766
|
+
if (line.startsWith("\"") && current.length > 0) {
|
|
1767
|
+
out.push(current.join("\n"));
|
|
1768
|
+
current = [line];
|
|
1769
|
+
} else current.push(line);
|
|
1770
|
+
}
|
|
1771
|
+
if (current.length > 0) out.push(current.join("\n"));
|
|
1772
|
+
return out;
|
|
1773
|
+
}
|
|
1774
|
+
function parseThreadBlock(block) {
|
|
1775
|
+
const lines = block.split("\n");
|
|
1776
|
+
const header = lines[0]?.trim() ?? "";
|
|
1777
|
+
const headerMatch = headerRe$1.exec(header);
|
|
1778
|
+
if (!headerMatch?.groups) return null;
|
|
1779
|
+
const name = headerMatch.groups.name;
|
|
1780
|
+
const rest = headerMatch.groups.rest ?? "";
|
|
1781
|
+
const idMatch = /#(\d+)\b/.exec(rest);
|
|
1782
|
+
const prioMatch = /\bprio=(\d+)/.exec(rest);
|
|
1783
|
+
const nidMatch = /\bnid=(0x[0-9a-f]+)/i.exec(rest);
|
|
1784
|
+
const daemon = /\bdaemon\b/.test(rest);
|
|
1785
|
+
let rawState = extractHeaderStateTrail(rest) ?? "";
|
|
1786
|
+
let state = normalizeJvmState(rawState);
|
|
1787
|
+
const frames = [];
|
|
1788
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1789
|
+
const line = lines[i];
|
|
1790
|
+
if (!line.trim()) continue;
|
|
1791
|
+
const stateMatch = stateRe.exec(line);
|
|
1792
|
+
if (stateMatch?.groups) {
|
|
1793
|
+
const primary = stateMatch.groups.state;
|
|
1794
|
+
const sub = stateMatch.groups.sub;
|
|
1795
|
+
rawState = sub ? `${primary} (${sub})` : primary;
|
|
1796
|
+
state = normalizeJvmState(primary);
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
const frameMatch = frameRe.exec(line);
|
|
1800
|
+
if (frameMatch?.groups) {
|
|
1801
|
+
const functionName = frameMatch.groups.fn;
|
|
1802
|
+
const src = frameMatch.groups.src;
|
|
1803
|
+
const frame = {
|
|
1804
|
+
functionName,
|
|
1805
|
+
displayName: sanitizeJvmFunctionName(functionName),
|
|
1806
|
+
kind: "frame",
|
|
1807
|
+
runtime: isJvmRuntimeFrame(functionName),
|
|
1808
|
+
nativeMethod: src === "Native Method"
|
|
1809
|
+
};
|
|
1810
|
+
const srcMatch = srcLineRe.exec(src);
|
|
1811
|
+
if (srcMatch?.groups) {
|
|
1812
|
+
frame.file = srcMatch.groups.file;
|
|
1813
|
+
frame.line = Number(srcMatch.groups.line);
|
|
1814
|
+
frame.location = `${frame.file}:${frame.line}`;
|
|
1815
|
+
} else if (src === "Native Method") frame.location = "Native Method";
|
|
1816
|
+
else frame.location = src;
|
|
1817
|
+
frames.push(frame);
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
const annoMatch = annotationRe.exec(line);
|
|
1821
|
+
if (annoMatch?.groups) {
|
|
1822
|
+
const kind = mapAnnotationKind(annoMatch.groups.kind);
|
|
1823
|
+
frames.push({
|
|
1824
|
+
functionName: annoMatch.groups.kind,
|
|
1825
|
+
displayName: annoMatch.groups.kind,
|
|
1826
|
+
kind,
|
|
1827
|
+
runtime: false,
|
|
1828
|
+
nativeMethod: false,
|
|
1829
|
+
annotationText: (annoMatch.groups.rest ?? "").trim()
|
|
1830
|
+
});
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
const userFrameCount = frames.filter((f) => f.kind === "frame" && !f.runtime).length;
|
|
1835
|
+
const topFunction = frames.find((f) => f.kind === "frame")?.functionName;
|
|
1836
|
+
const searchText = [header, ...frames.map((f) => `${f.functionName} ${f.location ?? ""} ${f.annotationText ?? ""}`)].join("\n").toLowerCase();
|
|
1837
|
+
return {
|
|
1838
|
+
id: idMatch ? Number(idMatch[1]) : deriveSyntheticId(name, rest),
|
|
1839
|
+
nid: nidMatch ? nidMatch[1] : void 0,
|
|
1840
|
+
name,
|
|
1841
|
+
state: state || "unknown",
|
|
1842
|
+
rawState: rawState || "",
|
|
1843
|
+
priority: prioMatch ? Number(prioMatch[1]) : void 0,
|
|
1844
|
+
daemon,
|
|
1845
|
+
frames,
|
|
1846
|
+
raw: block,
|
|
1847
|
+
userFrameCount,
|
|
1848
|
+
topFunction,
|
|
1849
|
+
searchText
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
function extractHeaderStateTrail(rest) {
|
|
1853
|
+
return rest.match(/nid=0x[0-9a-f]+\s+(?<desc>[^\[]+?)(?:\s+\[0x[0-9a-f]+\])?\s*$/i)?.groups?.desc?.trim();
|
|
1854
|
+
}
|
|
1855
|
+
function deriveSyntheticId(name, rest) {
|
|
1856
|
+
const tid = /\btid=(0x[0-9a-f]+)/i.exec(rest);
|
|
1857
|
+
if (tid) {
|
|
1858
|
+
const hex = tid[1].slice(2);
|
|
1859
|
+
const n = Number.parseInt(hex.slice(-8), 16);
|
|
1860
|
+
if (Number.isFinite(n)) return n;
|
|
1861
|
+
}
|
|
1862
|
+
let h = 0;
|
|
1863
|
+
for (let i = 0; i < name.length; i++) h = h * 31 + name.charCodeAt(i) | 0;
|
|
1864
|
+
return Math.abs(h);
|
|
1865
|
+
}
|
|
1866
|
+
function mapAnnotationKind(kind) {
|
|
1867
|
+
switch (kind) {
|
|
1868
|
+
case "locked": return "locked";
|
|
1869
|
+
case "waiting on": return "waiting_on";
|
|
1870
|
+
case "waiting to lock": return "waiting_to_lock";
|
|
1871
|
+
case "parking to wait for": return "parking";
|
|
1872
|
+
default: return "frame";
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function countThreadsByState(threads) {
|
|
1876
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1877
|
+
for (const t of threads) counts.set(t.state, (counts.get(t.state) ?? 0) + 1);
|
|
1878
|
+
return counts;
|
|
1879
|
+
}
|
|
1880
|
+
function normalizeJvmState(value) {
|
|
1881
|
+
if (!value) return "";
|
|
1882
|
+
const upper = value.trim().toUpperCase();
|
|
1883
|
+
if (upper.startsWith("RUNNABLE")) return "runnable";
|
|
1884
|
+
if (upper.startsWith("TIMED_WAITING")) return "timed_waiting";
|
|
1885
|
+
if (upper.startsWith("WAITING")) return "waiting";
|
|
1886
|
+
if (upper.startsWith("BLOCKED")) return "blocked";
|
|
1887
|
+
if (upper.startsWith("NEW")) return "new";
|
|
1888
|
+
if (upper.startsWith("TERMINATED")) return "terminated";
|
|
1889
|
+
const lower = value.trim().toLowerCase();
|
|
1890
|
+
if (lower.includes("runnable")) return "runnable";
|
|
1891
|
+
if (lower.includes("waiting on condition") || lower.includes("sleeping")) return "timed_waiting";
|
|
1892
|
+
if (lower.includes("waiting")) return "waiting";
|
|
1893
|
+
if (lower.includes("blocked")) return "blocked";
|
|
1894
|
+
return lower.split(/\s+/)[0];
|
|
1895
|
+
}
|
|
1896
|
+
const runtimePrefixes = [
|
|
1897
|
+
"java.",
|
|
1898
|
+
"javax.",
|
|
1899
|
+
"sun.",
|
|
1900
|
+
"jdk.",
|
|
1901
|
+
"com.sun.",
|
|
1902
|
+
"oracle.jrockit."
|
|
1903
|
+
];
|
|
1904
|
+
function isJvmRuntimeFrame(functionName) {
|
|
1905
|
+
return runtimePrefixes.some((p) => functionName.startsWith(p));
|
|
1906
|
+
}
|
|
1907
|
+
function sanitizeJvmFunctionName(functionName) {
|
|
1908
|
+
const parts = functionName.split(".");
|
|
1909
|
+
if (parts.length < 2) return functionName;
|
|
1910
|
+
const method = parts[parts.length - 1];
|
|
1911
|
+
return `${parts[parts.length - 2]}.${method}`;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
//#endregion
|
|
1915
|
+
//#region src/data/diagnostics/stacktrace.ts
|
|
1916
|
+
function detectDumpFormat(text) {
|
|
1917
|
+
const trimmed = text.trim();
|
|
1918
|
+
if (!trimmed) return "unknown";
|
|
1919
|
+
if (/^"[^"]*"[^\n]*\b(prio=|tid=|nid=)/m.test(trimmed)) return "jvm";
|
|
1920
|
+
if (/^goroutine\s+\d+\s+\[[^\]]+\]:/m.test(trimmed)) return "go";
|
|
1921
|
+
return "unknown";
|
|
1922
|
+
}
|
|
1923
|
+
function parseStackDump(text) {
|
|
1924
|
+
const format = detectDumpFormat(text);
|
|
1925
|
+
if (format === "jvm") return {
|
|
1926
|
+
format: "jvm",
|
|
1927
|
+
threads: parseJvmThreadDump(text)
|
|
1928
|
+
};
|
|
1929
|
+
if (format === "go") return {
|
|
1930
|
+
format: "go",
|
|
1931
|
+
goroutines: parseGoroutineDump(text)
|
|
1932
|
+
};
|
|
1933
|
+
return {
|
|
1934
|
+
format: "unknown",
|
|
1935
|
+
goroutines: [],
|
|
1936
|
+
threads: []
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function countStackByState(stack) {
|
|
1940
|
+
if (stack.format === "jvm") return countThreadsByState(stack.threads);
|
|
1941
|
+
if (stack.format === "go") return countGoroutinesByState(stack.goroutines);
|
|
1942
|
+
return /* @__PURE__ */ new Map();
|
|
1943
|
+
}
|
|
1944
|
+
const headerRe = /^goroutine\s+(\d+)\s+\[(.+?)\]:$/;
|
|
1945
|
+
const fileRe = /^\s*(.+?):(\d+)(?:\s+\+0x[0-9a-f]+)?$/i;
|
|
1946
|
+
const goSrcPrefixRe = /^\/usr\/local\/go[\d.]+\/src\//;
|
|
1947
|
+
const goWorkspacePrefixRe = /^.*?\/go\/src\//;
|
|
1948
|
+
const goPkgModPrefixRe = /^.*?\/go\/pkg\/mod\//;
|
|
1949
|
+
function parseGoroutineDump(text) {
|
|
1950
|
+
const trimmed = text.trim();
|
|
1951
|
+
if (!trimmed) return [];
|
|
1952
|
+
const blocks = trimmed.split(/\n\s*\n+/);
|
|
1953
|
+
const goroutines = [];
|
|
1954
|
+
for (const block of blocks) {
|
|
1955
|
+
const lines = block.split("\n").map((line) => line.replace(/\r$/, ""));
|
|
1956
|
+
const header = lines[0]?.trim();
|
|
1957
|
+
const match = headerRe.exec(header || "");
|
|
1958
|
+
if (!match) continue;
|
|
1959
|
+
const id = Number(match[1]);
|
|
1960
|
+
const rawState = match[2];
|
|
1961
|
+
const state = normalizeState(rawState);
|
|
1962
|
+
const frames = [];
|
|
1963
|
+
for (let i = 1; i < lines.length; i++) {
|
|
1964
|
+
const trimmedLine = lines[i].trim();
|
|
1965
|
+
if (!trimmedLine) continue;
|
|
1966
|
+
const fileMatch = fileRe.exec(trimmedLine);
|
|
1967
|
+
if (fileMatch && frames.length > 0 && !frames[frames.length - 1].file) {
|
|
1968
|
+
frames[frames.length - 1].file = fileMatch[1];
|
|
1969
|
+
frames[frames.length - 1].line = Number(fileMatch[2]);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
const kind = trimmedLine.startsWith("created by ") ? "created_by" : "frame";
|
|
1973
|
+
const functionName = kind === "created_by" ? trimmedLine.slice(11) : trimmedLine;
|
|
1974
|
+
frames.push({
|
|
1975
|
+
functionName,
|
|
1976
|
+
displayName: sanitizeFunctionName(functionName),
|
|
1977
|
+
kind,
|
|
1978
|
+
runtime: isRuntimeFrame(functionName)
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
for (const frame of frames) if (frame.file) {
|
|
1982
|
+
frame.file = normalizeFilePath(frame.file);
|
|
1983
|
+
frame.location = `${frame.file}${frame.line ? `:${frame.line}` : ""}`;
|
|
1984
|
+
}
|
|
1985
|
+
goroutines.push({
|
|
1986
|
+
id,
|
|
1987
|
+
state,
|
|
1988
|
+
rawState,
|
|
1989
|
+
frames,
|
|
1990
|
+
raw: block,
|
|
1991
|
+
userFrameCount: frames.filter((frame) => !frame.runtime && frame.kind === "frame").length,
|
|
1992
|
+
topFunction: frames.find((frame) => frame.kind === "frame")?.functionName,
|
|
1993
|
+
searchText: `${header}\n${frames.map((frame) => `${frame.functionName} ${frame.file || ""}`).join("\n")}`.toLowerCase()
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
return goroutines;
|
|
1997
|
+
}
|
|
1998
|
+
function countGoroutinesByState(goroutines) {
|
|
1999
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2000
|
+
for (const goroutine of goroutines) counts.set(goroutine.state, (counts.get(goroutine.state) || 0) + 1);
|
|
2001
|
+
return counts;
|
|
2002
|
+
}
|
|
2003
|
+
function normalizeState(value) {
|
|
2004
|
+
return value.split(",")[0].trim().toLowerCase();
|
|
2005
|
+
}
|
|
2006
|
+
function isRuntimeFrame(functionName) {
|
|
2007
|
+
return functionName.startsWith("runtime.") || functionName.startsWith("runtime/") || functionName.startsWith("internal/") || functionName.startsWith("runtime/internal/") || functionName.startsWith("syscall.") || functionName.startsWith("reflect.");
|
|
2008
|
+
}
|
|
2009
|
+
function sanitizeFunctionName(functionName) {
|
|
2010
|
+
let name = functionName.trim();
|
|
2011
|
+
const paren = name.indexOf("(");
|
|
2012
|
+
if (paren !== -1) name = name.slice(0, paren);
|
|
2013
|
+
name = name.replace(/\.\(\*([^)]+)\)\./g, ".$1.");
|
|
2014
|
+
return stripPackageQualifier(name);
|
|
2015
|
+
}
|
|
2016
|
+
function stripPackageQualifier(name) {
|
|
2017
|
+
return name.replace(/^((?:[^./\s]+\/)+)([^./\s]+)\./, "$2.");
|
|
2018
|
+
}
|
|
2019
|
+
function normalizeFilePath(path) {
|
|
2020
|
+
return path.replace(goSrcPrefixRe, "").replace(goWorkspacePrefixRe, "").replace(goPkgModPrefixRe, "");
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
//#endregion
|
|
2024
|
+
//#region src/data/diagnostics/GoroutineCard.tsx
|
|
2025
|
+
function goroutineStateBadge(state) {
|
|
2026
|
+
if (state.includes("running")) return "bg-green-50 text-green-700 dark:bg-green-500/20 dark:text-green-300";
|
|
2027
|
+
if (state.includes("chan") || state.includes("wait")) return "bg-blue-50 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300";
|
|
2028
|
+
if (state.includes("sleep")) return "bg-amber-50 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300";
|
|
2029
|
+
if (state.includes("select")) return "bg-violet-50 text-violet-700 dark:bg-violet-500/20 dark:text-violet-300";
|
|
2030
|
+
return "bg-muted text-muted-foreground";
|
|
2031
|
+
}
|
|
2032
|
+
function goroutineStateDot(state) {
|
|
2033
|
+
if (state.includes("running")) return "bg-green-500";
|
|
2034
|
+
if (state.includes("chan") || state.includes("wait")) return "bg-blue-500";
|
|
2035
|
+
if (state.includes("sleep")) return "bg-amber-500";
|
|
2036
|
+
if (state.includes("select")) return "bg-violet-500";
|
|
2037
|
+
return "bg-muted-foreground/40";
|
|
2038
|
+
}
|
|
2039
|
+
function GoroutineCard({ goroutine, search, hideRuntimeOnly }) {
|
|
2040
|
+
const frames = hideRuntimeOnly ? goroutine.frames.filter((frame) => !frame.runtime || frame.kind === "created_by") : goroutine.frames;
|
|
2041
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
2042
|
+
className: "border-0 bg-transparent",
|
|
2043
|
+
open: goroutine.state === "running" || !!search,
|
|
2044
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
2045
|
+
className: "cursor-pointer list-none px-0 py-1",
|
|
2046
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2047
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
2048
|
+
children: [
|
|
2049
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2050
|
+
className: "font-mono text-xs font-semibold text-foreground",
|
|
2051
|
+
children: ["g", goroutine.id]
|
|
2052
|
+
}),
|
|
2053
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2054
|
+
className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] ${goroutineStateBadge(goroutine.state)}`,
|
|
2055
|
+
children: [/* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${goroutineStateDot(goroutine.state)}` }), goroutine.rawState]
|
|
2056
|
+
}),
|
|
2057
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2058
|
+
className: "text-[11px] text-muted-foreground",
|
|
2059
|
+
children: [frames.length, "f"]
|
|
2060
|
+
}),
|
|
2061
|
+
goroutine.userFrameCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2062
|
+
className: "text-[11px] text-muted-foreground",
|
|
2063
|
+
children: [goroutine.userFrameCount, "u"]
|
|
2064
|
+
}),
|
|
2065
|
+
goroutine.topFunction && /* @__PURE__ */ jsx("span", {
|
|
2066
|
+
className: "truncate text-[11px] text-muted-foreground",
|
|
2067
|
+
children: goroutine.topFunction
|
|
2068
|
+
})
|
|
2069
|
+
]
|
|
2070
|
+
})
|
|
2071
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2072
|
+
className: "pl-3 py-1 space-y-0.5",
|
|
2073
|
+
children: frames.map((frame, index) => /* @__PURE__ */ jsx(FrameRow, { frame }, `${goroutine.id}-${index}`))
|
|
2074
|
+
})]
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
function FrameRow({ frame }) {
|
|
2078
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2079
|
+
className: frame.runtime ? "text-muted-foreground" : "text-foreground",
|
|
2080
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2081
|
+
className: "flex items-start gap-1.5",
|
|
2082
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2083
|
+
name: frame.kind === "created_by" ? "codicon:debug-restart" : frame.runtime ? "codicon:debug-step-over" : "codicon:symbol-method",
|
|
2084
|
+
className: "shrink-0 mt-0.5 text-[11px]"
|
|
2085
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2086
|
+
className: "min-w-0",
|
|
2087
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2088
|
+
className: "break-all font-mono text-[11px] font-semibold leading-4",
|
|
2089
|
+
children: [frame.displayName, frame.location && /* @__PURE__ */ jsx("span", {
|
|
2090
|
+
className: "ml-2 text-[10px] font-normal opacity-80",
|
|
2091
|
+
children: frame.location
|
|
2092
|
+
})]
|
|
2093
|
+
})
|
|
2094
|
+
})]
|
|
2095
|
+
})
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
//#endregion
|
|
2100
|
+
//#region src/data/diagnostics/ThreadCard.tsx
|
|
2101
|
+
function threadStateBadge(state) {
|
|
2102
|
+
switch (state) {
|
|
2103
|
+
case "runnable": return "bg-green-50 text-green-700 dark:bg-green-500/20 dark:text-green-300";
|
|
2104
|
+
case "blocked": return "bg-red-50 text-red-700 dark:bg-red-500/20 dark:text-red-300";
|
|
2105
|
+
case "waiting":
|
|
2106
|
+
case "timed_waiting": return "bg-blue-50 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300";
|
|
2107
|
+
case "new": return "bg-amber-50 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300";
|
|
2108
|
+
case "terminated": return "bg-muted text-muted-foreground";
|
|
2109
|
+
default: return "bg-muted text-muted-foreground";
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
function threadStateDot(state) {
|
|
2113
|
+
switch (state) {
|
|
2114
|
+
case "runnable": return "bg-green-500";
|
|
2115
|
+
case "blocked": return "bg-red-500";
|
|
2116
|
+
case "waiting":
|
|
2117
|
+
case "timed_waiting": return "bg-blue-500";
|
|
2118
|
+
case "new": return "bg-amber-500";
|
|
2119
|
+
case "terminated": return "bg-muted-foreground/40";
|
|
2120
|
+
default: return "bg-muted-foreground/40";
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
function ThreadCard({ thread, search, hideRuntimeOnly }) {
|
|
2124
|
+
const frames = hideRuntimeOnly ? thread.frames.filter((f) => f.kind !== "frame" || !f.runtime) : thread.frames;
|
|
2125
|
+
return /* @__PURE__ */ jsxs("details", {
|
|
2126
|
+
className: "border-0 bg-transparent",
|
|
2127
|
+
open: thread.state === "runnable" || thread.state === "blocked" || !!search,
|
|
2128
|
+
children: [/* @__PURE__ */ jsx("summary", {
|
|
2129
|
+
className: "cursor-pointer list-none px-0 py-1",
|
|
2130
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2131
|
+
className: "flex items-center gap-2 flex-wrap",
|
|
2132
|
+
children: [
|
|
2133
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2134
|
+
className: "font-mono text-xs font-semibold text-foreground",
|
|
2135
|
+
children: ["#", thread.id]
|
|
2136
|
+
}),
|
|
2137
|
+
/* @__PURE__ */ jsx("span", {
|
|
2138
|
+
className: "truncate font-mono text-xs text-foreground",
|
|
2139
|
+
children: thread.name
|
|
2140
|
+
}),
|
|
2141
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2142
|
+
className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] ${threadStateBadge(thread.state)}`,
|
|
2143
|
+
children: [/* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${threadStateDot(thread.state)}` }), thread.rawState || thread.state]
|
|
2144
|
+
}),
|
|
2145
|
+
thread.daemon && /* @__PURE__ */ jsx("span", {
|
|
2146
|
+
className: "rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
|
|
2147
|
+
children: "daemon"
|
|
2148
|
+
}),
|
|
2149
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2150
|
+
className: "text-[11px] text-muted-foreground",
|
|
2151
|
+
children: [frames.length, "f"]
|
|
2152
|
+
}),
|
|
2153
|
+
thread.userFrameCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2154
|
+
className: "text-[11px] text-muted-foreground",
|
|
2155
|
+
children: [thread.userFrameCount, "u"]
|
|
2156
|
+
}),
|
|
2157
|
+
thread.topFunction && /* @__PURE__ */ jsx("span", {
|
|
2158
|
+
className: "truncate text-[11px] text-muted-foreground",
|
|
2159
|
+
children: thread.topFunction
|
|
2160
|
+
})
|
|
2161
|
+
]
|
|
2162
|
+
})
|
|
2163
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2164
|
+
className: "pl-3 py-1 space-y-0.5",
|
|
2165
|
+
children: frames.map((frame, index) => /* @__PURE__ */ jsx(ThreadFrameRow, { frame }, `${thread.id}-${index}`))
|
|
2166
|
+
})]
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
function ThreadFrameRow({ frame }) {
|
|
2170
|
+
const isAnno = frame.kind !== "frame";
|
|
2171
|
+
const iconName = isAnno ? frame.kind === "locked" ? "codicon:lock" : frame.kind === "waiting_to_lock" ? "codicon:sync" : "codicon:watch" : frame.nativeMethod ? "codicon:chip" : frame.runtime ? "codicon:debug-step-over" : "codicon:symbol-method";
|
|
2172
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2173
|
+
className: frame.runtime ? "text-muted-foreground" : "text-foreground",
|
|
2174
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2175
|
+
className: "flex items-start gap-1.5",
|
|
2176
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2177
|
+
name: iconName,
|
|
2178
|
+
className: "shrink-0 mt-0.5 text-[11px]"
|
|
2179
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2180
|
+
className: "min-w-0",
|
|
2181
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2182
|
+
className: "break-all font-mono text-[11px] font-semibold leading-4",
|
|
2183
|
+
children: isAnno ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2184
|
+
className: "opacity-70",
|
|
2185
|
+
children: frame.functionName
|
|
2186
|
+
}), frame.annotationText && /* @__PURE__ */ jsx("span", {
|
|
2187
|
+
className: "ml-2 font-normal opacity-80",
|
|
2188
|
+
children: frame.annotationText
|
|
2189
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [frame.displayName, frame.location && /* @__PURE__ */ jsx("span", {
|
|
2190
|
+
className: "ml-2 text-[10px] font-normal opacity-80",
|
|
2191
|
+
children: frame.location
|
|
2192
|
+
})] })
|
|
2193
|
+
})
|
|
2194
|
+
})]
|
|
2195
|
+
})
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
//#endregion
|
|
2200
|
+
//#region src/data/diagnostics/DiagnosticsDetailPanel.tsx
|
|
2201
|
+
const STACK_MIN_HEIGHT = `10rem`;
|
|
2202
|
+
function cpuTone(pct) {
|
|
2203
|
+
if (pct >= 90) return "danger";
|
|
2204
|
+
if (pct >= 60) return "warning";
|
|
2205
|
+
if (pct > 0) return "success";
|
|
2206
|
+
return "neutral";
|
|
2207
|
+
}
|
|
2208
|
+
function memoryTone(rss, vms) {
|
|
2209
|
+
if (!rss || !vms) return "neutral";
|
|
2210
|
+
const ratio = rss / vms;
|
|
2211
|
+
if (ratio >= .9) return "danger";
|
|
2212
|
+
if (ratio >= .6) return "warning";
|
|
2213
|
+
return "info";
|
|
2214
|
+
}
|
|
2215
|
+
function DiagnosticsDetailPanel({ process, collectBusy, onCollectStack, runMeta }) {
|
|
2216
|
+
const [search, setSearch] = useState("");
|
|
2217
|
+
const [selectedStates, setSelectedStates] = useState(/* @__PURE__ */ new Set());
|
|
2218
|
+
const [hideRuntimeOnly, setHideRuntimeOnly] = useState(true);
|
|
2219
|
+
const stack = process?.stack_capture;
|
|
2220
|
+
const parsed = useMemo(() => parseStackDump(stack?.text || ""), [stack?.text]);
|
|
2221
|
+
const stateCounts = useMemo(() => countStackByState(parsed), [parsed]);
|
|
2222
|
+
const filtered = useMemo(() => {
|
|
2223
|
+
const needle = search.trim().toLowerCase();
|
|
2224
|
+
return (parsed.format === "jvm" ? parsed.threads : parsed.format === "go" ? parsed.goroutines : []).filter((item) => {
|
|
2225
|
+
if (selectedStates.size > 0 && !selectedStates.has(item.state)) return false;
|
|
2226
|
+
if (hideRuntimeOnly && item.userFrameCount === 0) return false;
|
|
2227
|
+
if (needle && !item.searchText.includes(needle)) return false;
|
|
2228
|
+
return true;
|
|
2229
|
+
});
|
|
2230
|
+
}, [
|
|
2231
|
+
parsed,
|
|
2232
|
+
search,
|
|
2233
|
+
selectedStates,
|
|
2234
|
+
hideRuntimeOnly
|
|
2235
|
+
]);
|
|
2236
|
+
useEffect(() => {
|
|
2237
|
+
setSearch("");
|
|
2238
|
+
setSelectedStates(/* @__PURE__ */ new Set());
|
|
2239
|
+
setHideRuntimeOnly(true);
|
|
2240
|
+
}, [stack?.text, process?.pid]);
|
|
2241
|
+
if (!process) return /* @__PURE__ */ jsx("div", {
|
|
2242
|
+
className: "flex items-center justify-center h-full text-muted-foreground text-sm",
|
|
2243
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2244
|
+
className: "text-center",
|
|
2245
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2246
|
+
name: "codicon:server-process",
|
|
2247
|
+
className: "text-4xl mb-density-2"
|
|
2248
|
+
}), /* @__PURE__ */ jsx("div", { children: "Select a process to view diagnostics" })]
|
|
2249
|
+
})
|
|
2250
|
+
});
|
|
2251
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2252
|
+
className: "h-full min-h-0 flex flex-col gap-density-3 p-density-4",
|
|
2253
|
+
children: [
|
|
2254
|
+
/* @__PURE__ */ jsx(Header, { process }),
|
|
2255
|
+
runMeta && /* @__PURE__ */ jsx(RunSection, { runMeta }),
|
|
2256
|
+
process.command && /* @__PURE__ */ jsx(PanelSection, {
|
|
2257
|
+
title: "Command",
|
|
2258
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
2259
|
+
className: "text-xs text-foreground whitespace-pre-wrap font-mono bg-blue-50 dark:bg-blue-500/10 rounded p-2 break-all",
|
|
2260
|
+
children: process.command
|
|
2261
|
+
})
|
|
2262
|
+
}),
|
|
2263
|
+
/* @__PURE__ */ jsx(PanelSection, {
|
|
2264
|
+
title: "Metrics",
|
|
2265
|
+
children: /* @__PURE__ */ jsx(ProcessMetrics, { process })
|
|
2266
|
+
}),
|
|
2267
|
+
/* @__PURE__ */ jsx(PanelSection, {
|
|
2268
|
+
title: "Stack",
|
|
2269
|
+
grow: true,
|
|
2270
|
+
children: /* @__PURE__ */ jsx(StackBlock, {
|
|
2271
|
+
process,
|
|
2272
|
+
parsed,
|
|
2273
|
+
stateCounts,
|
|
2274
|
+
filtered,
|
|
2275
|
+
search,
|
|
2276
|
+
setSearch,
|
|
2277
|
+
selectedStates,
|
|
2278
|
+
setSelectedStates,
|
|
2279
|
+
hideRuntimeOnly,
|
|
2280
|
+
setHideRuntimeOnly,
|
|
2281
|
+
collectBusy,
|
|
2282
|
+
onCollectStack
|
|
2283
|
+
})
|
|
2284
|
+
})
|
|
2285
|
+
]
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
function Header({ process }) {
|
|
2289
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2290
|
+
className: "flex items-start justify-between gap-density-3",
|
|
2291
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2292
|
+
className: "min-w-0 flex-1",
|
|
2293
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2294
|
+
className: "flex items-center gap-density-2",
|
|
2295
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2296
|
+
name: process.is_root ? "codicon:server-process" : "codicon:debug-alt",
|
|
2297
|
+
className: "text-2xl text-blue-600"
|
|
2298
|
+
}), /* @__PURE__ */ jsx("h2", {
|
|
2299
|
+
className: "text-lg font-bold text-foreground break-words",
|
|
2300
|
+
children: processLabel(process)
|
|
2301
|
+
})]
|
|
2302
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2303
|
+
className: "mt-1 flex items-center gap-density-2 flex-wrap text-xs text-muted-foreground",
|
|
2304
|
+
children: [
|
|
2305
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2306
|
+
className: "font-mono",
|
|
2307
|
+
children: ["pid ", process.pid]
|
|
2308
|
+
}),
|
|
2309
|
+
process.ppid ? /* @__PURE__ */ jsxs("span", {
|
|
2310
|
+
className: "font-mono",
|
|
2311
|
+
children: ["ppid ", process.ppid]
|
|
2312
|
+
}) : null,
|
|
2313
|
+
process.status ? /* @__PURE__ */ jsxs("span", {
|
|
2314
|
+
className: `inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 ${processStateColor(process.status)}`,
|
|
2315
|
+
children: [/* @__PURE__ */ jsx(Icon, { name: processStateIcon(process.status) }), process.status]
|
|
2316
|
+
}) : null
|
|
2317
|
+
]
|
|
2318
|
+
})]
|
|
2319
|
+
})
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
function RunSection({ runMeta }) {
|
|
2323
|
+
return /* @__PURE__ */ jsx(PanelSection, {
|
|
2324
|
+
title: "Run",
|
|
2325
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2326
|
+
className: "grid grid-cols-2 gap-density-2 text-sm",
|
|
2327
|
+
children: [/* @__PURE__ */ jsx(InfoTile, {
|
|
2328
|
+
label: runMeta.kind === "rerun" ? `Rerun #${runMeta.sequence}` : "Initial run",
|
|
2329
|
+
value: runMeta.started ? new Date(runMeta.started).toLocaleString() : "Unavailable"
|
|
2330
|
+
}), /* @__PURE__ */ jsx(InfoTile, {
|
|
2331
|
+
label: "Finished",
|
|
2332
|
+
value: runMeta.ended ? new Date(runMeta.ended).toLocaleString() : "In progress"
|
|
2333
|
+
})]
|
|
2334
|
+
})
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
function stackItemCount(parsed) {
|
|
2338
|
+
if (parsed.format === "jvm") return parsed.threads.length;
|
|
2339
|
+
if (parsed.format === "go") return parsed.goroutines.length;
|
|
2340
|
+
return 0;
|
|
2341
|
+
}
|
|
2342
|
+
function stackItemLabel(parsed) {
|
|
2343
|
+
if (parsed.format === "jvm") return "threads";
|
|
2344
|
+
if (parsed.format === "go") return "goroutines";
|
|
2345
|
+
return "frames";
|
|
2346
|
+
}
|
|
2347
|
+
function stackStateDot(parsed, state) {
|
|
2348
|
+
return parsed.format === "jvm" ? threadStateDot(state) : goroutineStateDot(state);
|
|
2349
|
+
}
|
|
2350
|
+
function StackBlock(props) {
|
|
2351
|
+
const { process, parsed, stateCounts, filtered, search, setSearch, selectedStates, setSelectedStates, hideRuntimeOnly, setHideRuntimeOnly, collectBusy, onCollectStack } = props;
|
|
2352
|
+
const stack = process.stack_capture;
|
|
2353
|
+
if (!stack?.text) return /* @__PURE__ */ jsxs("div", {
|
|
2354
|
+
className: "h-full min-h-[14rem] flex flex-col justify-center gap-density-3 p-density-3",
|
|
2355
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2356
|
+
className: "space-y-1.5",
|
|
2357
|
+
children: [
|
|
2358
|
+
/* @__PURE__ */ jsx("div", { className: `h-3 w-40 rounded ${collectBusy ? "animate-pulse bg-muted" : "bg-muted/70"}` }),
|
|
2359
|
+
/* @__PURE__ */ jsx("div", { className: `h-3 w-full rounded ${collectBusy ? "animate-pulse bg-muted" : "bg-muted/70"}` }),
|
|
2360
|
+
/* @__PURE__ */ jsx("div", { className: `h-3 w-5/6 rounded ${collectBusy ? "animate-pulse bg-muted" : "bg-muted/70"}` })
|
|
2361
|
+
]
|
|
2362
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2363
|
+
className: "flex items-center justify-between gap-density-3",
|
|
2364
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2365
|
+
className: "text-sm text-muted-foreground",
|
|
2366
|
+
children: stack?.error ? stack.error : collectBusy ? "Collecting stack trace..." : "No stack trace collected yet."
|
|
2367
|
+
}), onCollectStack && /* @__PURE__ */ jsx(CollectButton, {
|
|
2368
|
+
pid: process.pid,
|
|
2369
|
+
busy: collectBusy,
|
|
2370
|
+
onClick: onCollectStack,
|
|
2371
|
+
primary: true
|
|
2372
|
+
})]
|
|
2373
|
+
})]
|
|
2374
|
+
});
|
|
2375
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2376
|
+
className: "h-full min-h-0 flex flex-col",
|
|
2377
|
+
children: [
|
|
2378
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2379
|
+
className: "px-1 py-1.5 space-y-density-2",
|
|
2380
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2381
|
+
className: "flex items-center justify-between gap-density-2 text-[11px] text-muted-foreground flex-wrap",
|
|
2382
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2383
|
+
className: "flex items-center gap-density-2 flex-wrap",
|
|
2384
|
+
children: [
|
|
2385
|
+
/* @__PURE__ */ jsx(StackStatusBadge, { status: stack.status }),
|
|
2386
|
+
stack.collected_at && /* @__PURE__ */ jsx("span", { children: new Date(stack.collected_at).toLocaleString() }),
|
|
2387
|
+
stackItemCount(parsed) > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
2388
|
+
filtered.length,
|
|
2389
|
+
" / ",
|
|
2390
|
+
stackItemCount(parsed),
|
|
2391
|
+
" ",
|
|
2392
|
+
stackItemLabel(parsed)
|
|
2393
|
+
] })
|
|
2394
|
+
]
|
|
2395
|
+
}), onCollectStack && /* @__PURE__ */ jsx(CollectButton, {
|
|
2396
|
+
pid: process.pid,
|
|
2397
|
+
busy: collectBusy,
|
|
2398
|
+
onClick: onCollectStack
|
|
2399
|
+
})]
|
|
2400
|
+
}), stackItemCount(parsed) > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2401
|
+
className: "flex items-center gap-1.5 flex-wrap",
|
|
2402
|
+
children: [
|
|
2403
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2404
|
+
className: "relative min-w-[14rem] flex-1",
|
|
2405
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
2406
|
+
name: "codicon:search",
|
|
2407
|
+
className: "absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground text-xs"
|
|
2408
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
2409
|
+
className: "w-full rounded-md border border-border bg-muted/50 py-1 pl-7 pr-2 text-xs outline-none focus:border-primary focus:bg-background",
|
|
2410
|
+
placeholder: parsed.format === "jvm" ? "Filter by thread name, function, or file" : "Filter by goroutine id, function, or file",
|
|
2411
|
+
value: search,
|
|
2412
|
+
onChange: (e) => setSearch(e.target.value)
|
|
2413
|
+
})]
|
|
2414
|
+
}),
|
|
2415
|
+
/* @__PURE__ */ jsxs("label", {
|
|
2416
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border bg-muted/50 px-2 py-1 text-[11px] text-muted-foreground",
|
|
2417
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2418
|
+
type: "checkbox",
|
|
2419
|
+
checked: hideRuntimeOnly,
|
|
2420
|
+
onChange: (e) => setHideRuntimeOnly(e.target.checked)
|
|
2421
|
+
}), "Hide runtime-only"]
|
|
2422
|
+
}),
|
|
2423
|
+
(search || selectedStates.size > 0 || !hideRuntimeOnly) && /* @__PURE__ */ jsx("button", {
|
|
2424
|
+
className: "text-[11px] text-muted-foreground hover:text-foreground",
|
|
2425
|
+
onClick: () => {
|
|
2426
|
+
setSearch("");
|
|
2427
|
+
setSelectedStates(() => /* @__PURE__ */ new Set());
|
|
2428
|
+
setHideRuntimeOnly(true);
|
|
2429
|
+
},
|
|
2430
|
+
children: "Clear"
|
|
2431
|
+
})
|
|
2432
|
+
]
|
|
2433
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2434
|
+
className: "flex items-center gap-1 flex-wrap",
|
|
2435
|
+
children: Array.from(stateCounts.entries()).sort((a, b) => b[1] - a[1]).map(([state, count]) => {
|
|
2436
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2437
|
+
className: `inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] transition-colors ${selectedStates.has(state) ? "border-primary bg-primary/10 text-primary" : "border-border bg-muted/50 text-muted-foreground hover:bg-background"}`,
|
|
2438
|
+
onClick: () => {
|
|
2439
|
+
setSelectedStates((prev) => {
|
|
2440
|
+
const next = new Set(prev);
|
|
2441
|
+
if (next.has(state)) next.delete(state);
|
|
2442
|
+
else next.add(state);
|
|
2443
|
+
return next;
|
|
2444
|
+
});
|
|
2445
|
+
},
|
|
2446
|
+
children: [
|
|
2447
|
+
/* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${stackStateDot(parsed, state)}` }),
|
|
2448
|
+
state,
|
|
2449
|
+
/* @__PURE__ */ jsx("span", {
|
|
2450
|
+
className: "text-[10px] opacity-70",
|
|
2451
|
+
children: count
|
|
2452
|
+
})
|
|
2453
|
+
]
|
|
2454
|
+
}, state);
|
|
2455
|
+
})
|
|
2456
|
+
})] })]
|
|
2457
|
+
}),
|
|
2458
|
+
stack.error && /* @__PURE__ */ jsx("div", {
|
|
2459
|
+
className: "mt-density-2 text-xs text-red-600 whitespace-pre-wrap",
|
|
2460
|
+
children: stack.error
|
|
2461
|
+
}),
|
|
2462
|
+
stackItemCount(parsed) === 0 ? /* @__PURE__ */ jsx("pre", {
|
|
2463
|
+
className: "flex-1 min-h-0 overflow-auto py-1 text-[11px] text-foreground whitespace-pre-wrap font-mono leading-4",
|
|
2464
|
+
style: { minHeight: STACK_MIN_HEIGHT },
|
|
2465
|
+
children: stack.text
|
|
2466
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
2467
|
+
className: "flex-1 min-h-0 overflow-auto py-1 space-y-1",
|
|
2468
|
+
style: { minHeight: STACK_MIN_HEIGHT },
|
|
2469
|
+
children: [
|
|
2470
|
+
filtered.length === 0 && /* @__PURE__ */ jsxs("div", {
|
|
2471
|
+
className: "py-density-3 text-center text-xs text-muted-foreground",
|
|
2472
|
+
children: [
|
|
2473
|
+
"No ",
|
|
2474
|
+
stackItemLabel(parsed),
|
|
2475
|
+
" match the current filters."
|
|
2476
|
+
]
|
|
2477
|
+
}),
|
|
2478
|
+
parsed.format === "jvm" && parsed.threads.filter((t) => filtered.includes(t)).map((t) => /* @__PURE__ */ jsx(ThreadCard, {
|
|
2479
|
+
thread: t,
|
|
2480
|
+
search,
|
|
2481
|
+
hideRuntimeOnly
|
|
2482
|
+
}, t.id)),
|
|
2483
|
+
parsed.format === "go" && parsed.goroutines.filter((g) => filtered.includes(g)).map((goroutine) => /* @__PURE__ */ jsx(GoroutineCard, {
|
|
2484
|
+
goroutine,
|
|
2485
|
+
search,
|
|
2486
|
+
hideRuntimeOnly
|
|
2487
|
+
}, goroutine.id))
|
|
2488
|
+
]
|
|
2489
|
+
})
|
|
2490
|
+
]
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
function StackStatusBadge({ status }) {
|
|
2494
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2495
|
+
className: `px-2 py-0.5 rounded-full ${status === "ready" ? "bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300" : status === "unsupported" ? "bg-yellow-100 text-yellow-700 dark:bg-yellow-500/20 dark:text-yellow-300" : "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300"}`,
|
|
2496
|
+
children: status
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
function CollectButton({ pid, busy, onClick, primary }) {
|
|
2500
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
2501
|
+
className: `shrink-0 text-[11px] px-2 py-1 rounded disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1 ${primary ? "bg-primary text-primary-foreground hover:bg-primary/90" : "border border-border text-muted-foreground hover:bg-accent"}`,
|
|
2502
|
+
onClick: () => onClick(pid),
|
|
2503
|
+
disabled: busy,
|
|
2504
|
+
title: primary ? "Collect the latest stack trace" : "Refresh stack trace",
|
|
2505
|
+
children: [/* @__PURE__ */ jsx(Icon, { name: busy ? "svg-spinners:ring-resize" : primary ? "codicon:debug-alt-small" : "codicon:refresh" }), busy ? "Collecting..." : primary ? "Collect stack trace" : "Refresh"]
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
function PanelSection({ title, grow, children }) {
|
|
2509
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
2510
|
+
className: grow ? "flex min-h-0 flex-1 flex-col" : "",
|
|
2511
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2512
|
+
className: "text-[11px] font-semibold uppercase tracking-wide text-muted-foreground mb-1.5",
|
|
2513
|
+
children: title
|
|
2514
|
+
}), grow ? /* @__PURE__ */ jsx("div", {
|
|
2515
|
+
className: "flex-1 min-h-0 overflow-hidden",
|
|
2516
|
+
children
|
|
2517
|
+
}) : children]
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
function InfoTile({ label, value }) {
|
|
2521
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2522
|
+
className: "border border-border rounded-lg bg-muted/50 px-2.5 py-density-2",
|
|
2523
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2524
|
+
className: "text-[10px] uppercase tracking-wide text-muted-foreground",
|
|
2525
|
+
children: label
|
|
2526
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2527
|
+
className: "text-xs font-medium text-foreground mt-0.5",
|
|
2528
|
+
children: value
|
|
2529
|
+
})]
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
function ProcessMetrics({ process }) {
|
|
2533
|
+
const cpu = process.cpu_percent || 0;
|
|
2534
|
+
const rss = process.rss;
|
|
2535
|
+
const vms = process.vms;
|
|
2536
|
+
const memRatioPct = rss && vms && vms > 0 ? Math.min(100, Math.round(rss / vms * 100)) : 0;
|
|
2537
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2538
|
+
className: "grid grid-cols-4 gap-density-1",
|
|
2539
|
+
children: [
|
|
2540
|
+
/* @__PURE__ */ jsx(MetricTile, {
|
|
2541
|
+
label: "CPU",
|
|
2542
|
+
value: `${cpu.toFixed(1)}%`,
|
|
2543
|
+
barPct: Math.min(100, cpu),
|
|
2544
|
+
tone: cpuTone(cpu)
|
|
2545
|
+
}),
|
|
2546
|
+
/* @__PURE__ */ jsx(MetricTile, {
|
|
2547
|
+
label: "RSS",
|
|
2548
|
+
value: rss !== void 0 ? formatBytes$1(rss) : "n/a",
|
|
2549
|
+
barPct: memRatioPct,
|
|
2550
|
+
tone: memoryTone(rss, vms)
|
|
2551
|
+
}),
|
|
2552
|
+
/* @__PURE__ */ jsx(MetricTile, {
|
|
2553
|
+
label: "VMS",
|
|
2554
|
+
value: vms !== void 0 ? formatBytes$1(vms) : "n/a",
|
|
2555
|
+
tone: "neutral"
|
|
2556
|
+
}),
|
|
2557
|
+
/* @__PURE__ */ jsx(MetricTile, {
|
|
2558
|
+
label: "Files",
|
|
2559
|
+
value: process.open_files !== void 0 ? String(process.open_files) : "n/a",
|
|
2560
|
+
barPct: process.open_files ? Math.min(100, process.open_files / 1024 * 100) : 0,
|
|
2561
|
+
tone: "info"
|
|
2562
|
+
})
|
|
2563
|
+
]
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
function MetricTile({ label, value, barPct, tone = "neutral" }) {
|
|
2567
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2568
|
+
className: "flex flex-col gap-0.5 rounded-md border border-border bg-background px-density-2 py-1 min-w-0",
|
|
2569
|
+
children: [
|
|
2570
|
+
/* @__PURE__ */ jsx("span", {
|
|
2571
|
+
className: "text-[9px] uppercase tracking-wide text-muted-foreground leading-none",
|
|
2572
|
+
children: label
|
|
2573
|
+
}),
|
|
2574
|
+
/* @__PURE__ */ jsx("span", {
|
|
2575
|
+
className: `text-xs font-semibold tabular-nums truncate ${{
|
|
2576
|
+
neutral: "text-foreground",
|
|
2577
|
+
success: "text-green-600 dark:text-green-400",
|
|
2578
|
+
warning: "text-yellow-600 dark:text-yellow-400",
|
|
2579
|
+
danger: "text-red-600 dark:text-red-400",
|
|
2580
|
+
info: "text-blue-600 dark:text-blue-400"
|
|
2581
|
+
}[tone]}`,
|
|
2582
|
+
children: value
|
|
2583
|
+
}),
|
|
2584
|
+
barPct !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
2585
|
+
className: "h-0.5 w-full rounded-full bg-muted overflow-hidden",
|
|
2586
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2587
|
+
className: `h-full ${{
|
|
2588
|
+
neutral: "bg-muted-foreground/40",
|
|
2589
|
+
success: "bg-green-500",
|
|
2590
|
+
warning: "bg-yellow-500",
|
|
2591
|
+
danger: "bg-red-500",
|
|
2592
|
+
info: "bg-blue-500"
|
|
2593
|
+
}[tone]} transition-all duration-300`,
|
|
2594
|
+
style: { width: `${barPct}%` }
|
|
2595
|
+
})
|
|
2596
|
+
})
|
|
2597
|
+
]
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
//#endregion
|
|
2602
|
+
//#region src/data/har/HarPanel.tsx
|
|
2603
|
+
const COLS = [
|
|
2604
|
+
{
|
|
2605
|
+
key: "request.method",
|
|
2606
|
+
label: "Method",
|
|
2607
|
+
cls: "px-2 py-2 w-16"
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
key: "request.url",
|
|
2611
|
+
label: "URL",
|
|
2612
|
+
cls: "px-2 py-2"
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
key: "response.status",
|
|
2616
|
+
label: "Status",
|
|
2617
|
+
cls: "px-2 py-2 w-20"
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
key: "time",
|
|
2621
|
+
label: "Time",
|
|
2622
|
+
cls: "px-2 py-2 w-16 text-right",
|
|
2623
|
+
align: "right"
|
|
2624
|
+
},
|
|
2625
|
+
{
|
|
2626
|
+
key: "response.bodySize",
|
|
2627
|
+
label: "Size",
|
|
2628
|
+
cls: "px-2 py-2 w-16 text-right",
|
|
2629
|
+
align: "right"
|
|
2630
|
+
},
|
|
2631
|
+
{
|
|
2632
|
+
key: "response.content.mimeType",
|
|
2633
|
+
label: "Type",
|
|
2634
|
+
cls: "px-2 py-2 w-40"
|
|
2635
|
+
}
|
|
2636
|
+
];
|
|
2637
|
+
function HarPanel({ entries, search, emptyLabel = "No HTTP traffic captured", className }) {
|
|
2638
|
+
const { sorted, sort, toggle } = useSort(useMemo(() => {
|
|
2639
|
+
if (!search) return entries;
|
|
2640
|
+
const needle = search.toLowerCase();
|
|
2641
|
+
return entries.filter((e) => matchesSearch(needle, e));
|
|
2642
|
+
}, [entries, search]), { defaultKey: "time" });
|
|
2643
|
+
if (!entries || entries.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2644
|
+
className: "p-density-6 text-center text-muted-foreground text-sm",
|
|
2645
|
+
children: emptyLabel
|
|
2646
|
+
});
|
|
2647
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2648
|
+
className: `overflow-auto h-full ${className ?? ""}`,
|
|
2649
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
2650
|
+
className: "w-full text-left table-fixed",
|
|
2651
|
+
children: [/* @__PURE__ */ jsx("thead", {
|
|
2652
|
+
className: "bg-muted/50 sticky top-0",
|
|
2653
|
+
children: /* @__PURE__ */ jsx("tr", {
|
|
2654
|
+
className: "text-xs text-muted-foreground border-b border-border",
|
|
2655
|
+
children: COLS.map((c) => /* @__PURE__ */ jsx("th", {
|
|
2656
|
+
className: `${c.cls} cursor-pointer select-none whitespace-nowrap font-medium`,
|
|
2657
|
+
children: /* @__PURE__ */ jsx(SortableHeader, {
|
|
2658
|
+
active: sort?.key === c.key,
|
|
2659
|
+
dir: sort?.dir,
|
|
2660
|
+
onClick: () => toggle(c.key),
|
|
2661
|
+
align: c.align,
|
|
2662
|
+
children: c.label
|
|
2663
|
+
})
|
|
2664
|
+
}, c.key))
|
|
2665
|
+
})
|
|
2666
|
+
}), /* @__PURE__ */ jsx("tbody", { children: sorted.map((entry, i) => /* @__PURE__ */ jsx(HarRow, { entry }, i)) })]
|
|
2667
|
+
})
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
function HarRow({ entry }) {
|
|
2671
|
+
const [open, setOpen] = useState(false);
|
|
2672
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("tr", {
|
|
2673
|
+
className: "hover:bg-accent cursor-pointer text-xs border-b border-border",
|
|
2674
|
+
onClick: () => setOpen(!open),
|
|
2675
|
+
children: [
|
|
2676
|
+
/* @__PURE__ */ jsx("td", {
|
|
2677
|
+
className: "px-2 py-1.5 font-mono font-medium whitespace-nowrap",
|
|
2678
|
+
children: entry.request.method
|
|
2679
|
+
}),
|
|
2680
|
+
/* @__PURE__ */ jsx("td", {
|
|
2681
|
+
className: "px-2 py-1.5 font-mono truncate max-w-0",
|
|
2682
|
+
title: entry.request.url,
|
|
2683
|
+
children: entry.request.url
|
|
2684
|
+
}),
|
|
2685
|
+
/* @__PURE__ */ jsx("td", {
|
|
2686
|
+
className: `px-2 py-1.5 font-medium whitespace-nowrap ${statusColor(entry.response.status)}`,
|
|
2687
|
+
children: entry.response.status
|
|
2688
|
+
}),
|
|
2689
|
+
/* @__PURE__ */ jsxs("td", {
|
|
2690
|
+
className: "px-2 py-1.5 text-right text-muted-foreground whitespace-nowrap tabular-nums",
|
|
2691
|
+
children: [entry.time.toFixed(0), "ms"]
|
|
2692
|
+
}),
|
|
2693
|
+
/* @__PURE__ */ jsx("td", {
|
|
2694
|
+
className: "px-2 py-1.5 text-right text-muted-foreground whitespace-nowrap tabular-nums",
|
|
2695
|
+
children: formatBytes(entry.response.bodySize)
|
|
2696
|
+
}),
|
|
2697
|
+
/* @__PURE__ */ jsx("td", {
|
|
2698
|
+
className: "px-2 py-1.5 text-muted-foreground whitespace-nowrap",
|
|
2699
|
+
children: entry.response.content?.mimeType || ""
|
|
2700
|
+
})
|
|
2701
|
+
]
|
|
2702
|
+
}), open && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
2703
|
+
colSpan: COLS.length,
|
|
2704
|
+
className: "bg-muted/50 p-density-3 text-xs",
|
|
2705
|
+
children: /* @__PURE__ */ jsx(HarRowDetails, { entry })
|
|
2706
|
+
}) })] });
|
|
2707
|
+
}
|
|
2708
|
+
function HarRowDetails({ entry }) {
|
|
2709
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
2710
|
+
className: "grid grid-cols-2 gap-density-4",
|
|
2711
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(HeaderList, {
|
|
2712
|
+
title: "Request Headers",
|
|
2713
|
+
headers: entry.request.headers
|
|
2714
|
+
}), entry.request.postData?.text && /* @__PURE__ */ jsxs("div", {
|
|
2715
|
+
className: "mt-density-2",
|
|
2716
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2717
|
+
className: "font-semibold text-foreground mb-1",
|
|
2718
|
+
children: "Request Body"
|
|
2719
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2720
|
+
className: "bg-background p-density-2 rounded border border-border overflow-auto max-h-48",
|
|
2721
|
+
children: /* @__PURE__ */ jsx(BodyView, {
|
|
2722
|
+
text: entry.request.postData.text,
|
|
2723
|
+
mimeType: entry.request.postData.mimeType
|
|
2724
|
+
})
|
|
2725
|
+
})]
|
|
2726
|
+
})] }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(HeaderList, {
|
|
2727
|
+
title: "Response Headers",
|
|
2728
|
+
headers: entry.response.headers
|
|
2729
|
+
}) })]
|
|
2730
|
+
}), entry.response.content?.text && /* @__PURE__ */ jsxs("div", {
|
|
2731
|
+
className: "mt-density-3",
|
|
2732
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2733
|
+
className: "font-semibold text-foreground mb-1",
|
|
2734
|
+
children: "Response Body"
|
|
2735
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2736
|
+
className: "bg-background p-density-2 rounded border border-border overflow-auto max-h-64",
|
|
2737
|
+
children: /* @__PURE__ */ jsx(BodyView, {
|
|
2738
|
+
text: entry.response.content.text,
|
|
2739
|
+
mimeType: entry.response.content.mimeType
|
|
2740
|
+
})
|
|
2741
|
+
})]
|
|
2742
|
+
})] });
|
|
2743
|
+
}
|
|
2744
|
+
function HeaderList({ title, headers }) {
|
|
2745
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
2746
|
+
className: "font-semibold text-foreground mb-1",
|
|
2747
|
+
children: title
|
|
2748
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2749
|
+
className: "space-y-0.5",
|
|
2750
|
+
children: headers?.map((h, i) => /* @__PURE__ */ jsxs("div", {
|
|
2751
|
+
className: "whitespace-nowrap",
|
|
2752
|
+
children: [
|
|
2753
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2754
|
+
className: "text-purple-600 dark:text-purple-400",
|
|
2755
|
+
children: [h.name, ":"]
|
|
2756
|
+
}),
|
|
2757
|
+
" ",
|
|
2758
|
+
h.value
|
|
2759
|
+
]
|
|
2760
|
+
}, i))
|
|
2761
|
+
})] });
|
|
2762
|
+
}
|
|
2763
|
+
function BodyView({ text, mimeType }) {
|
|
2764
|
+
if (isJsonType(mimeType)) {
|
|
2765
|
+
const parsed = tryParseJson(text);
|
|
2766
|
+
if (parsed !== null) return /* @__PURE__ */ jsx(JsonView, { data: parsed });
|
|
2767
|
+
}
|
|
2768
|
+
return /* @__PURE__ */ jsx("pre", {
|
|
2769
|
+
className: "whitespace-pre-wrap break-all",
|
|
2770
|
+
children: text
|
|
2771
|
+
});
|
|
2772
|
+
}
|
|
2773
|
+
function tryParseJson(text) {
|
|
2774
|
+
try {
|
|
2775
|
+
return JSON.parse(text);
|
|
2776
|
+
} catch {
|
|
2777
|
+
return null;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
function isJsonType(mime) {
|
|
2781
|
+
return !!mime && (mime.includes("json") || mime.includes("javascript"));
|
|
2782
|
+
}
|
|
2783
|
+
function matchesSearch(needle, e) {
|
|
2784
|
+
return [
|
|
2785
|
+
e.request.url,
|
|
2786
|
+
e.request.method,
|
|
2787
|
+
e.request.postData?.text,
|
|
2788
|
+
e.response.content?.text
|
|
2789
|
+
].some((h) => !!h && h.toLowerCase().includes(needle));
|
|
2790
|
+
}
|
|
2791
|
+
function formatBytes(bytes) {
|
|
2792
|
+
if (bytes < 0) return "";
|
|
2793
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
2794
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
2795
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
2796
|
+
}
|
|
2797
|
+
function statusColor(status) {
|
|
2798
|
+
if (status >= 500) return "text-red-600";
|
|
2799
|
+
if (status >= 400) return "text-amber-600";
|
|
2800
|
+
if (status >= 300) return "text-blue-600";
|
|
2801
|
+
if (status >= 200) return "text-green-600";
|
|
2802
|
+
return "text-muted-foreground";
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
//#endregion
|
|
2806
|
+
//#region src/overlay/HoverCard.tsx
|
|
2807
|
+
const placementPos = {
|
|
2808
|
+
top: "bottom-full left-1/2 -translate-x-1/2 mb-1.5",
|
|
2809
|
+
bottom: "top-full left-1/2 -translate-x-1/2 mt-1.5",
|
|
2810
|
+
left: "right-full top-1/2 -translate-y-1/2 mr-1.5",
|
|
2811
|
+
right: "left-full top-1/2 -translate-y-1/2 ml-1.5"
|
|
2812
|
+
};
|
|
2813
|
+
function HoverCard({ trigger, children, placement = "top", delay = 0, arrow = true, className, cardClassName }) {
|
|
2814
|
+
const [open, setOpen] = useState(false);
|
|
2815
|
+
const timer = useState(null);
|
|
2816
|
+
function onEnter() {
|
|
2817
|
+
if (delay > 0) {
|
|
2818
|
+
const id = window.setTimeout(() => setOpen(true), delay);
|
|
2819
|
+
timer[1](id);
|
|
2820
|
+
} else setOpen(true);
|
|
2821
|
+
}
|
|
2822
|
+
function onLeave() {
|
|
2823
|
+
if (timer[0] !== null) window.clearTimeout(timer[0]);
|
|
2824
|
+
setOpen(false);
|
|
2825
|
+
}
|
|
2826
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
2827
|
+
className: cn("relative inline-flex items-center", className),
|
|
2828
|
+
onMouseEnter: onEnter,
|
|
2829
|
+
onMouseLeave: onLeave,
|
|
2830
|
+
onFocus: onEnter,
|
|
2831
|
+
onBlur: onLeave,
|
|
2832
|
+
children: [trigger, open && /* @__PURE__ */ jsxs("div", {
|
|
2833
|
+
role: "tooltip",
|
|
2834
|
+
className: cn("absolute z-20 bg-background border border-border rounded-md shadow-lg px-2.5 py-1.5 whitespace-nowrap text-[11px]", placementPos[placement], cardClassName),
|
|
2835
|
+
children: [children, arrow && placement === "top" && /* @__PURE__ */ jsx("div", {
|
|
2836
|
+
className: "absolute top-full left-1/2 -translate-x-1/2 -mt-px",
|
|
2837
|
+
children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-background border-b border-r border-border rotate-45 -translate-y-1" })
|
|
2838
|
+
})]
|
|
2839
|
+
})]
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
//#endregion
|
|
2844
|
+
//#region src/overlay/Modal.tsx
|
|
2845
|
+
const sizeClass = {
|
|
2846
|
+
sm: "max-w-sm",
|
|
2847
|
+
md: "max-w-md",
|
|
2848
|
+
lg: "max-w-2xl",
|
|
2849
|
+
xl: "max-w-4xl",
|
|
2850
|
+
full: "max-w-[95vw] max-h-[95vh]"
|
|
2851
|
+
};
|
|
2852
|
+
function Modal({ open, onClose, title, size = "md", closeOnBackdrop = true, closeOnEsc = true, hideClose = false, className, headerSlot, footer, children }) {
|
|
2853
|
+
const dialogRef = useRef(null);
|
|
2854
|
+
useEffect(() => {
|
|
2855
|
+
if (!open || !closeOnEsc) return;
|
|
2856
|
+
const onKey = (e) => {
|
|
2857
|
+
if (e.key === "Escape") onClose();
|
|
2858
|
+
};
|
|
2859
|
+
document.addEventListener("keydown", onKey);
|
|
2860
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
2861
|
+
}, [
|
|
2862
|
+
open,
|
|
2863
|
+
closeOnEsc,
|
|
2864
|
+
onClose
|
|
2865
|
+
]);
|
|
2866
|
+
useEffect(() => {
|
|
2867
|
+
if (!open) return;
|
|
2868
|
+
const prev = document.activeElement;
|
|
2869
|
+
dialogRef.current?.focus();
|
|
2870
|
+
return () => prev?.focus?.();
|
|
2871
|
+
}, [open]);
|
|
2872
|
+
if (!open) return null;
|
|
2873
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2874
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40",
|
|
2875
|
+
onClick: closeOnBackdrop ? onClose : void 0,
|
|
2876
|
+
role: "presentation",
|
|
2877
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2878
|
+
ref: dialogRef,
|
|
2879
|
+
tabIndex: -1,
|
|
2880
|
+
role: "dialog",
|
|
2881
|
+
"aria-modal": "true",
|
|
2882
|
+
"aria-label": typeof title === "string" ? title : void 0,
|
|
2883
|
+
className: cn("relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col", sizeClass[size], className),
|
|
2884
|
+
onClick: (e) => e.stopPropagation(),
|
|
2885
|
+
children: [
|
|
2886
|
+
(title || headerSlot || !hideClose) && /* @__PURE__ */ jsxs("div", {
|
|
2887
|
+
className: "flex items-center gap-density-2 px-density-4 py-density-3 border-b border-border",
|
|
2888
|
+
children: [
|
|
2889
|
+
title && /* @__PURE__ */ jsx("h2", {
|
|
2890
|
+
className: "text-sm font-semibold flex-1",
|
|
2891
|
+
children: title
|
|
2892
|
+
}),
|
|
2893
|
+
headerSlot,
|
|
2894
|
+
!hideClose && /* @__PURE__ */ jsx("button", {
|
|
2895
|
+
type: "button",
|
|
2896
|
+
onClick: onClose,
|
|
2897
|
+
"aria-label": "Close",
|
|
2898
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
2899
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "codicon:close" })
|
|
2900
|
+
})
|
|
2901
|
+
]
|
|
2902
|
+
}),
|
|
2903
|
+
/* @__PURE__ */ jsx("div", {
|
|
2904
|
+
className: "flex-1 overflow-auto px-density-4 py-density-3",
|
|
2905
|
+
children
|
|
2906
|
+
}),
|
|
2907
|
+
footer && /* @__PURE__ */ jsx("div", {
|
|
2908
|
+
className: "px-density-4 py-density-3 border-t border-border",
|
|
2909
|
+
children: footer
|
|
2910
|
+
})
|
|
2911
|
+
]
|
|
2912
|
+
})
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
//#endregion
|
|
2917
|
+
export { AVATAR_PALETTE, AnsiHtml, Avatar, Badge, Button, Clicky, DensityProvider, DensitySwitcher, DetailEmptyState, DiagnosticsDetailPanel, DiagnosticsTree, FilterPill, FilterPillGroup, FilterSeparator, Gauge, HarPanel, HoverCard, Icon, JsonView, LogViewer, Markdown, Modal, ProgressBar, Section, Select, SortableHeader, SplitPane, TabButton, ThemeProvider, ThemeSwitcher, Tree, TreeGroupHeader, TreeNode, badgeVariants, buttonVariants, cn, countGoroutinesByState, countProcesses, countStackByState, countThreadsByState, detectDumpFormat, findProcessByPID, fnv1a32, paletteClass, parseGoroutineDump, parseJvmThreadDump, parseStackDump, processLabel, processStateColor, processStateIcon, useDensity, useHistoryRoute, useSort, useTheme };
|
|
2918
|
+
//# sourceMappingURL=index.mjs.map
|