apiblaze 0.17.21 → 0.17.23
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 +63 -0
- package/dist/index.js +451 -217
- package/dist/react/index.d.mts +67 -7
- package/dist/react/index.d.ts +67 -7
- package/dist/react/index.js +651 -69
- package/dist/react/index.mjs +651 -69
- package/dist/server/index.d.mts +165 -25
- package/dist/server/index.d.ts +165 -25
- package/dist/server/index.js +207 -35
- package/dist/server/index.mjs +204 -36
- package/package.json +28 -16
package/dist/react/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
"use strict";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
@@ -31,151 +32,732 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
32
|
var react_exports = {};
|
|
32
33
|
__export(react_exports, {
|
|
33
34
|
ApiKeyWidget: () => ApiKeyWidget,
|
|
35
|
+
UsersGroupsWidget: () => UsersGroupsWidget,
|
|
34
36
|
default: () => react_default
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(react_exports);
|
|
39
|
+
var React2 = __toESM(require("react"));
|
|
40
|
+
|
|
41
|
+
// src/react/groups.tsx
|
|
37
42
|
var React = __toESM(require("react"));
|
|
38
43
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
44
|
+
var SEALED = "apiblaze_admins";
|
|
39
45
|
var DEFAULTS = {
|
|
40
46
|
accent: "#4f46e5",
|
|
47
|
+
accentText: "#ffffff",
|
|
41
48
|
background: "transparent",
|
|
42
49
|
surface: "#ffffff",
|
|
50
|
+
headerBackground: "",
|
|
43
51
|
text: "#111827",
|
|
44
52
|
muted: "#6b7280",
|
|
45
53
|
border: "#e5e7eb",
|
|
46
|
-
|
|
54
|
+
danger: "#dc2626",
|
|
55
|
+
success: "#16a34a",
|
|
56
|
+
radius: "12px",
|
|
57
|
+
fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
58
|
+
monoFontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
|
|
47
59
|
};
|
|
48
|
-
function
|
|
60
|
+
function userLabel(u) {
|
|
61
|
+
return u.display_name || u.email || u.abz_sub;
|
|
62
|
+
}
|
|
63
|
+
function relTime(iso) {
|
|
64
|
+
if (!iso) return null;
|
|
65
|
+
const t = new Date(iso).getTime();
|
|
66
|
+
if (Number.isNaN(t)) return null;
|
|
67
|
+
const s = Math.floor((Date.now() - t) / 1e3);
|
|
68
|
+
if (s < 60) return "just now";
|
|
69
|
+
const m = Math.floor(s / 60);
|
|
70
|
+
if (m < 60) return `${m}m ago`;
|
|
71
|
+
const h = Math.floor(m / 60);
|
|
72
|
+
if (h < 24) return `${h}h ago`;
|
|
73
|
+
const d = Math.floor(h / 24);
|
|
74
|
+
if (d < 30) return `${d}d ago`;
|
|
75
|
+
return new Date(iso).toLocaleDateString();
|
|
76
|
+
}
|
|
77
|
+
var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint = "/api/apiblaze/groups", theme, title = "Users & groups", className }, ref) {
|
|
49
78
|
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
50
|
-
const
|
|
51
|
-
const [
|
|
52
|
-
const [
|
|
79
|
+
const headerBg = t.headerBackground || t.surface;
|
|
80
|
+
const [access, setAccess] = React.useState("ok");
|
|
81
|
+
const [groups, setGroups] = React.useState(null);
|
|
82
|
+
const [users, setUsers] = React.useState([]);
|
|
83
|
+
const [observed, setObserved] = React.useState([]);
|
|
84
|
+
const [loadError, setLoadError] = React.useState(null);
|
|
85
|
+
const [open, setOpen] = React.useState(null);
|
|
86
|
+
const [detail, setDetail] = React.useState(null);
|
|
87
|
+
const [detailLoading, setDetailLoading] = React.useState(false);
|
|
88
|
+
const [creating, setCreating] = React.useState(false);
|
|
89
|
+
const [newName, setNewName] = React.useState("");
|
|
90
|
+
const [showCreate, setShowCreate] = React.useState(false);
|
|
91
|
+
const [pending, setPending] = React.useState(null);
|
|
53
92
|
const [err, setErr] = React.useState(null);
|
|
54
|
-
const [
|
|
55
|
-
const [
|
|
93
|
+
const [confirmDelete, setConfirmDelete] = React.useState(null);
|
|
94
|
+
const [showTraffic, setShowTraffic] = React.useState(false);
|
|
95
|
+
const [addSel, setAddSel] = React.useState("");
|
|
96
|
+
const [addRole, setAddRole] = React.useState("member");
|
|
97
|
+
const [nestSel, setNestSel] = React.useState("");
|
|
56
98
|
const call = React.useCallback(async (body) => {
|
|
57
|
-
setErr(null);
|
|
58
99
|
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
59
100
|
const data = await res.json().catch(() => ({}));
|
|
60
101
|
if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
|
|
61
102
|
return data;
|
|
62
103
|
}, [endpoint]);
|
|
63
104
|
const load = React.useCallback(async () => {
|
|
105
|
+
setLoadError(null);
|
|
64
106
|
try {
|
|
65
107
|
const d = await call();
|
|
66
|
-
|
|
108
|
+
setAccess(d.access === "pending" ? "pending" : "ok");
|
|
109
|
+
setGroups((d.groups ?? []).slice().sort((a, b) => a.name.localeCompare(b.name)));
|
|
110
|
+
setUsers(d.users ?? []);
|
|
111
|
+
setObserved(d.observed ?? []);
|
|
67
112
|
} catch (e) {
|
|
68
|
-
|
|
69
|
-
|
|
113
|
+
setLoadError(e instanceof Error ? e.message : "Could not load groups.");
|
|
114
|
+
setGroups(null);
|
|
70
115
|
}
|
|
71
116
|
}, [call]);
|
|
72
117
|
React.useEffect(() => {
|
|
73
118
|
load();
|
|
74
119
|
}, [load]);
|
|
120
|
+
React.useImperativeHandle(ref, () => ({ refresh: load }), [load]);
|
|
121
|
+
const loadDetail = React.useCallback(async (groupId) => {
|
|
122
|
+
setDetailLoading(true);
|
|
123
|
+
setDetail(null);
|
|
124
|
+
setErr(null);
|
|
125
|
+
setAddSel("");
|
|
126
|
+
setNestSel("");
|
|
127
|
+
try {
|
|
128
|
+
setDetail(await call({ action: "group-detail", groupId }));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
setErr(e instanceof Error ? e.message : "Could not load this group.");
|
|
131
|
+
} finally {
|
|
132
|
+
setDetailLoading(false);
|
|
133
|
+
}
|
|
134
|
+
}, [call]);
|
|
135
|
+
function toggle(g) {
|
|
136
|
+
if (open === g.id) {
|
|
137
|
+
setOpen(null);
|
|
138
|
+
setDetail(null);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
setOpen(g.id);
|
|
142
|
+
loadDetail(g.id);
|
|
143
|
+
}
|
|
144
|
+
async function act(label, body, after) {
|
|
145
|
+
setPending(label);
|
|
146
|
+
setErr(null);
|
|
147
|
+
try {
|
|
148
|
+
await call(body);
|
|
149
|
+
await load();
|
|
150
|
+
if (after) await after();
|
|
151
|
+
} catch (e) {
|
|
152
|
+
setErr(e instanceof Error ? e.message : "The action failed.");
|
|
153
|
+
} finally {
|
|
154
|
+
setPending(null);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function createGroup() {
|
|
158
|
+
const name = newName.trim();
|
|
159
|
+
if (!name) return;
|
|
160
|
+
setCreating(true);
|
|
161
|
+
setErr(null);
|
|
162
|
+
try {
|
|
163
|
+
await call({ action: "create-group", name });
|
|
164
|
+
setNewName("");
|
|
165
|
+
setShowCreate(false);
|
|
166
|
+
await load();
|
|
167
|
+
} catch (e) {
|
|
168
|
+
setErr(e instanceof Error ? e.message : "Could not create the group.");
|
|
169
|
+
} finally {
|
|
170
|
+
setCreating(false);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const vars = {
|
|
174
|
+
// @ts-expect-error CSS custom properties
|
|
175
|
+
"--abz-accent": t.accent,
|
|
176
|
+
background: t.background,
|
|
177
|
+
color: t.text,
|
|
178
|
+
fontFamily: t.fontFamily,
|
|
179
|
+
fontSize: 14
|
|
180
|
+
};
|
|
181
|
+
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius, overflow: "hidden" };
|
|
182
|
+
const btn = (kind = "ghost", small = false) => ({
|
|
183
|
+
cursor: "pointer",
|
|
184
|
+
borderRadius: 8,
|
|
185
|
+
fontWeight: 600,
|
|
186
|
+
fontSize: small ? 12 : 13,
|
|
187
|
+
padding: small ? "5px 10px" : "8px 14px",
|
|
188
|
+
fontFamily: "inherit",
|
|
189
|
+
border: `1px solid ${kind === "primary" ? t.accent : kind === "danger" ? t.danger : t.border}`,
|
|
190
|
+
background: kind === "primary" ? t.accent : "transparent",
|
|
191
|
+
color: kind === "primary" ? t.accentText : kind === "danger" ? t.danger : t.text,
|
|
192
|
+
whiteSpace: "nowrap"
|
|
193
|
+
});
|
|
194
|
+
const inputStyle = {
|
|
195
|
+
fontFamily: "inherit",
|
|
196
|
+
fontSize: 13,
|
|
197
|
+
padding: "6px 10px",
|
|
198
|
+
borderRadius: 8,
|
|
199
|
+
border: `1px solid ${t.border}`,
|
|
200
|
+
background: t.surface,
|
|
201
|
+
color: t.text,
|
|
202
|
+
minWidth: 0
|
|
203
|
+
};
|
|
204
|
+
const selectStyle = { ...inputStyle, fontWeight: 600, fontSize: 12, cursor: "pointer" };
|
|
205
|
+
const chip = { fontSize: 11, padding: "2px 7px", borderRadius: 999, border: `1px solid ${t.border}`, color: t.muted };
|
|
206
|
+
const busy = pending !== null || creating;
|
|
207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `abz-widget ${className ?? ""}`, style: vars, children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `.abz-widget button:focus-visible,.abz-widget select:focus-visible,.abz-widget input:focus-visible{outline:2px solid var(--abz-accent);outline-offset:2px}` }),
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: card, children: [
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", borderBottom: `1px solid ${t.border}`, background: headerBg }, children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { fontSize: 15 }, children: title }),
|
|
212
|
+
access === "ok" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: busy, onClick: () => setShowCreate((v) => !v), "aria-label": "Create a new group", children: "+ New group" })
|
|
213
|
+
] }),
|
|
214
|
+
access === "pending" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: [
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, color: t.text, marginBottom: 6 }, children: "Admin access pending" }),
|
|
216
|
+
"You don\u2019t have group-management access for this workspace yet. If you were just added as an admin, it activates on your first sign-in \u2014 try refreshing. Otherwise ask your API provider to add your email as a workspace admin.",
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: load, children: "Refresh" }) })
|
|
218
|
+
] }),
|
|
219
|
+
access === "ok" && showCreate && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, padding: "12px 16px", borderBottom: `1px solid ${t.border}` }, children: [
|
|
220
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
221
|
+
"input",
|
|
222
|
+
{
|
|
223
|
+
style: { ...inputStyle, flex: 1 },
|
|
224
|
+
placeholder: "Group name (e.g. admin, reservationists)",
|
|
225
|
+
value: newName,
|
|
226
|
+
onChange: (e) => setNewName(e.target.value),
|
|
227
|
+
onKeyDown: (e) => e.key === "Enter" && createGroup(),
|
|
228
|
+
"aria-label": "New group name"
|
|
229
|
+
}
|
|
230
|
+
),
|
|
231
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("primary", true), disabled: creating || !newName.trim(), onClick: createGroup, children: creating ? "Creating\u2026" : "Create" }),
|
|
232
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => {
|
|
233
|
+
setShowCreate(false);
|
|
234
|
+
setNewName("");
|
|
235
|
+
}, children: "Cancel" })
|
|
236
|
+
] }),
|
|
237
|
+
access === "ok" && groups === null && !loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading groups\u2026" }),
|
|
238
|
+
access === "ok" && loadError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
239
|
+
loadError,
|
|
240
|
+
" ",
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
242
|
+
] }),
|
|
243
|
+
access === "ok" && groups !== null && groups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: "No groups yet. Create one to start organizing your users." }),
|
|
244
|
+
access === "ok" && groups !== null && groups.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: groups.map((g) => {
|
|
245
|
+
const sealed = g.name === SEALED;
|
|
246
|
+
const isOpen = open === g.id;
|
|
247
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { borderTop: `1px solid ${t.border}` }, children: [
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
249
|
+
"button",
|
|
250
|
+
{
|
|
251
|
+
onClick: () => toggle(g),
|
|
252
|
+
"aria-expanded": isOpen,
|
|
253
|
+
style: { display: "flex", width: "100%", gap: 10, alignItems: "center", padding: "12px 16px", background: "transparent", border: "none", cursor: "pointer", color: t.text, fontFamily: "inherit", fontSize: 14, textAlign: "left" },
|
|
254
|
+
children: [
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: isOpen ? "\u25BE" : "\u25B8" }),
|
|
256
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: g.name }),
|
|
257
|
+
sealed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: "admins \xB7 managed by allowlist" }),
|
|
258
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip }, children: [
|
|
259
|
+
g.member_count ?? 0,
|
|
260
|
+
" member",
|
|
261
|
+
(g.member_count ?? 0) === 1 ? "" : "s"
|
|
262
|
+
] })
|
|
263
|
+
]
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "0 16px 14px 34px" }, children: [
|
|
267
|
+
detailLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { color: t.muted, fontSize: 13 }, children: "Loading\u2026" }),
|
|
268
|
+
detail && detail.id === g.id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
269
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
270
|
+
detail.members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140 }, children: userLabel(m) }),
|
|
272
|
+
m.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: m.source_label }),
|
|
273
|
+
sealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...chip, fontWeight: 600 }, children: "admin" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
275
|
+
"select",
|
|
276
|
+
{
|
|
277
|
+
style: selectStyle,
|
|
278
|
+
value: m.role,
|
|
279
|
+
disabled: busy,
|
|
280
|
+
"aria-label": `Role of ${userLabel(m)}`,
|
|
281
|
+
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
282
|
+
children: [
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
284
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
),
|
|
288
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
289
|
+
"button",
|
|
290
|
+
{
|
|
291
|
+
style: btn("danger", true),
|
|
292
|
+
disabled: busy,
|
|
293
|
+
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
294
|
+
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
295
|
+
children: "Remove"
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
] })
|
|
299
|
+
] }, m.abz_sub)),
|
|
300
|
+
detail.members.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
301
|
+
] }),
|
|
302
|
+
sealed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: t.muted, marginTop: 8 }, children: "Workspace admins are granted by email on the provider\u2019s admin list \u2014 membership can\u2019t be edited here." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
303
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }, children: [
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
305
|
+
"select",
|
|
306
|
+
{
|
|
307
|
+
style: { ...selectStyle, flex: 1, minWidth: 160 },
|
|
308
|
+
value: addSel,
|
|
309
|
+
disabled: busy,
|
|
310
|
+
"aria-label": "User to add",
|
|
311
|
+
onChange: (e) => setAddSel(e.target.value),
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Add a user\u2026" }),
|
|
314
|
+
users.filter((u) => !detail.members.some((m) => m.abz_sub === u.abz_sub)).map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: u.abz_sub, children: userLabel(u) }, u.abz_sub))
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
),
|
|
318
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
319
|
+
"select",
|
|
320
|
+
{
|
|
321
|
+
style: selectStyle,
|
|
322
|
+
value: addRole,
|
|
323
|
+
disabled: busy,
|
|
324
|
+
"aria-label": "Role for the new member",
|
|
325
|
+
onChange: (e) => setAddRole(e.target.value),
|
|
326
|
+
children: [
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "member", children: "member" }),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "admin", children: "group admin" })
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
),
|
|
332
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
333
|
+
"button",
|
|
334
|
+
{
|
|
335
|
+
style: btn("primary", true),
|
|
336
|
+
disabled: busy || !addSel,
|
|
337
|
+
onClick: () => act("add-member", { action: "add-member", groupId: g.id, abzSub: addSel, role: addRole }, () => {
|
|
338
|
+
setAddSel("");
|
|
339
|
+
return loadDetail(g.id);
|
|
340
|
+
}),
|
|
341
|
+
children: "Add"
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
] }),
|
|
345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
346
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
347
|
+
detail.subgroups.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
348
|
+
s.name,
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
350
|
+
"button",
|
|
351
|
+
{
|
|
352
|
+
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
353
|
+
disabled: busy,
|
|
354
|
+
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
355
|
+
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
356
|
+
children: "\u2715"
|
|
357
|
+
}
|
|
358
|
+
)
|
|
359
|
+
] }, s.id)),
|
|
360
|
+
detail.subgroups.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
362
|
+
"select",
|
|
363
|
+
{
|
|
364
|
+
style: selectStyle,
|
|
365
|
+
value: nestSel,
|
|
366
|
+
disabled: busy,
|
|
367
|
+
"aria-label": "Group to nest",
|
|
368
|
+
onChange: (e) => setNestSel(e.target.value),
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: "Nest a group\u2026" }),
|
|
371
|
+
groups.filter((x) => x.id !== g.id && x.name !== SEALED && !detail.subgroups.some((s) => s.id === x.id)).map((x) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: x.id, children: x.name }, x.id))
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
),
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
376
|
+
"button",
|
|
377
|
+
{
|
|
378
|
+
style: btn("ghost", true),
|
|
379
|
+
disabled: busy || !nestSel,
|
|
380
|
+
onClick: () => act("nest", { action: "add-subgroup", groupId: g.id, childGroupId: nestSel }, () => {
|
|
381
|
+
setNestSel("");
|
|
382
|
+
return loadDetail(g.id);
|
|
383
|
+
}),
|
|
384
|
+
children: "Nest"
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
] }),
|
|
388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 12, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: confirmDelete === g.id ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: t.text }, children: [
|
|
390
|
+
"Delete \u201C",
|
|
391
|
+
g.name,
|
|
392
|
+
"\u201D? Members lose this group everywhere it\u2019s used. This can\u2019t be undone."
|
|
393
|
+
] }),
|
|
394
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
395
|
+
"button",
|
|
396
|
+
{
|
|
397
|
+
style: btn("danger", true),
|
|
398
|
+
disabled: busy,
|
|
399
|
+
onClick: () => {
|
|
400
|
+
setConfirmDelete(null);
|
|
401
|
+
act("del", { action: "delete-group", groupId: g.id }, () => {
|
|
402
|
+
setOpen(null);
|
|
403
|
+
setDetail(null);
|
|
404
|
+
});
|
|
405
|
+
},
|
|
406
|
+
children: "Delete group"
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: btn("ghost", true), onClick: () => setConfirmDelete(null), children: "Cancel" })
|
|
410
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: { ...btn("ghost", true), color: t.danger }, disabled: busy, onClick: () => setConfirmDelete(g.id), children: "Delete group" }) })
|
|
411
|
+
] })
|
|
412
|
+
] })
|
|
413
|
+
] })
|
|
414
|
+
] }, g.id);
|
|
415
|
+
}) }),
|
|
416
|
+
access === "ok" && groups !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { borderTop: `1px solid ${t.border}` }, children: [
|
|
417
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
418
|
+
"button",
|
|
419
|
+
{
|
|
420
|
+
onClick: () => setShowTraffic((v) => !v),
|
|
421
|
+
"aria-expanded": showTraffic,
|
|
422
|
+
style: { display: "flex", width: "100%", gap: 10, alignItems: "center", padding: "12px 16px", background: "transparent", border: "none", cursor: "pointer", color: t.text, fontFamily: "inherit", fontSize: 13, textAlign: "left" },
|
|
423
|
+
children: [
|
|
424
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showTraffic ? "\u25BE" : "\u25B8" }),
|
|
425
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { flex: 1 }, children: "Seen in traffic" }),
|
|
426
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: chip, children: [
|
|
427
|
+
observed.filter((o) => !o.provisioned).length,
|
|
428
|
+
" new"
|
|
429
|
+
] })
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
showTraffic && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 14px 34px" }, children: [
|
|
434
|
+
observed.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { style: { color: t.muted, fontSize: 13 }, children: "No identities observed yet \u2014 they appear here after their first API call." }),
|
|
435
|
+
observed.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
437
|
+
o.source_label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: chip, children: o.source_label }),
|
|
438
|
+
o.last_seen && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
439
|
+
"seen ",
|
|
440
|
+
relTime(o.last_seen),
|
|
441
|
+
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
442
|
+
] }),
|
|
443
|
+
o.provisioned ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: [
|
|
444
|
+
"user",
|
|
445
|
+
o.groups?.length ? ` \xB7 ${o.groups.join(", ")}` : ""
|
|
446
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
447
|
+
"button",
|
|
448
|
+
{
|
|
449
|
+
style: btn("primary", true),
|
|
450
|
+
disabled: busy,
|
|
451
|
+
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
452
|
+
onClick: () => act("provision", { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }),
|
|
453
|
+
children: "Add as user"
|
|
454
|
+
}
|
|
455
|
+
)
|
|
456
|
+
] }, o.abz_sub))
|
|
457
|
+
] })
|
|
458
|
+
] }),
|
|
459
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: err })
|
|
460
|
+
] })
|
|
461
|
+
] });
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// src/react/index.tsx
|
|
465
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
466
|
+
var DEFAULTS2 = {
|
|
467
|
+
accent: "#4f46e5",
|
|
468
|
+
accentText: "#ffffff",
|
|
469
|
+
background: "transparent",
|
|
470
|
+
surface: "#ffffff",
|
|
471
|
+
headerBackground: "",
|
|
472
|
+
text: "#111827",
|
|
473
|
+
muted: "#6b7280",
|
|
474
|
+
border: "#e5e7eb",
|
|
475
|
+
danger: "#dc2626",
|
|
476
|
+
success: "#16a34a",
|
|
477
|
+
radius: "12px",
|
|
478
|
+
fontFamily: "ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
479
|
+
monoFontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
|
|
480
|
+
};
|
|
481
|
+
function relTime2(iso) {
|
|
482
|
+
if (!iso) return null;
|
|
483
|
+
const t = new Date(iso).getTime();
|
|
484
|
+
if (Number.isNaN(t)) return null;
|
|
485
|
+
const s = Math.floor((Date.now() - t) / 1e3);
|
|
486
|
+
if (s < 60) return "just now";
|
|
487
|
+
const m = Math.floor(s / 60);
|
|
488
|
+
if (m < 60) return `${m}m ago`;
|
|
489
|
+
const h = Math.floor(m / 60);
|
|
490
|
+
if (h < 24) return `${h}h ago`;
|
|
491
|
+
const d = Math.floor(h / 24);
|
|
492
|
+
if (d < 30) return `${d}d ago`;
|
|
493
|
+
return new Date(iso).toLocaleDateString();
|
|
494
|
+
}
|
|
495
|
+
var ApiKeyWidget = React2.forwardRef(function ApiKeyWidget2({ endpoint = "/api/apiblaze/keys", theme, title = "Your API keys", className }, ref) {
|
|
496
|
+
const t = { ...DEFAULTS2, ...theme ?? {} };
|
|
497
|
+
const headerBg = t.headerBackground || t.surface;
|
|
498
|
+
const [keys, setKeys] = React2.useState(null);
|
|
499
|
+
const [eligible, setEligible] = React2.useState([]);
|
|
500
|
+
const [access, setAccess] = React2.useState("ok");
|
|
501
|
+
const [chosenType, setChosenType] = React2.useState("");
|
|
502
|
+
const [loadError, setLoadError] = React2.useState(null);
|
|
503
|
+
const [secret, setSecret] = React2.useState(null);
|
|
504
|
+
const [creating, setCreating] = React2.useState(false);
|
|
505
|
+
const [pending, setPending] = React2.useState(null);
|
|
506
|
+
const [confirming, setConfirming] = React2.useState(null);
|
|
507
|
+
const [rowError, setRowError] = React2.useState(null);
|
|
508
|
+
const [copied, setCopied] = React2.useState(false);
|
|
509
|
+
const call = React2.useCallback(async (body) => {
|
|
510
|
+
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
511
|
+
const data = await res.json().catch(() => ({}));
|
|
512
|
+
if (!res.ok) throw new Error(data.error || `Error ${res.status}`);
|
|
513
|
+
return data;
|
|
514
|
+
}, [endpoint]);
|
|
515
|
+
const load = React2.useCallback(async () => {
|
|
516
|
+
setLoadError(null);
|
|
517
|
+
try {
|
|
518
|
+
const d = await call();
|
|
519
|
+
setKeys((d.keys ?? []).slice().sort(byNewest));
|
|
520
|
+
setEligible(d.keyTypes ?? []);
|
|
521
|
+
setAccess(d.access === "denied" ? "denied" : "ok");
|
|
522
|
+
} catch (e) {
|
|
523
|
+
setLoadError(e instanceof Error ? e.message : "Could not load your keys.");
|
|
524
|
+
setKeys(null);
|
|
525
|
+
}
|
|
526
|
+
}, [call]);
|
|
527
|
+
React2.useEffect(() => {
|
|
528
|
+
load();
|
|
529
|
+
}, [load]);
|
|
530
|
+
React2.useImperativeHandle(ref, () => ({ refresh: load }), [load]);
|
|
531
|
+
React2.useEffect(() => {
|
|
532
|
+
if (eligible.length && !eligible.includes(chosenType)) {
|
|
533
|
+
setChosenType(eligible.includes("call-only") ? "call-only" : eligible[0]);
|
|
534
|
+
}
|
|
535
|
+
}, [eligible, chosenType]);
|
|
75
536
|
async function create() {
|
|
76
|
-
|
|
537
|
+
setCreating(true);
|
|
538
|
+
setRowError(null);
|
|
77
539
|
try {
|
|
78
|
-
const d = await call({ action: "create" });
|
|
79
|
-
if (d.key) setSecret(d.key);
|
|
540
|
+
const d = await call({ action: "create", keyType: chosenType || void 0 });
|
|
541
|
+
if (d.key) setSecret({ key: d.key, keyId: d.key_id });
|
|
80
542
|
await load();
|
|
81
543
|
} catch (e) {
|
|
82
|
-
|
|
544
|
+
setRowError({ keyId: "", msg: e instanceof Error ? e.message : "Could not create a key." });
|
|
83
545
|
} finally {
|
|
84
|
-
|
|
546
|
+
setCreating(false);
|
|
85
547
|
}
|
|
86
548
|
}
|
|
87
549
|
async function reveal(keyId) {
|
|
88
|
-
|
|
550
|
+
setPending(`reveal:${keyId}`);
|
|
551
|
+
setRowError(null);
|
|
89
552
|
try {
|
|
90
553
|
const d = await call({ action: "reveal", keyId });
|
|
91
|
-
if (d.key) setSecret(d.key);
|
|
92
|
-
else
|
|
554
|
+
if (d.key) setSecret({ key: d.key, keyId });
|
|
555
|
+
else setRowError({ keyId, msg: "For security, this key can only be shown once \u2014 right after it was created." });
|
|
93
556
|
} catch (e) {
|
|
94
|
-
|
|
557
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not show this key." });
|
|
95
558
|
} finally {
|
|
96
|
-
|
|
559
|
+
setPending(null);
|
|
97
560
|
}
|
|
98
561
|
}
|
|
99
562
|
async function rotate(keyId) {
|
|
100
|
-
|
|
563
|
+
setConfirming(null);
|
|
564
|
+
setPending(`rotate:${keyId}`);
|
|
565
|
+
setRowError(null);
|
|
101
566
|
try {
|
|
102
567
|
const d = await call({ action: "rotate", keyId });
|
|
103
|
-
if (d.key) setSecret(d.key);
|
|
568
|
+
if (d.key) setSecret({ key: d.key, keyId: d.key_id });
|
|
104
569
|
await load();
|
|
105
570
|
} catch (e) {
|
|
106
|
-
|
|
571
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not rotate this key." });
|
|
107
572
|
} finally {
|
|
108
|
-
|
|
573
|
+
setPending(null);
|
|
109
574
|
}
|
|
110
575
|
}
|
|
111
576
|
async function revoke(keyId) {
|
|
112
|
-
|
|
577
|
+
setConfirming(null);
|
|
578
|
+
setPending(`revoke:${keyId}`);
|
|
579
|
+
setRowError(null);
|
|
113
580
|
try {
|
|
114
581
|
await call({ action: "revoke", keyId });
|
|
115
582
|
await load();
|
|
116
583
|
} catch (e) {
|
|
117
|
-
|
|
584
|
+
setRowError({ keyId, msg: e instanceof Error ? e.message : "Could not revoke this key." });
|
|
118
585
|
} finally {
|
|
119
|
-
|
|
586
|
+
setPending(null);
|
|
120
587
|
}
|
|
121
588
|
}
|
|
122
|
-
function copy(v) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
589
|
+
async function copy(v) {
|
|
590
|
+
try {
|
|
591
|
+
await navigator.clipboard.writeText(v);
|
|
592
|
+
setCopied(true);
|
|
593
|
+
setTimeout(() => setCopied(false), 1600);
|
|
594
|
+
} catch {
|
|
595
|
+
}
|
|
126
596
|
}
|
|
127
597
|
const vars = {
|
|
128
|
-
// @ts-expect-error CSS custom
|
|
598
|
+
// @ts-expect-error CSS custom properties
|
|
129
599
|
"--abz-accent": t.accent,
|
|
130
600
|
"--abz-surface": t.surface,
|
|
131
601
|
"--abz-text": t.text,
|
|
132
602
|
"--abz-muted": t.muted,
|
|
133
603
|
"--abz-border": t.border,
|
|
604
|
+
"--abz-danger": t.danger,
|
|
134
605
|
"--abz-radius": t.radius,
|
|
135
606
|
background: t.background,
|
|
136
607
|
color: t.text,
|
|
137
|
-
fontFamily:
|
|
608
|
+
fontFamily: t.fontFamily,
|
|
138
609
|
fontSize: 14
|
|
139
610
|
};
|
|
140
|
-
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius,
|
|
141
|
-
const btn = (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
611
|
+
const card = { background: t.surface, border: `1px solid ${t.border}`, borderRadius: t.radius, overflow: "hidden" };
|
|
612
|
+
const btn = (kind = "ghost", small = false) => ({
|
|
613
|
+
cursor: "pointer",
|
|
614
|
+
borderRadius: 8,
|
|
615
|
+
fontWeight: 600,
|
|
616
|
+
fontSize: small ? 12 : 13,
|
|
617
|
+
padding: small ? "5px 10px" : "8px 14px",
|
|
618
|
+
fontFamily: "inherit",
|
|
619
|
+
border: `1px solid ${kind === "primary" ? t.accent : kind === "danger" ? t.danger : t.border}`,
|
|
620
|
+
background: kind === "primary" ? t.accent : "transparent",
|
|
621
|
+
color: kind === "primary" ? t.accentText : kind === "danger" ? t.danger : t.text,
|
|
622
|
+
whiteSpace: "nowrap"
|
|
623
|
+
});
|
|
624
|
+
const selectStyle = {
|
|
625
|
+
fontFamily: "inherit",
|
|
626
|
+
fontSize: 12,
|
|
627
|
+
fontWeight: 600,
|
|
628
|
+
padding: "5px 8px",
|
|
629
|
+
borderRadius: 8,
|
|
630
|
+
border: `1px solid ${t.border}`,
|
|
631
|
+
background: t.surface,
|
|
632
|
+
color: t.text,
|
|
633
|
+
cursor: "pointer"
|
|
634
|
+
};
|
|
635
|
+
const disabledStyle = (on) => on ? { opacity: 0.5, cursor: "not-allowed" } : {};
|
|
636
|
+
const secretBg = `color-mix(in srgb, ${t.accent} 8%, transparent)`;
|
|
637
|
+
const busyAny = creating || pending !== null;
|
|
638
|
+
const denied = access === "denied";
|
|
639
|
+
const showPicker = eligible.length > 1;
|
|
640
|
+
const Picker = showPicker ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { value: chosenType, onChange: (e) => setChosenType(e.target.value), style: selectStyle, "aria-label": "Key type", disabled: busyAny, children: eligible.map((k) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: k, children: k }, k)) }) : null;
|
|
641
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `abz-widget ${className ?? ""}`, style: vars, children: [
|
|
642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `.abz-widget button:focus-visible,.abz-widget select:focus-visible{outline:2px solid var(--abz-accent);outline-offset:2px}` }),
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: card, children: [
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", borderBottom: `1px solid ${t.border}`, background: headerBg }, children: [
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { style: { fontSize: 15 }, children: title }),
|
|
646
|
+
!denied && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
647
|
+
Picker,
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
649
|
+
"button",
|
|
650
|
+
{
|
|
651
|
+
style: { ...btn("primary", true), ...disabledStyle(busyAny) },
|
|
652
|
+
disabled: busyAny,
|
|
653
|
+
onClick: create,
|
|
654
|
+
"aria-label": "Create a new API key",
|
|
655
|
+
children: creating ? "Creating\u2026" : "+ Create key"
|
|
656
|
+
}
|
|
657
|
+
)
|
|
658
|
+
] })
|
|
166
659
|
] }),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/* @__PURE__ */ (0,
|
|
170
|
-
/* @__PURE__ */ (0,
|
|
171
|
-
/* @__PURE__ */ (0,
|
|
172
|
-
|
|
173
|
-
|
|
660
|
+
denied && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: "Your account doesn\u2019t have API access." }),
|
|
661
|
+
!denied && secret && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "status", "aria-live": "polite", style: { margin: 16, padding: 14, borderRadius: 10, border: `1px solid ${t.accent}`, background: secretBg }, children: [
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "New API key" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: t.muted, marginBottom: 8 }, children: "This is the only time you\u2019ll see this key. Copy it and store it somewhere safe." }),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
666
|
+
"input",
|
|
667
|
+
{
|
|
668
|
+
readOnly: true,
|
|
669
|
+
value: secret.key,
|
|
670
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
671
|
+
"aria-label": "New API key value",
|
|
672
|
+
style: { flex: 1, fontFamily: t.monoFontFamily, fontSize: 13, padding: "8px 10px", borderRadius: 8, border: `1px solid ${t.border}`, background: t.surface, color: t.text, minWidth: 0 }
|
|
673
|
+
}
|
|
674
|
+
),
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: { ...btn("primary"), ...copied ? { background: t.success, borderColor: t.success } : {} }, onClick: () => copy(secret.key), children: copied ? "Copied" : "Copy" })
|
|
676
|
+
] }),
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: 10, textAlign: "right" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btn("ghost", true), onClick: () => setSecret(null), children: "I\u2019ve saved it" }) })
|
|
678
|
+
] }),
|
|
679
|
+
!denied && keys === null && !loadError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading your keys\u2026" }),
|
|
680
|
+
!denied && loadError && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
681
|
+
loadError,
|
|
682
|
+
" ",
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
684
|
+
] }),
|
|
685
|
+
!denied && keys !== null && keys.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "28px 20px", textAlign: "center" }, children: [
|
|
686
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: t.muted, marginBottom: 14 }, children: "You don\u2019t have an API key yet. Create one to start calling the API." }),
|
|
687
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "inline-flex", gap: 8, alignItems: "center" }, children: [
|
|
688
|
+
Picker,
|
|
689
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: { ...btn("primary"), ...disabledStyle(creating) }, disabled: creating, onClick: create, children: creating ? "Creating\u2026" : "Create API key" })
|
|
690
|
+
] })
|
|
691
|
+
] }),
|
|
692
|
+
!denied && keys !== null && keys.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: keys.map((k) => {
|
|
693
|
+
const anyBusy = busyAny;
|
|
694
|
+
const used = relTime2(k.last_used);
|
|
695
|
+
const created = k.created_at ? new Date(k.created_at).toLocaleDateString() : null;
|
|
696
|
+
const isConfirm = confirming?.keyId === k.key_id;
|
|
697
|
+
const showable = !!k.expires_at;
|
|
698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { style: { padding: "12px 16px", borderTop: `1px solid ${t.border}`, opacity: k.disabled ? 0.6 : 1 }, children: [
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("code", { "aria-label": `API key ending in ${k.key_suffix ?? ""}`, style: { flex: 1, minWidth: 150, fontFamily: t.monoFontFamily, color: t.muted }, children: [
|
|
701
|
+
k.key_prefix ?? "\u2022\u2022\u2022",
|
|
702
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", children: "\u2022\u2022\u2022\u2022\u2022\u2022" }),
|
|
703
|
+
k.key_suffix ?? "\u2022\u2022\u2022"
|
|
704
|
+
] }),
|
|
705
|
+
k.environment && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, padding: "2px 7px", borderRadius: 999, border: `1px solid ${t.border}`, color: t.muted }, children: k.environment }),
|
|
706
|
+
k.disabled ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 11, fontWeight: 600, color: t.danger }, children: "Revoked" }) : isConfirm ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", flexBasis: "100%", flexWrap: "wrap", marginTop: 6 }, children: [
|
|
707
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: 12, color: t.text, flex: 1, minWidth: 200 }, children: confirming.action === "rotate" ? "Rotate this key? A new secret is generated now. The current key keeps working for a short grace period, then stops \u2014 anything still using it will break." : "Revoke this key? It stops working immediately and can\u2019t be undone." }),
|
|
708
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btn(confirming.action === "revoke" ? "danger" : "primary", true), onClick: () => confirming.action === "rotate" ? rotate(k.key_id) : revoke(k.key_id), children: confirming.action === "rotate" ? "Rotate key" : "Revoke key" }),
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btn("ghost", true), onClick: () => setConfirming(null), children: "Cancel" })
|
|
710
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
711
|
+
showable && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
712
|
+
"button",
|
|
713
|
+
{
|
|
714
|
+
style: { ...btn("ghost", true), ...disabledStyle(anyBusy) },
|
|
715
|
+
disabled: anyBusy,
|
|
716
|
+
"aria-label": `Show key ending in ${k.key_suffix ?? ""}`,
|
|
717
|
+
onClick: () => reveal(k.key_id),
|
|
718
|
+
children: pending === `reveal:${k.key_id}` ? "Showing\u2026" : "Show"
|
|
719
|
+
}
|
|
720
|
+
),
|
|
721
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
722
|
+
"button",
|
|
723
|
+
{
|
|
724
|
+
style: { ...btn("ghost", true), ...disabledStyle(anyBusy) },
|
|
725
|
+
disabled: anyBusy,
|
|
726
|
+
"aria-label": `Rotate key ending in ${k.key_suffix ?? ""}`,
|
|
727
|
+
onClick: () => setConfirming({ keyId: k.key_id, action: "rotate" }),
|
|
728
|
+
children: pending === `rotate:${k.key_id}` ? "Rotating\u2026" : "Rotate"
|
|
729
|
+
}
|
|
730
|
+
),
|
|
731
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
732
|
+
"button",
|
|
733
|
+
{
|
|
734
|
+
style: { ...btn("danger", true), ...disabledStyle(anyBusy) },
|
|
735
|
+
disabled: anyBusy,
|
|
736
|
+
"aria-label": `Revoke key ending in ${k.key_suffix ?? ""}`,
|
|
737
|
+
onClick: () => setConfirming({ keyId: k.key_id, action: "revoke" }),
|
|
738
|
+
children: "Revoke"
|
|
739
|
+
}
|
|
740
|
+
)
|
|
741
|
+
] })
|
|
742
|
+
] }),
|
|
743
|
+
(created || used) && !isConfirm && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 11, color: t.muted, marginTop: 5 }, children: [
|
|
744
|
+
created ? `Created ${created}` : "",
|
|
745
|
+
created && used ? " \xB7 " : "",
|
|
746
|
+
used ? `Last used ${used}` : created ? " \xB7 Never used" : "Never used"
|
|
747
|
+
] }),
|
|
748
|
+
rowError?.keyId === k.key_id && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { role: "alert", style: { fontSize: 12, color: t.danger, marginTop: 6 }, children: rowError.msg })
|
|
749
|
+
] }, k.key_id);
|
|
750
|
+
}) }),
|
|
751
|
+
!denied && rowError && rowError.keyId === "" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: rowError.msg })
|
|
174
752
|
] })
|
|
175
|
-
] })
|
|
753
|
+
] });
|
|
754
|
+
});
|
|
755
|
+
function byNewest(a, b) {
|
|
756
|
+
return new Date(b.created_at ?? 0).getTime() - new Date(a.created_at ?? 0).getTime();
|
|
176
757
|
}
|
|
177
758
|
var react_default = ApiKeyWidget;
|
|
178
759
|
// Annotate the CommonJS export names for ESM import in node:
|
|
179
760
|
0 && (module.exports = {
|
|
180
|
-
ApiKeyWidget
|
|
761
|
+
ApiKeyWidget,
|
|
762
|
+
UsersGroupsWidget
|
|
181
763
|
});
|