@hansenexus/hud 0.1.1 → 0.3.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/README.md +106 -25
- package/dist/define-hud-BHQR8Lbv.d.ts +31 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +1198 -0
- package/dist/next.d.ts +48 -0
- package/dist/next.js +77 -0
- package/dist/plugins/vitals.d.ts +25 -0
- package/dist/plugins/vitals.js +109 -0
- package/dist/plugins-1QmTmZyL.d.ts +76 -0
- package/dist/plugins-CzUiTn_r.js +68 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.js +106 -0
- package/package.json +55 -133
- package/chunk-20zke7cs.js +0 -574
- package/chunk-24vrg16w.js +0 -39
- package/chunk-50pq4sg6.js +0 -41
- package/chunk-5gtx3pza.js +0 -9
- package/chunk-6cdpasm9.js +0 -79
- package/chunk-6jt8yg4m.js +0 -117
- package/chunk-6ngkpwz7.js +0 -127
- package/chunk-71se605m.js +0 -85
- package/chunk-82nm9wcg.js +0 -316
- package/chunk-9hw5r9y1.js +0 -67
- package/chunk-c5y856ad.js +0 -98
- package/chunk-edq4jqch.js +0 -89
- package/chunk-emfnvz4p.js +0 -166
- package/chunk-h0hmjdqa.js +0 -264
- package/chunk-hc2g0n35.js +0 -24
- package/chunk-hex3wqag.js +0 -179
- package/chunk-kvxve0hx.js +0 -140
- package/chunk-kyeb6qeq.js +0 -95
- package/chunk-mz55rvw3.js +0 -172
- package/chunk-nm82wr8r.js +0 -58
- package/chunk-q15xwpy8.js +0 -48
- package/chunk-q96mkjve.js +0 -68
- package/chunk-tczzx083.js +0 -26
- package/chunk-tf774c4w.js +0 -115
- package/chunk-v086j53b.js +0 -19
- package/chunk-vjh0d29g.js +0 -52
- package/chunk-xezxydm3.js +0 -47
- package/chunk-xztw0xyt.js +0 -113
- package/chunk-yn1d38pb.js +0 -172
- package/core/access.d.mts +0 -53
- package/core/access.js +0 -27
- package/core/api-types.d.mts +0 -146
- package/core/api-types.js +0 -9
- package/core/capabilities.d.mts +0 -27
- package/core/capabilities.js +0 -14
- package/core/capture-buffer.d.mts +0 -37
- package/core/capture-buffer.js +0 -9
- package/core/force-state.d.mts +0 -68
- package/core/force-state.js +0 -21
- package/core/mock-cookie.d.mts +0 -30
- package/core/mock-cookie.js +0 -15
- package/core/types.d.mts +0 -59
- package/core/types.js +0 -9
- package/hud-provider.d.mts +0 -38
- package/hud-provider.js +0 -15
- package/hud.d.mts +0 -15
- package/hud.js +0 -18
- package/index.d.mts +0 -94
- package/index.js +0 -170
- package/lazy-panel.d.mts +0 -28
- package/lazy-panel.js +0 -11
- package/panels/cicd-panel.d.mts +0 -9
- package/panels/cicd-panel.js +0 -13
- package/panels/debug-info-panel.d.mts +0 -21
- package/panels/debug-info-panel.js +0 -9
- package/panels/dispatch-panel.d.mts +0 -10
- package/panels/dispatch-panel.js +0 -14
- package/panels/env-info-panel.d.mts +0 -9
- package/panels/env-info-panel.js +0 -12
- package/panels/feature-flags-panel.d.mts +0 -26
- package/panels/feature-flags-panel.js +0 -9
- package/panels/force-state-panel.d.mts +0 -11
- package/panels/force-state-panel.js +0 -113
- package/panels/info-row.d.mts +0 -21
- package/panels/info-row.js +0 -14
- package/panels/mock-seed-panel.d.mts +0 -15
- package/panels/mock-seed-panel.js +0 -14
- package/panels/network-console-panel.d.mts +0 -6
- package/panels/network-console-panel.js +0 -9
- package/panels/role-switcher-panel.d.mts +0 -28
- package/panels/role-switcher-panel.js +0 -9
- package/panels/sessions-panel.d.mts +0 -6
- package/panels/sessions-panel.js +0 -13
- package/panels/tokens-panel.d.mts +0 -38
- package/panels/tokens-panel.js +0 -9
- package/panels/unlock-panel.d.mts +0 -33
- package/panels/unlock-panel.js +0 -14
- package/panels/variants-panel.d.mts +0 -51
- package/panels/variants-panel.js +0 -12
- package/panels/vitals-panel.d.mts +0 -11
- package/panels/vitals-panel.js +0 -15
- package/server/guard.d.mts +0 -42
- package/server/guard.js +0 -12
- package/server/unlock.d.mts +0 -28
- package/server/unlock.js +0 -108
- package/state-boundary.d.mts +0 -25
- package/state-boundary.js +0 -54
package/chunk-6cdpasm9.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveHudEnv
|
|
3
|
-
} from "./chunk-vjh0d29g.js";
|
|
4
|
-
|
|
5
|
-
// core/access.ts
|
|
6
|
-
var HUD_UNLOCK_COOKIE = "hud-unlock";
|
|
7
|
-
var HUD_UNLOCK_TTL_MS = {
|
|
8
|
-
open: 0,
|
|
9
|
-
password: 12 * 60 * 60 * 1000,
|
|
10
|
-
flag: 2 * 60 * 60 * 1000,
|
|
11
|
-
denied: 0
|
|
12
|
-
};
|
|
13
|
-
function resolveHudAccess(env = resolveHudEnv()) {
|
|
14
|
-
if (env === "disabled")
|
|
15
|
-
return "denied";
|
|
16
|
-
if (env === "development")
|
|
17
|
-
return "open";
|
|
18
|
-
if (env === "production")
|
|
19
|
-
return "flag";
|
|
20
|
-
return "password";
|
|
21
|
-
}
|
|
22
|
-
function hudRequiresUnlock(access) {
|
|
23
|
-
return access === "password" || access === "flag";
|
|
24
|
-
}
|
|
25
|
-
function hudUnlockTtlMs(access) {
|
|
26
|
-
return HUD_UNLOCK_TTL_MS[access];
|
|
27
|
-
}
|
|
28
|
-
var encoder = new TextEncoder;
|
|
29
|
-
async function hmacHex(password, message) {
|
|
30
|
-
const key = await crypto.subtle.importKey("raw", encoder.encode(password), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
31
|
-
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
32
|
-
return Array.from(new Uint8Array(signature)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
33
|
-
}
|
|
34
|
-
function timingSafeEqual(a, b) {
|
|
35
|
-
const length = Math.max(a.length, b.length);
|
|
36
|
-
let diff = a.length ^ b.length;
|
|
37
|
-
for (let i = 0;i < length; i++) {
|
|
38
|
-
diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
39
|
-
}
|
|
40
|
-
return diff === 0;
|
|
41
|
-
}
|
|
42
|
-
async function signHudUnlockToken(password, issuedAt = Date.now()) {
|
|
43
|
-
return `${issuedAt}.${await hmacHex(password, String(issuedAt))}`;
|
|
44
|
-
}
|
|
45
|
-
async function verifyHudUnlockToken(token, password, options = {}) {
|
|
46
|
-
if (!token || !password)
|
|
47
|
-
return false;
|
|
48
|
-
const separator = token.indexOf(".");
|
|
49
|
-
if (separator <= 0)
|
|
50
|
-
return false;
|
|
51
|
-
const issuedAtRaw = token.slice(0, separator);
|
|
52
|
-
const signature = token.slice(separator + 1);
|
|
53
|
-
const issuedAt = Number(issuedAtRaw);
|
|
54
|
-
if (!Number.isSafeInteger(issuedAt) || issuedAt <= 0)
|
|
55
|
-
return false;
|
|
56
|
-
const now = options.now ?? Date.now();
|
|
57
|
-
const ttlMs = options.ttlMs ?? HUD_UNLOCK_TTL_MS.flag;
|
|
58
|
-
const age = now - issuedAt;
|
|
59
|
-
if (age < 0 || age > ttlMs)
|
|
60
|
-
return false;
|
|
61
|
-
return timingSafeEqual(signature, await hmacHex(password, issuedAtRaw));
|
|
62
|
-
}
|
|
63
|
-
function readCookie(header, name) {
|
|
64
|
-
if (!header)
|
|
65
|
-
return;
|
|
66
|
-
for (const part of header.split(";")) {
|
|
67
|
-
const trimmed = part.trim();
|
|
68
|
-
if (trimmed.startsWith(`${name}=`)) {
|
|
69
|
-
return decodeURIComponent(trimmed.slice(name.length + 1));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
function hudUnlockCookieString(token, ttlMs) {
|
|
75
|
-
const base = `${HUD_UNLOCK_COOKIE}=${token ?? ""}; Path=/; HttpOnly; SameSite=Strict; Secure`;
|
|
76
|
-
return token ? `${base}; Max-Age=${Math.floor(ttlMs / 1000)}` : `${base}; Max-Age=0`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export { HUD_UNLOCK_COOKIE, HUD_UNLOCK_TTL_MS, resolveHudAccess, hudRequiresUnlock, hudUnlockTtlMs, timingSafeEqual, signHudUnlockToken, verifyHudUnlockToken, readCookie, hudUnlockCookieString };
|
package/chunk-6jt8yg4m.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LoadingSpinner,
|
|
3
|
-
PanelSection
|
|
4
|
-
} from "./chunk-kyeb6qeq.js";
|
|
5
|
-
import {
|
|
6
|
-
Badge,
|
|
7
|
-
Button
|
|
8
|
-
} from "./chunk-xztw0xyt.js";
|
|
9
|
-
|
|
10
|
-
// panels/role-switcher-panel.tsx
|
|
11
|
-
import { Check, Shield, User } from "lucide-react";
|
|
12
|
-
import { useState } from "react";
|
|
13
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
-
"use client";
|
|
15
|
-
function RoleSwitcherPanel({ adapter }) {
|
|
16
|
-
const { roles, currentRoleId, currentUserLabel, isLoading = false, onSwitch } = adapter;
|
|
17
|
-
const [switching, setSwitching] = useState(false);
|
|
18
|
-
const [error, setError] = useState(null);
|
|
19
|
-
async function handleSwitch(roleId) {
|
|
20
|
-
if (roleId === currentRoleId)
|
|
21
|
-
return;
|
|
22
|
-
setSwitching(true);
|
|
23
|
-
setError(null);
|
|
24
|
-
try {
|
|
25
|
-
await onSwitch(roleId);
|
|
26
|
-
} catch (err) {
|
|
27
|
-
setError(err instanceof Error ? err.message : "Role switch failed");
|
|
28
|
-
} finally {
|
|
29
|
-
setSwitching(false);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (isLoading)
|
|
33
|
-
return /* @__PURE__ */ jsx(LoadingSpinner, {});
|
|
34
|
-
const current = roles.find((role) => role.id === currentRoleId);
|
|
35
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
36
|
-
className: "space-y-4",
|
|
37
|
-
children: [
|
|
38
|
-
/* @__PURE__ */ jsx(PanelSection, {
|
|
39
|
-
icon: Shield,
|
|
40
|
-
title: "Current Role",
|
|
41
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
42
|
-
className: "border bg-muted/30 px-3 py-2",
|
|
43
|
-
children: [
|
|
44
|
-
/* @__PURE__ */ jsxs("div", {
|
|
45
|
-
className: "flex items-center gap-2",
|
|
46
|
-
children: [
|
|
47
|
-
/* @__PURE__ */ jsx(User, {
|
|
48
|
-
className: "h-4 w-4 text-muted-foreground"
|
|
49
|
-
}),
|
|
50
|
-
/* @__PURE__ */ jsx("span", {
|
|
51
|
-
className: "text-sm font-medium",
|
|
52
|
-
children: currentUserLabel ?? "Unknown"
|
|
53
|
-
})
|
|
54
|
-
]
|
|
55
|
-
}),
|
|
56
|
-
/* @__PURE__ */ jsx(Badge, {
|
|
57
|
-
variant: "outline",
|
|
58
|
-
className: `mt-1 ${current?.badgeClassName ?? ""}`,
|
|
59
|
-
children: current?.label ?? currentRoleId ?? "—"
|
|
60
|
-
})
|
|
61
|
-
]
|
|
62
|
-
})
|
|
63
|
-
}),
|
|
64
|
-
/* @__PURE__ */ jsx(PanelSection, {
|
|
65
|
-
title: "Switch Role",
|
|
66
|
-
children: roles.map((role) => {
|
|
67
|
-
const isCurrent = role.id === currentRoleId;
|
|
68
|
-
return /* @__PURE__ */ jsxs(Button, {
|
|
69
|
-
variant: "ghost",
|
|
70
|
-
size: "sm",
|
|
71
|
-
disabled: isCurrent || switching,
|
|
72
|
-
className: "w-full justify-between text-xs",
|
|
73
|
-
onClick: () => handleSwitch(role.id),
|
|
74
|
-
children: [
|
|
75
|
-
/* @__PURE__ */ jsxs("div", {
|
|
76
|
-
className: "flex items-center gap-2",
|
|
77
|
-
children: [
|
|
78
|
-
/* @__PURE__ */ jsx("span", {
|
|
79
|
-
className: `h-2 w-2 rounded-full ${isCurrent ? "bg-primary" : "bg-muted-foreground/30"}`
|
|
80
|
-
}),
|
|
81
|
-
/* @__PURE__ */ jsx("span", {
|
|
82
|
-
children: role.label
|
|
83
|
-
})
|
|
84
|
-
]
|
|
85
|
-
}),
|
|
86
|
-
/* @__PURE__ */ jsxs("div", {
|
|
87
|
-
className: "flex items-center gap-1.5",
|
|
88
|
-
children: [
|
|
89
|
-
role.level !== undefined && /* @__PURE__ */ jsxs("span", {
|
|
90
|
-
className: "text-[10px] text-muted-foreground",
|
|
91
|
-
children: [
|
|
92
|
-
"Lvl ",
|
|
93
|
-
role.level
|
|
94
|
-
]
|
|
95
|
-
}),
|
|
96
|
-
isCurrent && /* @__PURE__ */ jsx(Check, {
|
|
97
|
-
className: "h-3 w-3 text-primary"
|
|
98
|
-
})
|
|
99
|
-
]
|
|
100
|
-
})
|
|
101
|
-
]
|
|
102
|
-
}, role.id);
|
|
103
|
-
})
|
|
104
|
-
}),
|
|
105
|
-
error && /* @__PURE__ */ jsx("p", {
|
|
106
|
-
className: "px-2 text-[10px] text-destructive",
|
|
107
|
-
children: error
|
|
108
|
-
}),
|
|
109
|
-
/* @__PURE__ */ jsx("p", {
|
|
110
|
-
className: "text-[10px] text-muted-foreground/60",
|
|
111
|
-
children: "Role changes take effect immediately. Use this for testing role-based access."
|
|
112
|
-
})
|
|
113
|
-
]
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export { RoleSwitcherPanel };
|
package/chunk-6ngkpwz7.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useHud
|
|
3
|
-
} from "./chunk-9hw5r9y1.js";
|
|
4
|
-
import {
|
|
5
|
-
InfoRow,
|
|
6
|
-
PanelSection,
|
|
7
|
-
ShortcutRow
|
|
8
|
-
} from "./chunk-kyeb6qeq.js";
|
|
9
|
-
import {
|
|
10
|
-
Badge
|
|
11
|
-
} from "./chunk-xztw0xyt.js";
|
|
12
|
-
import {
|
|
13
|
-
CAPABILITY_MATRIX
|
|
14
|
-
} from "./chunk-vjh0d29g.js";
|
|
15
|
-
import {
|
|
16
|
-
HUD_CAPABILITIES
|
|
17
|
-
} from "./chunk-v086j53b.js";
|
|
18
|
-
|
|
19
|
-
// panels/env-info-panel.tsx
|
|
20
|
-
import { Boxes, Info, Monitor, ShieldCheck } from "lucide-react";
|
|
21
|
-
import { version as reactVersion } from "react";
|
|
22
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
23
|
-
"use client";
|
|
24
|
-
function EnvInfoPanel() {
|
|
25
|
-
const { config, env, can } = useHud();
|
|
26
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
27
|
-
className: "space-y-4",
|
|
28
|
-
children: [
|
|
29
|
-
/* @__PURE__ */ jsxs(PanelSection, {
|
|
30
|
-
icon: Boxes,
|
|
31
|
-
title: "App",
|
|
32
|
-
children: [
|
|
33
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
34
|
-
label: "App",
|
|
35
|
-
value: config.appSlug
|
|
36
|
-
}),
|
|
37
|
-
/* @__PURE__ */ jsxs("div", {
|
|
38
|
-
className: "flex items-center justify-between px-2 py-1",
|
|
39
|
-
children: [
|
|
40
|
-
/* @__PURE__ */ jsx("span", {
|
|
41
|
-
className: "text-[10px] text-muted-foreground",
|
|
42
|
-
children: "hud env"
|
|
43
|
-
}),
|
|
44
|
-
/* @__PURE__ */ jsx(Badge, {
|
|
45
|
-
variant: "outline",
|
|
46
|
-
className: "h-4 px-1.5 font-mono text-[10px] uppercase",
|
|
47
|
-
children: env
|
|
48
|
-
})
|
|
49
|
-
]
|
|
50
|
-
}),
|
|
51
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
52
|
-
label: "Origin",
|
|
53
|
-
value: typeof window !== "undefined" ? window.location.origin : "—",
|
|
54
|
-
copyable: true
|
|
55
|
-
})
|
|
56
|
-
]
|
|
57
|
-
}),
|
|
58
|
-
/* @__PURE__ */ jsxs(PanelSection, {
|
|
59
|
-
icon: Monitor,
|
|
60
|
-
title: "Build",
|
|
61
|
-
children: [
|
|
62
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
63
|
-
label: "NODE_ENV",
|
|
64
|
-
value: process.env.NODE_ENV ?? "unknown"
|
|
65
|
-
}),
|
|
66
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
67
|
-
label: "React",
|
|
68
|
-
value: reactVersion
|
|
69
|
-
}),
|
|
70
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
71
|
-
label: "Convex URL",
|
|
72
|
-
value: process.env.NEXT_PUBLIC_CONVEX_URL ?? "not set",
|
|
73
|
-
copyable: true
|
|
74
|
-
}),
|
|
75
|
-
/* @__PURE__ */ jsx(InfoRow, {
|
|
76
|
-
label: "App URL",
|
|
77
|
-
value: process.env.NEXT_PUBLIC_APP_URL ?? "not set"
|
|
78
|
-
})
|
|
79
|
-
]
|
|
80
|
-
}),
|
|
81
|
-
/* @__PURE__ */ jsxs(PanelSection, {
|
|
82
|
-
icon: ShieldCheck,
|
|
83
|
-
title: `Capabilities · ${env}`,
|
|
84
|
-
children: [
|
|
85
|
-
/* @__PURE__ */ jsx("div", {
|
|
86
|
-
className: "grid grid-cols-2 gap-x-4",
|
|
87
|
-
children: HUD_CAPABILITIES.map((capability) => {
|
|
88
|
-
const granted = can(capability);
|
|
89
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
90
|
-
className: "flex items-center justify-between px-2 py-0.5",
|
|
91
|
-
title: granted ? undefined : `denied in ${env} (see CAPABILITY_MATRIX in core/capabilities.ts)`,
|
|
92
|
-
children: [
|
|
93
|
-
/* @__PURE__ */ jsx("span", {
|
|
94
|
-
className: `font-mono text-[10px] ${granted ? "" : "text-muted-foreground/50 line-through"}`,
|
|
95
|
-
children: capability
|
|
96
|
-
}),
|
|
97
|
-
/* @__PURE__ */ jsx("span", {
|
|
98
|
-
className: `h-1.5 w-1.5 rounded-full ${granted ? "bg-emerald-500" : "bg-muted-foreground/20"}`
|
|
99
|
-
})
|
|
100
|
-
]
|
|
101
|
-
}, capability);
|
|
102
|
-
})
|
|
103
|
-
}),
|
|
104
|
-
/* @__PURE__ */ jsxs("p", {
|
|
105
|
-
className: "px-2 text-[10px] text-muted-foreground/60",
|
|
106
|
-
children: [
|
|
107
|
-
CAPABILITY_MATRIX[env].length,
|
|
108
|
-
"/",
|
|
109
|
-
HUD_CAPABILITIES.length,
|
|
110
|
-
" granted — default deny, server routes re-check independently."
|
|
111
|
-
]
|
|
112
|
-
})
|
|
113
|
-
]
|
|
114
|
-
}),
|
|
115
|
-
/* @__PURE__ */ jsx(PanelSection, {
|
|
116
|
-
icon: Info,
|
|
117
|
-
title: "Shortcuts",
|
|
118
|
-
children: /* @__PURE__ */ jsx(ShortcutRow, {
|
|
119
|
-
keys: ["⌘", "⇧", "H"],
|
|
120
|
-
label: "Toggle hud"
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
]
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export { EnvInfoPanel };
|
package/chunk-71se605m.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LoadingSpinner
|
|
3
|
-
} from "./chunk-kyeb6qeq.js";
|
|
4
|
-
import {
|
|
5
|
-
Badge,
|
|
6
|
-
Switch
|
|
7
|
-
} from "./chunk-xztw0xyt.js";
|
|
8
|
-
|
|
9
|
-
// panels/feature-flags-panel.tsx
|
|
10
|
-
import { Flag } from "lucide-react";
|
|
11
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
-
"use client";
|
|
13
|
-
function FeatureFlagsPanel({ adapter }) {
|
|
14
|
-
const { flags, isLoading = false, onToggle } = adapter;
|
|
15
|
-
if (isLoading)
|
|
16
|
-
return /* @__PURE__ */ jsx(LoadingSpinner, {});
|
|
17
|
-
const categories = [...new Set(flags.map((flag) => flag.category ?? "general"))];
|
|
18
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
19
|
-
className: "space-y-4",
|
|
20
|
-
children: [
|
|
21
|
-
categories.map((category) => /* @__PURE__ */ jsxs("div", {
|
|
22
|
-
children: [
|
|
23
|
-
/* @__PURE__ */ jsxs("h4", {
|
|
24
|
-
className: "mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground",
|
|
25
|
-
children: [
|
|
26
|
-
/* @__PURE__ */ jsx(Flag, {
|
|
27
|
-
className: "h-3 w-3"
|
|
28
|
-
}),
|
|
29
|
-
category
|
|
30
|
-
]
|
|
31
|
-
}),
|
|
32
|
-
/* @__PURE__ */ jsx("div", {
|
|
33
|
-
className: "space-y-1",
|
|
34
|
-
children: flags.filter((flag) => (flag.category ?? "general") === category).map((flag) => /* @__PURE__ */ jsx(FlagRow, {
|
|
35
|
-
flag,
|
|
36
|
-
onToggle: () => void onToggle(flag.id)
|
|
37
|
-
}, flag.id))
|
|
38
|
-
})
|
|
39
|
-
]
|
|
40
|
-
}, category)),
|
|
41
|
-
flags.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
42
|
-
className: "px-2 text-[10px] text-muted-foreground/60",
|
|
43
|
-
children: "No flags registered."
|
|
44
|
-
})
|
|
45
|
-
]
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
function FlagRow({ flag, onToggle }) {
|
|
49
|
-
const isOverridden = flag.enabled !== flag.defaultValue;
|
|
50
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
51
|
-
className: "flex items-center justify-between px-2 py-1.5 hover:bg-muted/50",
|
|
52
|
-
children: [
|
|
53
|
-
/* @__PURE__ */ jsxs("div", {
|
|
54
|
-
className: "min-w-0 flex-1",
|
|
55
|
-
children: [
|
|
56
|
-
/* @__PURE__ */ jsxs("div", {
|
|
57
|
-
className: "flex items-center gap-1.5",
|
|
58
|
-
children: [
|
|
59
|
-
/* @__PURE__ */ jsx("span", {
|
|
60
|
-
className: "text-xs font-medium",
|
|
61
|
-
children: flag.label
|
|
62
|
-
}),
|
|
63
|
-
isOverridden && /* @__PURE__ */ jsx(Badge, {
|
|
64
|
-
variant: "outline",
|
|
65
|
-
className: "h-4 border-amber-500/50 px-1 text-[9px] text-amber-500",
|
|
66
|
-
children: "overridden"
|
|
67
|
-
})
|
|
68
|
-
]
|
|
69
|
-
}),
|
|
70
|
-
flag.description && /* @__PURE__ */ jsx("p", {
|
|
71
|
-
className: "truncate text-[10px] text-muted-foreground",
|
|
72
|
-
children: flag.description
|
|
73
|
-
})
|
|
74
|
-
]
|
|
75
|
-
}),
|
|
76
|
-
/* @__PURE__ */ jsx(Switch, {
|
|
77
|
-
checked: flag.enabled,
|
|
78
|
-
onCheckedChange: onToggle,
|
|
79
|
-
className: "ml-2 shrink-0"
|
|
80
|
-
})
|
|
81
|
-
]
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export { FeatureFlagsPanel };
|