@rebasepro/studio 0.6.1 → 0.8.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.
Files changed (37) hide show
  1. package/dist/ApiKeysView-D-_FSlNL.js +684 -0
  2. package/dist/ApiKeysView-D-_FSlNL.js.map +1 -0
  3. package/dist/{BranchesView-DncIRcZt.js → BranchesView-Dlg78EQ8.js} +4 -5
  4. package/dist/{BranchesView-DncIRcZt.js.map → BranchesView-Dlg78EQ8.js.map} +1 -1
  5. package/dist/{JSEditor-BhAbEjCP.js → JSEditor-Ca4XYGRp.js} +7 -7
  6. package/dist/JSEditor-Ca4XYGRp.js.map +1 -0
  7. package/dist/{LogsExplorer-CqtKILj8.js → LogsExplorer-J4xfsuv3.js} +49 -83
  8. package/dist/LogsExplorer-J4xfsuv3.js.map +1 -0
  9. package/dist/{RLSEditor-DpF1u9EC.js → RLSEditor-BM64laoW.js} +294 -107
  10. package/dist/RLSEditor-BM64laoW.js.map +1 -0
  11. package/dist/{SQLEditor-BLuq_zDM.js → SQLEditor-CuAhR-zr.js} +4 -4
  12. package/dist/{SQLEditor-BLuq_zDM.js.map → SQLEditor-CuAhR-zr.js.map} +1 -1
  13. package/dist/{SchemaVisualizer-BJK2u3C0.js → SchemaVisualizer-OibKoD3g.js} +2 -2
  14. package/dist/{SchemaVisualizer-BJK2u3C0.js.map → SchemaVisualizer-OibKoD3g.js.map} +1 -1
  15. package/dist/{StorageView-CvrnHmDG.js → StorageView-BMhD29YO.js} +33 -5
  16. package/dist/StorageView-BMhD29YO.js.map +1 -0
  17. package/dist/components/ApiKeys/ApiKeysView.d.ts +2 -0
  18. package/dist/index.es.js +141 -94
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +1221 -317
  21. package/dist/index.umd.js.map +1 -1
  22. package/package.json +8 -8
  23. package/src/components/ApiKeys/ApiKeysView.tsx +580 -0
  24. package/src/components/Branches/BranchesView.tsx +3 -4
  25. package/src/components/JSEditor/JSEditorSidebar.tsx +3 -3
  26. package/src/components/JSEditor/JSMonacoEditor.tsx +3 -3
  27. package/src/components/LogsExplorer/LogsExplorer.tsx +46 -84
  28. package/src/components/RLSEditor/RLSEditor.tsx +354 -117
  29. package/src/components/RebaseStudio.tsx +10 -1
  30. package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
  31. package/src/components/StorageView/StorageView.tsx +43 -4
  32. package/src/components/StudioHomePage.tsx +144 -85
  33. package/src/utils/pgColumnToProperty.ts +2 -2
  34. package/dist/JSEditor-BhAbEjCP.js.map +0 -1
  35. package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
  36. package/dist/RLSEditor-DpF1u9EC.js.map +0 -1
  37. package/dist/StorageView-CvrnHmDG.js.map +0 -1
@@ -0,0 +1,684 @@
1
+ import { useRebaseClient, useSnackbarController } from "@rebasepro/core";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { AlertCircleIcon, Button, CheckCircleIcon, Checkbox, Chip, CircularProgress, CopyIcon, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, KeyRoundIcon, PlusIcon, RefreshCwIcon, Select, SelectItem, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/ApiKeys/ApiKeysView.tsx
6
+ function formatRelative(iso) {
7
+ if (!iso) return "—";
8
+ const d = new Date(iso);
9
+ const now = Date.now();
10
+ const diff = d.getTime() - now;
11
+ const abs = Math.abs(diff);
12
+ if (abs < 6e4) return diff > 0 ? "in <1m" : "<1m ago";
13
+ if (abs < 36e5) {
14
+ const m = Math.round(abs / 6e4);
15
+ return diff > 0 ? `in ${m}m` : `${m}m ago`;
16
+ }
17
+ if (abs < 864e5) {
18
+ const h = Math.round(abs / 36e5);
19
+ return diff > 0 ? `in ${h}h` : `${h}h ago`;
20
+ }
21
+ return d.toLocaleDateString();
22
+ }
23
+ function permissionSummary(perms) {
24
+ if (perms.length === 0) return "No permissions";
25
+ const wildcard = perms.find((p) => p.collection === "*");
26
+ if (wildcard) return `All collections (${wildcard.operations.join(", ")})`;
27
+ if (perms.length === 1) return `${perms[0].collection} (${perms[0].operations.join(", ")})`;
28
+ return `${perms.length} collections`;
29
+ }
30
+ function isExpired(key) {
31
+ return !!(key.expires_at && new Date(key.expires_at) < /* @__PURE__ */ new Date());
32
+ }
33
+ function keyStatus(key) {
34
+ if (key.revoked_at) return {
35
+ label: "Revoked",
36
+ color: "text-red-500"
37
+ };
38
+ if (isExpired(key)) return {
39
+ label: "Expired",
40
+ color: "text-amber-500"
41
+ };
42
+ return {
43
+ label: "Active",
44
+ color: "text-emerald-500"
45
+ };
46
+ }
47
+ function ApiKeysView() {
48
+ const client = useRebaseClient();
49
+ const snackbar = useSnackbarController();
50
+ const [keys, setKeys] = useState([]);
51
+ const [loading, setLoading] = useState(true);
52
+ const [selectedId, setSelectedId] = useState(null);
53
+ const [showCreate, setShowCreate] = useState(false);
54
+ const [showSecret, setShowSecret] = useState(null);
55
+ const [revoking, setRevoking] = useState(null);
56
+ const clientRef = useRef(client);
57
+ clientRef.current = client;
58
+ const snackbarRef = useRef(snackbar);
59
+ snackbarRef.current = snackbar;
60
+ const loadKeys = useCallback(async () => {
61
+ const c = clientRef.current;
62
+ if (!c?.apiKeys) {
63
+ setLoading(false);
64
+ return;
65
+ }
66
+ try {
67
+ setKeys((await c.apiKeys.listKeys()).keys);
68
+ } catch (e) {
69
+ snackbarRef.current.open({
70
+ type: "error",
71
+ message: e instanceof Error ? e.message : String(e)
72
+ });
73
+ } finally {
74
+ setLoading(false);
75
+ }
76
+ }, []);
77
+ useEffect(() => {
78
+ loadKeys();
79
+ }, [loadKeys]);
80
+ const handleRevoke = async (id) => {
81
+ const c = clientRef.current;
82
+ if (!c?.apiKeys) return;
83
+ setRevoking(id);
84
+ try {
85
+ await c.apiKeys.revokeKey(id);
86
+ snackbarRef.current.open({
87
+ type: "success",
88
+ message: "API key revoked"
89
+ });
90
+ await loadKeys();
91
+ if (selectedId === id) setSelectedId(null);
92
+ } catch (e) {
93
+ snackbarRef.current.open({
94
+ type: "error",
95
+ message: e instanceof Error ? e.message : String(e)
96
+ });
97
+ } finally {
98
+ setRevoking(null);
99
+ }
100
+ };
101
+ const handleCreated = (keyWithSecret) => {
102
+ setShowCreate(false);
103
+ setShowSecret(keyWithSecret);
104
+ loadKeys();
105
+ };
106
+ const selectedKey = keys.find((k) => k.id === selectedId);
107
+ const activeKeys = keys.filter((k) => !k.revoked_at && !isExpired(k));
108
+ const inactiveKeys = keys.filter((k) => k.revoked_at || isExpired(k));
109
+ if (loading) return /* @__PURE__ */ jsx("div", {
110
+ className: "flex items-center justify-center h-full",
111
+ children: /* @__PURE__ */ jsx(CircularProgress, {})
112
+ });
113
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsxs("div", {
115
+ className: "flex h-full w-full overflow-hidden bg-white dark:bg-surface-950",
116
+ children: [/* @__PURE__ */ jsxs("div", {
117
+ className: cls("flex flex-col w-[340px] min-w-[280px] border-r h-full", defaultBorderMixin),
118
+ children: [/* @__PURE__ */ jsxs("div", {
119
+ className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
120
+ children: [/* @__PURE__ */ jsxs("div", {
121
+ className: "flex items-center gap-2",
122
+ children: [
123
+ /* @__PURE__ */ jsx(KeyRoundIcon, {
124
+ size: iconSize.smallest,
125
+ className: "text-primary"
126
+ }),
127
+ /* @__PURE__ */ jsx(Typography, {
128
+ variant: "subtitle2",
129
+ className: "font-semibold",
130
+ children: "API Keys"
131
+ }),
132
+ /* @__PURE__ */ jsx(Chip, {
133
+ size: "smallest",
134
+ className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
135
+ children: activeKeys.length
136
+ })
137
+ ]
138
+ }), /* @__PURE__ */ jsxs("div", {
139
+ className: "flex items-center gap-1",
140
+ children: [/* @__PURE__ */ jsx(IconButton, {
141
+ size: "small",
142
+ onClick: loadKeys,
143
+ title: "Refresh",
144
+ children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
145
+ }), /* @__PURE__ */ jsx(Button, {
146
+ size: "small",
147
+ color: "primary",
148
+ onClick: () => setShowCreate(true),
149
+ startIcon: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.smallest }),
150
+ children: "New"
151
+ })]
152
+ })]
153
+ }), /* @__PURE__ */ jsxs("div", {
154
+ className: "flex-1 overflow-y-auto p-2 space-y-1",
155
+ children: [
156
+ activeKeys.length === 0 && inactiveKeys.length === 0 && /* @__PURE__ */ jsxs("div", {
157
+ className: "flex flex-col items-center justify-center h-full gap-3 text-center p-6",
158
+ children: [
159
+ /* @__PURE__ */ jsx(KeyRoundIcon, {
160
+ size: iconSize.medium,
161
+ className: "text-surface-300 dark:text-surface-600"
162
+ }),
163
+ /* @__PURE__ */ jsx(Typography, {
164
+ variant: "body2",
165
+ color: "secondary",
166
+ children: "No API keys yet"
167
+ }),
168
+ /* @__PURE__ */ jsx(Typography, {
169
+ variant: "caption",
170
+ color: "disabled",
171
+ children: "Create a key to enable machine-to-machine authentication"
172
+ })
173
+ ]
174
+ }),
175
+ activeKeys.map((key) => /* @__PURE__ */ jsx(KeyListItem, {
176
+ apiKey: key,
177
+ selected: selectedId === key.id,
178
+ onClick: () => setSelectedId(key.id)
179
+ }, key.id)),
180
+ inactiveKeys.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
181
+ className: "px-2 pt-3 pb-1",
182
+ children: /* @__PURE__ */ jsx(Typography, {
183
+ variant: "caption",
184
+ color: "disabled",
185
+ className: "text-[10px] uppercase tracking-wider font-medium",
186
+ children: "Revoked / Expired"
187
+ })
188
+ }), inactiveKeys.map((key) => /* @__PURE__ */ jsx(KeyListItem, {
189
+ apiKey: key,
190
+ selected: selectedId === key.id,
191
+ onClick: () => setSelectedId(key.id)
192
+ }, key.id))] })
193
+ ]
194
+ })]
195
+ }), /* @__PURE__ */ jsx("div", {
196
+ className: "flex-1 flex flex-col min-w-0 h-full overflow-hidden",
197
+ children: !selectedKey ? /* @__PURE__ */ jsx("div", {
198
+ className: "flex items-center justify-center h-full",
199
+ children: /* @__PURE__ */ jsx(Typography, {
200
+ variant: "body2",
201
+ color: "disabled",
202
+ children: "Select an API key to view details"
203
+ })
204
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
205
+ /* @__PURE__ */ jsxs("div", {
206
+ className: cls("flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]", defaultBorderMixin),
207
+ children: [/* @__PURE__ */ jsxs("div", {
208
+ className: "flex items-center gap-3 min-w-0",
209
+ children: [/* @__PURE__ */ jsx(KeyRoundIcon, {
210
+ size: iconSize.small,
211
+ className: "text-primary shrink-0"
212
+ }), /* @__PURE__ */ jsxs("div", {
213
+ className: "min-w-0",
214
+ children: [/* @__PURE__ */ jsx(Typography, {
215
+ variant: "subtitle1",
216
+ className: "font-semibold truncate",
217
+ children: selectedKey.name
218
+ }), /* @__PURE__ */ jsxs(Typography, {
219
+ variant: "caption",
220
+ color: "secondary",
221
+ className: "font-mono text-[11px]",
222
+ children: [selectedKey.key_prefix, "•••"]
223
+ })]
224
+ })]
225
+ }), /* @__PURE__ */ jsx("div", {
226
+ className: "flex items-center gap-2 shrink-0",
227
+ children: !selectedKey.revoked_at && /* @__PURE__ */ jsx(Button, {
228
+ size: "small",
229
+ color: "error",
230
+ variant: "outlined",
231
+ onClick: () => handleRevoke(selectedKey.id),
232
+ disabled: revoking === selectedKey.id,
233
+ startIcon: revoking === selectedKey.id ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(Trash2Icon, { size: iconSize.smallest }),
234
+ children: "Revoke"
235
+ })
236
+ })]
237
+ }),
238
+ /* @__PURE__ */ jsxs("div", {
239
+ className: "px-5 py-4 bg-surface-50 dark:bg-surface-900/50",
240
+ children: [/* @__PURE__ */ jsxs("div", {
241
+ className: "grid grid-cols-2 md:grid-cols-4 gap-3",
242
+ children: [
243
+ /* @__PURE__ */ jsx(StatCard, {
244
+ label: "Status",
245
+ value: keyStatus(selectedKey).label,
246
+ className: keyStatus(selectedKey).color
247
+ }),
248
+ /* @__PURE__ */ jsx(StatCard, {
249
+ label: "Created",
250
+ value: formatRelative(selectedKey.created_at)
251
+ }),
252
+ /* @__PURE__ */ jsx(StatCard, {
253
+ label: "Last Used",
254
+ value: formatRelative(selectedKey.last_used_at)
255
+ }),
256
+ /* @__PURE__ */ jsx(StatCard, {
257
+ label: "Expires",
258
+ value: selectedKey.expires_at ? formatRelative(selectedKey.expires_at) : "Never"
259
+ })
260
+ ]
261
+ }), /* @__PURE__ */ jsxs("div", {
262
+ className: "grid grid-cols-2 gap-3 mt-3",
263
+ children: [/* @__PURE__ */ jsx(StatCard, {
264
+ label: "Rate Limit",
265
+ value: selectedKey.rate_limit ? `${selectedKey.rate_limit}/15min` : "Default (1000/15min)"
266
+ }), /* @__PURE__ */ jsx(StatCard, {
267
+ label: "Created By",
268
+ value: selectedKey.created_by,
269
+ mono: true
270
+ })]
271
+ })]
272
+ }),
273
+ /* @__PURE__ */ jsxs("div", {
274
+ className: cls("flex items-center gap-2 px-5 py-2 border-y bg-white dark:bg-surface-950", defaultBorderMixin),
275
+ children: [/* @__PURE__ */ jsx(Typography, {
276
+ variant: "subtitle2",
277
+ className: "font-semibold text-[13px]",
278
+ children: "Permissions"
279
+ }), /* @__PURE__ */ jsx(Chip, {
280
+ size: "smallest",
281
+ className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
282
+ children: selectedKey.permissions.length
283
+ })]
284
+ }),
285
+ /* @__PURE__ */ jsx("div", {
286
+ className: "flex-1 overflow-y-auto px-5 py-3",
287
+ children: selectedKey.permissions.length === 0 ? /* @__PURE__ */ jsx(Typography, {
288
+ variant: "body2",
289
+ color: "disabled",
290
+ children: "No permissions configured"
291
+ }) : /* @__PURE__ */ jsx("div", {
292
+ className: "space-y-2",
293
+ children: selectedKey.permissions.map((perm, idx) => /* @__PURE__ */ jsxs("div", {
294
+ className: cls("flex items-center gap-3 px-3 py-2 rounded-lg border", defaultBorderMixin),
295
+ children: [/* @__PURE__ */ jsx(Typography, {
296
+ variant: "body2",
297
+ className: "font-mono text-[13px] font-medium flex-1",
298
+ children: perm.collection === "*" ? "* (all collections)" : perm.collection
299
+ }), /* @__PURE__ */ jsx("div", {
300
+ className: "flex items-center gap-1",
301
+ children: perm.operations.map((op) => /* @__PURE__ */ jsx(Chip, {
302
+ size: "smallest",
303
+ className: cls(op === "read" && "bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300", op === "write" && "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300", op === "delete" && "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300"),
304
+ children: op
305
+ }, op))
306
+ })]
307
+ }, idx))
308
+ })
309
+ })
310
+ ] })
311
+ })]
312
+ }),
313
+ showCreate && /* @__PURE__ */ jsx(CreateApiKeyDialog, {
314
+ onClose: () => setShowCreate(false),
315
+ onCreated: handleCreated
316
+ }),
317
+ showSecret && /* @__PURE__ */ jsx(SecretDisplayDialog, {
318
+ keyWithSecret: showSecret,
319
+ onClose: () => setShowSecret(null)
320
+ })
321
+ ] });
322
+ }
323
+ function KeyListItem({ apiKey, selected, onClick }) {
324
+ const status = keyStatus(apiKey);
325
+ return /* @__PURE__ */ jsxs("div", {
326
+ onClick,
327
+ className: cls("flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all", selected ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30" : "hover:bg-surface-100 dark:hover:bg-surface-950"),
328
+ children: [
329
+ /* @__PURE__ */ jsx("div", { className: cls("w-2 h-2 rounded-full shrink-0", status.label === "Active" ? "bg-emerald-400" : status.label === "Expired" ? "bg-amber-400" : "bg-red-400") }),
330
+ /* @__PURE__ */ jsxs("div", {
331
+ className: "flex-1 min-w-0",
332
+ children: [/* @__PURE__ */ jsx(Typography, {
333
+ variant: "body2",
334
+ className: "truncate font-medium text-[13px]",
335
+ children: apiKey.name
336
+ }), /* @__PURE__ */ jsxs(Typography, {
337
+ variant: "caption",
338
+ color: "secondary",
339
+ className: "truncate text-[11px] font-mono",
340
+ children: [apiKey.key_prefix, "•••"]
341
+ })]
342
+ }),
343
+ /* @__PURE__ */ jsx("div", {
344
+ className: "shrink-0",
345
+ children: /* @__PURE__ */ jsx(Typography, {
346
+ variant: "caption",
347
+ color: "disabled",
348
+ className: "text-[10px]",
349
+ children: permissionSummary(apiKey.permissions)
350
+ })
351
+ })
352
+ ]
353
+ });
354
+ }
355
+ function StatCard({ label, value, mono, className }) {
356
+ return /* @__PURE__ */ jsxs("div", {
357
+ className: cls("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", defaultBorderMixin),
358
+ children: [/* @__PURE__ */ jsx(Typography, {
359
+ variant: "caption",
360
+ color: "secondary",
361
+ className: "text-[10px] uppercase tracking-wider font-medium",
362
+ children: label
363
+ }), /* @__PURE__ */ jsx(Typography, {
364
+ variant: "body2",
365
+ className: cls("mt-0.5 font-semibold text-[13px]", mono && "font-mono", className),
366
+ children: value
367
+ })]
368
+ });
369
+ }
370
+ function CreateApiKeyDialog({ onClose, onCreated }) {
371
+ const client = useRebaseClient();
372
+ const snackbar = useSnackbarController();
373
+ const [name, setName] = useState("");
374
+ const [permissions, setPermissions] = useState([{
375
+ collection: "*",
376
+ read: true,
377
+ write: false,
378
+ delete: false
379
+ }]);
380
+ const [rateLimit, setRateLimit] = useState("");
381
+ const [expiresIn, setExpiresIn] = useState("never");
382
+ const [creating, setCreating] = useState(false);
383
+ const addRow = () => setPermissions([...permissions, {
384
+ collection: "",
385
+ read: true,
386
+ write: false,
387
+ delete: false
388
+ }]);
389
+ const removeRow = (idx) => setPermissions(permissions.filter((_, i) => i !== idx));
390
+ const updateRow = (idx, field, value) => {
391
+ setPermissions(permissions.map((row, i) => i === idx ? {
392
+ ...row,
393
+ [field]: value
394
+ } : row));
395
+ };
396
+ const handleCreate = async () => {
397
+ if (!client?.apiKeys || !name.trim()) return;
398
+ const apiPerms = permissions.filter((r) => r.collection.trim()).map((r) => ({
399
+ collection: r.collection.trim(),
400
+ operations: [
401
+ ...r.read ? ["read"] : [],
402
+ ...r.write ? ["write"] : [],
403
+ ...r.delete ? ["delete"] : []
404
+ ]
405
+ })).filter((p) => p.operations.length > 0);
406
+ if (apiPerms.length === 0) {
407
+ snackbar.open({
408
+ type: "error",
409
+ message: "At least one permission is required"
410
+ });
411
+ return;
412
+ }
413
+ let expires_at = null;
414
+ if (expiresIn === "7d") expires_at = new Date(Date.now() + 7 * 864e5).toISOString();
415
+ else if (expiresIn === "30d") expires_at = new Date(Date.now() + 30 * 864e5).toISOString();
416
+ else if (expiresIn === "90d") expires_at = new Date(Date.now() + 90 * 864e5).toISOString();
417
+ else if (expiresIn === "1y") expires_at = new Date(Date.now() + 365 * 864e5).toISOString();
418
+ setCreating(true);
419
+ try {
420
+ onCreated((await client.apiKeys.createKey({
421
+ name: name.trim(),
422
+ permissions: apiPerms,
423
+ rate_limit: rateLimit ? parseInt(rateLimit, 10) : null,
424
+ expires_at
425
+ })).key);
426
+ } catch (e) {
427
+ snackbar.open({
428
+ type: "error",
429
+ message: e instanceof Error ? e.message : String(e)
430
+ });
431
+ } finally {
432
+ setCreating(false);
433
+ }
434
+ };
435
+ return /* @__PURE__ */ jsxs(Dialog, {
436
+ open: true,
437
+ onOpenChange: (open) => {
438
+ if (!open) onClose();
439
+ },
440
+ maxWidth: "lg",
441
+ children: [
442
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Create API Key" }),
443
+ /* @__PURE__ */ jsxs(DialogContent, {
444
+ className: "space-y-4",
445
+ children: [
446
+ /* @__PURE__ */ jsx(TextField, {
447
+ label: "Name",
448
+ value: name,
449
+ onChange: (e) => setName(e.target.value),
450
+ placeholder: "e.g. Analytics Pipeline",
451
+ size: "small",
452
+ autoFocus: true
453
+ }),
454
+ /* @__PURE__ */ jsxs("div", { children: [
455
+ /* @__PURE__ */ jsx(Typography, {
456
+ variant: "caption",
457
+ color: "secondary",
458
+ className: "text-[11px] uppercase tracking-wider font-medium mb-2 block",
459
+ children: "Permissions"
460
+ }),
461
+ /* @__PURE__ */ jsx("div", {
462
+ className: "space-y-2",
463
+ children: permissions.map((row, idx) => /* @__PURE__ */ jsxs("div", {
464
+ className: cls("flex items-center gap-2 p-2 rounded-lg border", defaultBorderMixin),
465
+ children: [
466
+ /* @__PURE__ */ jsx(TextField, {
467
+ size: "small",
468
+ value: row.collection,
469
+ onChange: (e) => updateRow(idx, "collection", e.target.value),
470
+ placeholder: "Collection slug or *",
471
+ className: "flex-1"
472
+ }),
473
+ /* @__PURE__ */ jsxs("label", {
474
+ className: "flex items-center gap-1 text-xs cursor-pointer",
475
+ children: [/* @__PURE__ */ jsx(Checkbox, {
476
+ size: "small",
477
+ checked: row.read,
478
+ onCheckedChange: (v) => updateRow(idx, "read", !!v)
479
+ }), /* @__PURE__ */ jsx("span", {
480
+ className: "text-emerald-600 dark:text-emerald-400",
481
+ children: "read"
482
+ })]
483
+ }),
484
+ /* @__PURE__ */ jsxs("label", {
485
+ className: "flex items-center gap-1 text-xs cursor-pointer",
486
+ children: [/* @__PURE__ */ jsx(Checkbox, {
487
+ size: "small",
488
+ checked: row.write,
489
+ onCheckedChange: (v) => updateRow(idx, "write", !!v)
490
+ }), /* @__PURE__ */ jsx("span", {
491
+ className: "text-blue-600 dark:text-blue-400",
492
+ children: "write"
493
+ })]
494
+ }),
495
+ /* @__PURE__ */ jsxs("label", {
496
+ className: "flex items-center gap-1 text-xs cursor-pointer",
497
+ children: [/* @__PURE__ */ jsx(Checkbox, {
498
+ size: "small",
499
+ checked: row.delete,
500
+ onCheckedChange: (v) => updateRow(idx, "delete", !!v)
501
+ }), /* @__PURE__ */ jsx("span", {
502
+ className: "text-red-600 dark:text-red-400",
503
+ children: "delete"
504
+ })]
505
+ }),
506
+ permissions.length > 1 && /* @__PURE__ */ jsx(IconButton, {
507
+ size: "small",
508
+ onClick: () => removeRow(idx),
509
+ children: /* @__PURE__ */ jsx(Trash2Icon, { size: iconSize.smallest })
510
+ })
511
+ ]
512
+ }, idx))
513
+ }),
514
+ /* @__PURE__ */ jsx(Button, {
515
+ size: "small",
516
+ variant: "text",
517
+ onClick: addRow,
518
+ className: "mt-1",
519
+ startIcon: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.smallest }),
520
+ children: "Add collection"
521
+ })
522
+ ] }),
523
+ /* @__PURE__ */ jsxs("div", {
524
+ className: "flex gap-4",
525
+ children: [/* @__PURE__ */ jsx("div", {
526
+ className: "flex-1",
527
+ children: /* @__PURE__ */ jsxs(Select, {
528
+ label: "Expires",
529
+ value: expiresIn,
530
+ onValueChange: setExpiresIn,
531
+ size: "small",
532
+ renderValue: (v) => v === "never" ? "Never" : v === "7d" ? "7 days" : v === "30d" ? "30 days" : v === "90d" ? "90 days" : "1 year",
533
+ children: [
534
+ /* @__PURE__ */ jsx(SelectItem, {
535
+ value: "never",
536
+ children: "Never"
537
+ }),
538
+ /* @__PURE__ */ jsx(SelectItem, {
539
+ value: "7d",
540
+ children: "7 days"
541
+ }),
542
+ /* @__PURE__ */ jsx(SelectItem, {
543
+ value: "30d",
544
+ children: "30 days"
545
+ }),
546
+ /* @__PURE__ */ jsx(SelectItem, {
547
+ value: "90d",
548
+ children: "90 days"
549
+ }),
550
+ /* @__PURE__ */ jsx(SelectItem, {
551
+ value: "1y",
552
+ children: "1 year"
553
+ })
554
+ ]
555
+ })
556
+ }), /* @__PURE__ */ jsx("div", {
557
+ className: "flex-1",
558
+ children: /* @__PURE__ */ jsx(TextField, {
559
+ label: "Rate Limit (per 15 min)",
560
+ value: rateLimit,
561
+ onChange: (e) => setRateLimit(e.target.value.replace(/\D/g, "")),
562
+ placeholder: "Default: 1000",
563
+ size: "small"
564
+ })
565
+ })]
566
+ })
567
+ ]
568
+ }),
569
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
570
+ variant: "text",
571
+ onClick: onClose,
572
+ children: "Cancel"
573
+ }), /* @__PURE__ */ jsx(Button, {
574
+ color: "primary",
575
+ onClick: handleCreate,
576
+ disabled: creating || !name.trim(),
577
+ startIcon: creating ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : void 0,
578
+ children: "Create Key"
579
+ })] })
580
+ ]
581
+ });
582
+ }
583
+ function SecretDisplayDialog({ keyWithSecret, onClose }) {
584
+ const snackbar = useSnackbarController();
585
+ const [copied, setCopied] = useState(false);
586
+ const handleCopy = async () => {
587
+ try {
588
+ await navigator.clipboard.writeText(keyWithSecret.key);
589
+ setCopied(true);
590
+ snackbar.open({
591
+ type: "success",
592
+ message: "API key copied to clipboard"
593
+ });
594
+ setTimeout(() => setCopied(false), 2e3);
595
+ } catch {
596
+ snackbar.open({
597
+ type: "error",
598
+ message: "Failed to copy"
599
+ });
600
+ }
601
+ };
602
+ return /* @__PURE__ */ jsxs(Dialog, {
603
+ open: true,
604
+ onOpenChange: (open) => {
605
+ if (!open) onClose();
606
+ },
607
+ maxWidth: "md",
608
+ children: [
609
+ /* @__PURE__ */ jsx(DialogTitle, { children: /* @__PURE__ */ jsxs("div", {
610
+ className: "flex items-center gap-2",
611
+ children: [/* @__PURE__ */ jsx(CheckCircleIcon, {
612
+ size: iconSize.small,
613
+ className: "text-emerald-500"
614
+ }), "API Key Created"]
615
+ }) }),
616
+ /* @__PURE__ */ jsxs(DialogContent, { children: [
617
+ /* @__PURE__ */ jsxs("div", {
618
+ className: "p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 mb-4",
619
+ children: [/* @__PURE__ */ jsxs("div", {
620
+ className: "flex items-center gap-2 mb-1",
621
+ children: [/* @__PURE__ */ jsx(AlertCircleIcon, {
622
+ size: iconSize.smallest,
623
+ className: "text-amber-600 dark:text-amber-400"
624
+ }), /* @__PURE__ */ jsx(Typography, {
625
+ variant: "caption",
626
+ className: "font-semibold text-amber-700 dark:text-amber-400",
627
+ children: "Copy your key now — it won't be shown again"
628
+ })]
629
+ }), /* @__PURE__ */ jsx(Typography, {
630
+ variant: "caption",
631
+ className: "text-amber-600 dark:text-amber-300",
632
+ children: "This is the only time the full API key will be displayed. Store it securely."
633
+ })]
634
+ }),
635
+ /* @__PURE__ */ jsxs("div", {
636
+ className: cls("flex items-center gap-2 p-3 rounded-lg border bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
637
+ children: [/* @__PURE__ */ jsx("code", {
638
+ className: "flex-1 text-[12px] font-mono break-all text-surface-700 dark:text-surface-300 select-all",
639
+ children: keyWithSecret.key
640
+ }), /* @__PURE__ */ jsx(Tooltip, {
641
+ title: copied ? "Copied!" : "Copy",
642
+ children: /* @__PURE__ */ jsx(IconButton, {
643
+ size: "small",
644
+ onClick: handleCopy,
645
+ children: copied ? /* @__PURE__ */ jsx(CheckCircleIcon, {
646
+ size: iconSize.smallest,
647
+ className: "text-emerald-500"
648
+ }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })
649
+ })
650
+ })]
651
+ }),
652
+ /* @__PURE__ */ jsxs("div", {
653
+ className: "mt-4 space-y-1",
654
+ children: [/* @__PURE__ */ jsxs(Typography, {
655
+ variant: "caption",
656
+ color: "secondary",
657
+ children: [
658
+ /* @__PURE__ */ jsx("strong", { children: "Name:" }),
659
+ " ",
660
+ keyWithSecret.name
661
+ ]
662
+ }), /* @__PURE__ */ jsxs(Typography, {
663
+ variant: "caption",
664
+ color: "secondary",
665
+ children: [
666
+ /* @__PURE__ */ jsx("strong", { children: "Permissions:" }),
667
+ " ",
668
+ permissionSummary(keyWithSecret.permissions)
669
+ ]
670
+ })]
671
+ })
672
+ ] }),
673
+ /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button, {
674
+ color: "primary",
675
+ onClick: onClose,
676
+ children: "Done"
677
+ }) })
678
+ ]
679
+ });
680
+ }
681
+ //#endregion
682
+ export { ApiKeysView };
683
+
684
+ //# sourceMappingURL=ApiKeysView-D-_FSlNL.js.map