@rebasepro/studio 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -1
- package/README.md +12 -4
- package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
- package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
- package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
- package/dist/BackupsView-DVkKzaPR.js.map +1 -0
- package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
- package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
- package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
- package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
- package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
- package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
- package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
- package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
- package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
- package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
- package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
- package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
- package/dist/components/RLSEditor/saveRules.d.ts +38 -0
- package/dist/components/RebaseStudio.d.ts +1 -1
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
- package/dist/components/load-failure-view.d.ts +17 -0
- package/dist/components/load-failure.d.ts +47 -0
- package/dist/index.es.js +56 -36
- package/dist/index.es.js.map +1 -1
- package/dist/load-failure-C6OMJSGC.js +31 -0
- package/dist/load-failure-C6OMJSGC.js.map +1 -0
- package/dist/load-failure-view-CBTB_h-h.js +48 -0
- package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
- package/package.json +36 -21
- package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
- package/dist/BackupsView-DNS6LdVg.js.map +0 -1
- package/dist/BranchesView-CM3B-ER3.js.map +0 -1
- package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
- package/dist/RLSEditor-CBX4uD66.js.map +0 -1
- package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
- package/dist/StorageView-BGDeC5GI.js.map +0 -1
- package/dist/components/StorageView/storage-failure.d.ts +0 -34
- package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
- package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
- package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
- package/src/components/ApiExplorer/parseSpec.ts +0 -104
- package/src/components/ApiExplorer/types.ts +0 -84
- package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
- package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
- package/src/components/ApiKeys/permissions.ts +0 -145
- package/src/components/AuthSimulationSelector.tsx +0 -72
- package/src/components/Backups/BackupsView.tsx +0 -185
- package/src/components/Branches/BranchesView.tsx +0 -386
- package/src/components/CronJobs/CronJobsView.tsx +0 -413
- package/src/components/JSEditor/JSEditor.tsx +0 -1100
- package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
- package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
- package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
- package/src/components/LogsExplorer/useLogTail.ts +0 -356
- package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
- package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
- package/src/components/RLSEditor/index.ts +0 -1
- package/src/components/RLSEditor/policy-presets.ts +0 -176
- package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
- package/src/components/RebaseStudio.tsx +0 -174
- package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
- package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
- package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
- package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
- package/src/components/SQLEditor/sql_editor_types.ts +0 -18
- package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
- package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
- package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
- package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
- package/src/components/StorageView/StorageView.tsx +0 -1588
- package/src/components/StorageView/storage-failure.ts +0 -63
- package/src/components/StudioHomePage.tsx +0 -430
- package/src/index.ts +0 -31
- package/src/utils/parseSpec.test.ts +0 -309
- package/src/utils/sql_utils.test.ts +0 -265
- package/src/utils/sql_utils.ts +0 -291
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ApiKeysView-DiCurTEU.js","names":[],"sources":["../src/components/ApiKeys/permissions.ts","../src/components/ApiKeys/CreateApiKeyDialog.tsx","../src/components/ApiKeys/ApiKeysView.tsx"],"sourcesContent":["/**\n * The vocabulary of an API key's permission entries.\n *\n * A permission entry is `{ collection, operations }`, but `collection` is not\n * only a collection: the same field addresses three namespaces, and the guard\n * that enforces them lives in `@rebasepro/server`\n * (`auth/api-keys/api-key-permission-guard.ts`):\n *\n * - `\"*\"` — every collection, every custom function, and storage\n * - `\"storage\"` — the storage routes\n * - `\"functions\"` — every custom function, including the function index\n * - `\"functions/<name>\"`— one named function\n * - anything else — the collection with that slug\n *\n * The UI used to label the field \"Collection slug or *\", which made two of\n * those namespaces undiscoverable and made `\"*\"` read as \"all collections\"\n * when it also hands over storage and every function. This module is the one\n * place that knows the mapping, so the picker, the row description, the grant\n * summary and the detail panel cannot drift from each other.\n *\n * @module\n */\n\nimport type { ApiKeyPermission } from \"@rebasepro/types\";\n\n/** Grants every collection, every function, and storage. */\nexport const RESOURCE_EVERYTHING = \"*\";\n/** Grants the storage routes. */\nexport const RESOURCE_STORAGE = \"storage\";\n/** Grants every custom function. */\nexport const RESOURCE_ALL_FUNCTIONS = \"functions\";\n/** Prefix addressing a single custom function. */\nexport const FUNCTION_PREFIX = \"functions/\";\n\nexport type ResourceKind =\n | \"everything\"\n | \"storage\"\n | \"all-functions\"\n | \"function\"\n | \"collection\";\n\nexport interface ParsedResource {\n kind: ResourceKind;\n /** The function name for `\"function\"`, the slug for `\"collection\"`. */\n name: string;\n}\n\n/** Classify a raw `collection` field into the namespace it addresses. */\nexport function parseResource(collection: string): ParsedResource {\n const value = collection.trim();\n if (value === RESOURCE_EVERYTHING) return { kind: \"everything\", name: \"\" };\n if (value === RESOURCE_STORAGE) return { kind: \"storage\", name: \"\" };\n if (value === RESOURCE_ALL_FUNCTIONS) return { kind: \"all-functions\", name: \"\" };\n if (value.startsWith(FUNCTION_PREFIX)) {\n return { kind: \"function\", name: value.slice(FUNCTION_PREFIX.length) };\n }\n return { kind: \"collection\", name: value };\n}\n\n/**\n * Short label for a resource — what a picker or a chip shows.\n *\n * Deliberately not the raw value: `\"*\"` alone is the thing nobody could read.\n */\nexport function resourceLabel(collection: string): string {\n const { kind, name } = parseResource(collection);\n switch (kind) {\n case \"everything\": return \"Everything\";\n case \"storage\": return \"Storage\";\n case \"all-functions\": return \"All functions\";\n case \"function\": return name ? `${name}()` : \"Function\";\n case \"collection\": return name || \"—\";\n }\n}\n\n/**\n * The resource as a sentence fragment, for \"this key can read <fragment>\".\n *\n * `\"everything\"` spells out all three namespaces, because that is exactly the\n * fact the old `*` input hid.\n */\nexport function resourcePhrase(collection: string): string {\n const { kind, name } = parseResource(collection);\n switch (kind) {\n case \"everything\": return \"every collection, every custom function and storage\";\n case \"storage\": return \"storage\";\n case \"all-functions\": return \"every custom function\";\n case \"function\": return name ? `the ${name} function` : \"one function\";\n case \"collection\": return name ? `the ${name} collection` : \"an unnamed collection\";\n }\n}\n\n/** How each operation reads for a given namespace, so the sentence stays true. */\nfunction operationPhrase(kind: ResourceKind, operation: string): string {\n if (kind === \"all-functions\" || kind === \"function\") {\n // Functions are invoked, not written to; the HTTP method still picks\n // the operation, so a POST-only function needs `write`.\n return operation === \"read\" ? \"call (GET)\"\n : operation === \"write\" ? \"call (POST, PUT, PATCH)\"\n : \"call (DELETE)\";\n }\n if (kind === \"storage\") {\n return operation === \"read\" ? \"download from\"\n : operation === \"write\" ? \"upload to\"\n : \"delete from\";\n }\n return operation;\n}\n\n/** Join with an Oxford-less \"and\", the way the rest of the panel reads. */\nfunction joinPhrases(parts: string[]): string {\n if (parts.length <= 1) return parts[0] ?? \"\";\n return `${parts.slice(0, -1).join(\", \")} and ${parts[parts.length - 1]}`;\n}\n\n/**\n * One plain sentence per permission entry: what the key will actually be able\n * to do. An entry with no operations selected grants nothing and says so,\n * rather than being silently dropped at submit time.\n */\nexport function grantSentence(perm: ApiKeyPermission): string {\n const { kind } = parseResource(perm.collection);\n const target = resourcePhrase(perm.collection);\n if (perm.operations.length === 0) return `No access to ${target}`;\n const verbs = joinPhrases(perm.operations.map(op => operationPhrase(kind, op)));\n const verb = verbs.charAt(0).toUpperCase() + verbs.slice(1);\n return `${verb} ${target}`;\n}\n\n/**\n * Dense one-line summary for list rows and the created-key confirmation.\n *\n * The wildcard wins over everything else in the array, because the guard\n * returns on the first match — a key holding `*` is a full-access key no\n * matter what else is listed beside it.\n */\nexport function permissionSummary(perms: ApiKeyPermission[]): string {\n if (perms.length === 0) return \"No permissions\";\n const wildcard = perms.find(p => p.collection === RESOURCE_EVERYTHING);\n if (wildcard) return `Everything (${wildcard.operations.join(\", \")})`;\n if (perms.length === 1) {\n return `${resourceLabel(perms[0].collection)} (${perms[0].operations.join(\", \")})`;\n }\n return `${perms.length} resources`;\n}\n","import React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport {\n AlertTriangleIcon,\n BooleanSwitchWithLabel,\n Button,\n ChevronsUpDownIcon,\n CircularProgress,\n cls,\n DatabaseIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FolderIcon,\n FunctionSquareIcon,\n GlobeIcon,\n iconSize,\n IconButton,\n PlusIcon,\n Select,\n SelectGroup,\n SelectItem,\n ShieldIcon,\n TextField,\n Tooltip,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\nimport {\n useApiBase,\n useApiConfig,\n useRebaseClient,\n useSnackbarController,\n useStudioCollectionRegistry\n} from \"@rebasepro/app\";\nimport type { ApiKeyPermission, ApiKeyWithSecret, RebaseClient } from \"@rebasepro/types\";\n\nimport {\n FUNCTION_PREFIX,\n grantSentence,\n parseResource,\n RESOURCE_ALL_FUNCTIONS,\n RESOURCE_EVERYTHING,\n RESOURCE_STORAGE,\n type ResourceKind\n} from \"./permissions\";\n\n/* ═══════════════════════════════════════════════════════════════\n Row model\n ═══════════════════════════════════════════════════════════════ */\n\ninterface PermissionRow {\n /** The raw wire value for `ApiKeyPermission.collection`. */\n resource: string;\n read: boolean;\n write: boolean;\n delete: boolean;\n /**\n * The row edits its resource as free text instead of through the picker.\n * Tracked separately from the value: a slug the panel has not registered\n * is still a legitimate grant, and without this flag the row would snap\n * back to the picker on the next render as soon as it was typed.\n */\n freeText: boolean;\n}\n\nconst OPERATIONS = [\"read\", \"write\", \"delete\"] as const;\ntype Operation = (typeof OPERATIONS)[number];\n\n/** What each operation actually permits, per namespace, shown on the toggle. */\nconst OPERATION_HINT: Record<ResourceKind, Record<Operation, string>> = {\n everything: {\n read: \"GET on every resource\",\n write: \"POST, PUT and PATCH on every resource\",\n delete: \"DELETE on every resource\"\n },\n collection: {\n read: \"List and get snapshots\",\n write: \"Create and update snapshots\",\n delete: \"Delete snapshots\"\n },\n storage: {\n read: \"List and download files\",\n write: \"Upload files and create folders\",\n delete: \"Delete files\"\n },\n \"all-functions\": {\n read: \"Call any function over GET\",\n write: \"Call any function over POST, PUT or PATCH\",\n delete: \"Call any function over DELETE\"\n },\n function: {\n read: \"Call it over GET\",\n write: \"Call it over POST, PUT or PATCH\",\n delete: \"Call it over DELETE\"\n }\n};\n\n/**\n * Picker value that switches a row to free text.\n *\n * Never reaches the wire — choosing it sets `freeText` and clears the resource\n * — so it only has to be a value no collection slug would take.\n */\nconst SENTINEL_FREE_TEXT = \"__custom__\";\n\nconst rowToPermission = (row: PermissionRow): ApiKeyPermission => ({\n collection: row.resource.trim(),\n operations: OPERATIONS.filter(op => row[op])\n});\n\nconst rowGrantsNothing = (row: PermissionRow): boolean => {\n const perm = rowToPermission(row);\n return !perm.collection || perm.operations.length === 0;\n};\n\nfunction ResourceIcon({ kind, className }: { kind: ResourceKind; className?: string }) {\n const Component = kind === \"everything\" ? GlobeIcon\n : kind === \"storage\" ? FolderIcon\n : kind === \"all-functions\" || kind === \"function\" ? FunctionSquareIcon\n : DatabaseIcon;\n return <Component size={iconSize.smallest} className={className}/>;\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Operation toggles\n\n Three states carried by one control each: off is neutral, on is tinted with\n the operation's own semantic colour. The previous version coloured the\n labels whether or not they were checked, so a read-only key still showed a\n red \"delete\" and the row read as more permissive than it was.\n ═══════════════════════════════════════════════════════════════ */\n\nconst OPERATION_STYLES: Record<Operation, { on: string; dot: string }> = {\n read: {\n on: \"bg-emerald-500/12 text-emerald-700 dark:text-emerald-300 ring-emerald-500/40\",\n dot: \"bg-emerald-500\"\n },\n write: {\n on: \"bg-blue-500/12 text-blue-700 dark:text-blue-300 ring-blue-500/40\",\n dot: \"bg-blue-500\"\n },\n delete: {\n on: \"bg-rose-500/12 text-rose-700 dark:text-rose-300 ring-rose-500/40\",\n dot: \"bg-rose-500\"\n }\n};\n\nfunction OperationToggles({\n row,\n onToggle\n }: {\n row: PermissionRow;\n onToggle: (operation: Operation, value: boolean) => void;\n}) {\n const kind = parseResource(row.resource).kind;\n return (\n <div className=\"flex items-center gap-1\" role=\"group\" aria-label=\"Allowed operations\">\n {OPERATIONS.map(op => {\n const active = row[op];\n const styles = OPERATION_STYLES[op];\n return (\n <Tooltip key={op} title={OPERATION_HINT[kind][op]} delayDuration={400}>\n <button\n type=\"button\"\n aria-pressed={active}\n onClick={() => onToggle(op, !active)}\n className={cls(\n \"flex items-center gap-1.5 h-7 pl-2 pr-2.5 rounded-md text-2xs font-medium\",\n \"ring-1 transition-colors duration-150 cursor-pointer\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\",\n active\n ? styles.on\n : \"bg-transparent ring-transparent text-surface-500 dark:text-surface-400 hover:bg-surface-accent-100 dark:hover:bg-surface-800\"\n )}\n >\n <span className={cls(\n \"w-1.5 h-1.5 rounded-full transition-colors duration-150\",\n active ? styles.dot : \"bg-surface-300 dark:bg-surface-600\"\n )}/>\n {op}\n </button>\n </Tooltip>\n );\n })}\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Section heading\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SectionLabel({ children, hint }: { children: React.ReactNode; hint?: React.ReactNode }) {\n return (\n <div className=\"flex items-baseline gap-2 mb-2\">\n <Typography\n variant=\"label\"\n className=\"text-2xs uppercase tracking-wider font-semibold text-surface-600 dark:text-surface-300\"\n gutterBottom={false}\n >\n {children}\n </Typography>\n {hint && (\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-2xs\" gutterBottom={false}>\n {hint}\n </Typography>\n )}\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Create API Key Dialog\n ═══════════════════════════════════════════════════════════════ */\n\nexport function CreateApiKeyDialog({\n onClose,\n onCreated\n }: {\n onClose: () => void;\n onCreated: (key: ApiKeyWithSecret) => void;\n}) {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const collectionRegistry = useStudioCollectionRegistry();\n const apiConfig = useApiConfig();\n const apiBase = useApiBase();\n\n const [name, setName] = useState(\"\");\n const [rows, setRows] = useState<PermissionRow[]>([\n { resource: RESOURCE_EVERYTHING, read: true, write: false, delete: false, freeText: false }\n ]);\n const [admin, setAdmin] = useState(false);\n const [rateLimit, setRateLimit] = useState(\"\");\n const [expiresIn, setExpiresIn] = useState(\"never\");\n const [creating, setCreating] = useState(false);\n\n /* The collections the panel already knows — the picker's main list. */\n const collections = useMemo(\n () => (collectionRegistry?.collections ?? [])\n .map(col => ({ slug: col.slug, name: col.name }))\n .filter(col => !!col.slug)\n .sort((a, b) => a.slug.localeCompare(b.slug)),\n [collectionRegistry?.collections]\n );\n\n /**\n * Deployed functions, so a single-function grant can be picked instead of\n * spelled `functions/<name>` from memory. Best-effort: this is an ordinary\n * API route, and a backend that serves no functions — or refuses the\n * request — just leaves the free-text escape to cover it.\n */\n const [functionNames, setFunctionNames] = useState<string[]>([]);\n useEffect(() => {\n if (!apiBase) return;\n let cancelled = false;\n (async () => {\n try {\n const token = await apiConfig?.getAuthToken?.();\n const res = await fetch(`${apiBase}/functions`, {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined\n });\n if (!res.ok) return;\n const body = await res.json() as { functions?: { name?: string }[] };\n if (cancelled) return;\n setFunctionNames((body.functions ?? [])\n .map(fn => fn.name)\n .filter((fnName): fnName is string => !!fnName));\n } catch {\n /* No listing available; the picker falls back to free text. */\n }\n })();\n return () => { cancelled = true; };\n }, [apiBase, apiConfig]);\n\n const updateRow = useCallback((idx: number, patch: Partial<PermissionRow>) => {\n setRows(current => current.map((row, i) => i === idx ? { ...row, ...patch } : row));\n }, []);\n\n const addRow = () => setRows(current => [\n ...current,\n {\n resource: collections[0]?.slug ?? \"\",\n read: true,\n write: false,\n delete: false,\n freeText: collections.length === 0\n }\n ]);\n\n const removeRow = (idx: number) => setRows(current => current.filter((_, i) => i !== idx));\n\n /* What would actually be sent. A row with no operations grants nothing;\n it used to be dropped at submit time with no indication it had been. */\n const effectivePermissions = useMemo(\n () => rows.map(rowToPermission).filter(perm => perm.collection && perm.operations.length > 0),\n [rows]\n );\n\n const droppedRows = rows.filter(rowGrantsNothing).length;\n const hasWildcard = rows.some(row => row.resource === RESOURCE_EVERYTHING && OPERATIONS.some(op => row[op]));\n const canSubmit = !!name.trim() && effectivePermissions.length > 0 && !creating;\n\n const handleCreate = async () => {\n if (!client?.apiKeys || !canSubmit) return;\n\n let expires_at: string | null = null;\n if (expiresIn === \"7d\") expires_at = new Date(Date.now() + 7 * 86400000).toISOString();\n else if (expiresIn === \"30d\") expires_at = new Date(Date.now() + 30 * 86400000).toISOString();\n else if (expiresIn === \"90d\") expires_at = new Date(Date.now() + 90 * 86400000).toISOString();\n else if (expiresIn === \"1y\") expires_at = new Date(Date.now() + 365 * 86400000).toISOString();\n\n setCreating(true);\n try {\n const res = await client.apiKeys.createKey({\n name: name.trim(),\n permissions: effectivePermissions,\n admin,\n rate_limit: rateLimit ? parseInt(rateLimit, 10) : null,\n expires_at\n });\n onCreated(res.key);\n } catch (e: unknown) {\n snackbar.open({ type: \"error\", message: e instanceof Error ? e.message : String(e) });\n } finally {\n setCreating(false);\n }\n };\n\n const submitBlockedReason = !name.trim()\n ? \"Name the key first\"\n : effectivePermissions.length === 0\n ? \"Grant at least one operation on one resource\"\n : \"\";\n\n return (\n <Dialog open onOpenChange={(open) => { if (!open && !creating) onClose(); }} maxWidth=\"2xl\">\n\n <DialogTitle variant=\"subtitle1\" gutterBottom={false} className=\"font-semibold\">\n Create API key\n </DialogTitle>\n\n <DialogContent includeMargin={false} className=\"px-8 pt-2 pb-4 flex flex-col gap-6\">\n\n <Typography variant=\"body2\" color=\"secondary\" gutterBottom={false} className=\"text-[13px] max-w-[62ch]\">\n A credential for scripts, cron jobs and agents. It authenticates as itself rather\n than as a user, and reaches only what you grant here.\n </Typography>\n\n <TextField\n label=\"Name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Analytics pipeline\"\n size=\"small\"\n autoFocus\n />\n\n {/* ── Access ── */}\n <div>\n <SectionLabel hint=\"What the key may call\">Access</SectionLabel>\n\n <div className={cls(\"rounded-lg border overflow-hidden\", defaultBorderMixin)}>\n {rows.map((row, idx) => {\n const parsed = parseResource(row.resource);\n return (\n <div\n key={idx}\n role=\"group\"\n aria-label={`Resource ${idx + 1}`}\n className={cls(\n \"flex flex-wrap items-center gap-x-3 gap-y-2 px-3 py-2.5\",\n idx > 0 && cls(\"border-t\", defaultBorderMixin)\n )}\n >\n <div className=\"flex items-center gap-2 flex-1 min-w-[15rem]\">\n <ResourceIcon\n kind={parsed.kind}\n className={cls(\n \"shrink-0\",\n parsed.kind === \"everything\"\n ? \"text-amber-600 dark:text-amber-400\"\n : \"text-surface-500 dark:text-surface-400\"\n )}\n />\n {row.freeText\n ? (\n <TextField\n size=\"small\"\n aria-label={`Resource ${idx + 1} name`}\n value={row.resource}\n onChange={(e) => updateRow(idx, { resource: e.target.value })}\n placeholder=\"collection slug, or functions/<name>\"\n className=\"flex-1\"\n endAdornment={\n <Tooltip title=\"Back to the list\">\n <IconButton\n size=\"smallest\"\n aria-label=\"Pick from the list instead\"\n onClick={() => updateRow(idx, {\n resource: collections[0]?.slug ?? RESOURCE_EVERYTHING,\n freeText: false\n })}\n >\n <ChevronsUpDownIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n }\n />\n )\n : (\n <Select\n size=\"small\"\n fullWidth\n className=\"flex-1\"\n aria-label={`Resource ${idx + 1}`}\n value={row.resource}\n position=\"popper\"\n onValueChange={(value) => {\n if (value === SENTINEL_FREE_TEXT) {\n updateRow(idx, { freeText: true, resource: \"\" });\n } else {\n updateRow(idx, { resource: value, freeText: false });\n }\n }}\n renderValue={(value) => (\n <span className=\"truncate\">\n {value === RESOURCE_EVERYTHING ? \"Everything\"\n : value === RESOURCE_STORAGE ? \"Storage\"\n : value === RESOURCE_ALL_FUNCTIONS ? \"All functions\"\n : String(value).startsWith(FUNCTION_PREFIX)\n ? `${String(value).slice(FUNCTION_PREFIX.length)}()`\n : String(value)}\n </span>\n )}\n >\n <SelectItem value={RESOURCE_EVERYTHING}>\n <div className=\"flex flex-col text-left\">\n <span>Everything</span>\n <span className=\"text-2xs text-text-secondary dark:text-text-secondary-dark\">\n Every collection, every function and storage\n </span>\n </div>\n </SelectItem>\n\n {collections.length > 0 && (\n <SelectGroup label=\"Collections\">\n {collections.map(col => (\n <SelectItem key={col.slug} value={col.slug}>\n <div className=\"flex flex-col text-left\">\n <span>{col.slug}</span>\n {col.name && col.name !== col.slug && (\n <span className=\"text-2xs text-text-secondary dark:text-text-secondary-dark\">\n {col.name}\n </span>\n )}\n </div>\n </SelectItem>\n ))}\n </SelectGroup>\n )}\n\n <SelectGroup label=\"Functions\">\n <SelectItem value={RESOURCE_ALL_FUNCTIONS}>All functions</SelectItem>\n {functionNames.map(fnName => (\n <SelectItem key={fnName} value={`${FUNCTION_PREFIX}${fnName}`}>\n {fnName}()\n </SelectItem>\n ))}\n </SelectGroup>\n\n <SelectGroup label=\"Storage\">\n <SelectItem value={RESOURCE_STORAGE}>Storage</SelectItem>\n </SelectGroup>\n\n <SelectGroup label=\"Other\">\n <SelectItem value={SENTINEL_FREE_TEXT}>Type a name…</SelectItem>\n </SelectGroup>\n </Select>\n )}\n </div>\n\n <OperationToggles\n row={row}\n onToggle={(operation, value) => updateRow(idx, { [operation]: value })}\n />\n\n <Tooltip title={rows.length > 1 ? \"Remove\" : \"At least one resource is required\"}>\n <span>\n <IconButton\n size=\"small\"\n disabled={rows.length === 1}\n onClick={() => removeRow(idx)}\n aria-label={`Remove resource ${idx + 1}`}\n >\n <Trash2Icon size={iconSize.smallest}/>\n </IconButton>\n </span>\n </Tooltip>\n </div>\n );\n })}\n </div>\n\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={addRow}\n className=\"mt-1.5\"\n startIcon={<PlusIcon size={iconSize.smallest}/>}\n >\n Add resource\n </Button>\n\n {/* Plain-language read-back of the grant being built. */}\n <div className={cls(\n \"mt-3 rounded-lg border px-3 py-2.5 bg-surface-accent-50 dark:bg-surface-900\",\n defaultBorderMixin\n )}>\n <Typography\n variant=\"label\"\n gutterBottom={false}\n className=\"text-2xs uppercase tracking-wider font-semibold text-surface-600 dark:text-surface-300\"\n >\n This key will be able to\n </Typography>\n {effectivePermissions.length === 0\n ? (\n <Typography\n variant=\"body2\"\n gutterBottom={false}\n className=\"mt-1.5 text-[13px] text-surface-500 dark:text-surface-400\"\n >\n Nothing yet — pick a resource and at least one operation.\n </Typography>\n )\n : (\n <ul className=\"mt-1.5 space-y-1\">\n {effectivePermissions.map((perm, idx) => (\n <li key={idx} className=\"flex items-start gap-2\">\n <ResourceIcon\n kind={parseResource(perm.collection).kind}\n className=\"mt-[3px] shrink-0 text-surface-500 dark:text-surface-400\"\n />\n <Typography variant=\"body2\" gutterBottom={false} className=\"text-[13px] leading-snug\">\n {grantSentence(perm)}\n </Typography>\n </li>\n ))}\n {admin && (\n <li className=\"flex items-start gap-2\">\n <ShieldIcon size={iconSize.smallest} className=\"mt-[3px] shrink-0 text-amber-600 dark:text-amber-400\"/>\n <Typography variant=\"body2\" gutterBottom={false} className=\"text-[13px] leading-snug\">\n Reach every admin route — users, roles, cron, backups, logs — and read\n through the <span className=\"font-mono text-2xs\">default_admin</span> policies\n </Typography>\n </li>\n )}\n </ul>\n )}\n {droppedRows > 0 && effectivePermissions.length > 0 && (\n <Typography\n variant=\"caption\"\n gutterBottom={false}\n className=\"block mt-2 text-2xs text-surface-500 dark:text-surface-400\"\n >\n {droppedRows === 1\n ? \"1 row grants nothing and will not be saved.\"\n : `${droppedRows} rows grant nothing and will not be saved.`}\n </Typography>\n )}\n </div>\n\n {hasWildcard && (\n <div className=\"flex items-start gap-2 mt-2 px-1\">\n <AlertTriangleIcon size={iconSize.smallest} className=\"mt-[3px] shrink-0 text-amber-600 dark:text-amber-400\"/>\n <Typography\n variant=\"caption\"\n gutterBottom={false}\n className=\"text-2xs text-amber-700 dark:text-amber-300 leading-snug max-w-[70ch]\"\n >\n <span className=\"font-semibold\">Everything</span> is the widest grant there is. It also\n covers collections and functions you add later, without this key being edited again.\n </Typography>\n </div>\n )}\n </div>\n\n {/* ── Admin role ── */}\n <div>\n <SectionLabel hint=\"Off for almost every key\">Admin role</SectionLabel>\n <div className={cls(\n \"rounded-lg border px-3 py-2.5 transition-colors duration-150\",\n admin ? \"border-amber-500/40 bg-amber-500/[0.06]\" : defaultBorderMixin\n )}>\n <BooleanSwitchWithLabel\n size=\"small\"\n position=\"start\"\n invisible\n value={admin}\n onValueChange={setAdmin}\n label={\n <div className=\"flex items-center gap-2\">\n <ShieldIcon\n size={iconSize.smallest}\n className={admin\n ? \"text-amber-600 dark:text-amber-400\"\n : \"text-surface-500 dark:text-surface-400\"}\n />\n <span className=\"text-[13px]\">Grant the admin role</span>\n </div>\n }\n />\n <Typography\n variant=\"caption\"\n color=\"secondary\"\n gutterBottom={false}\n className=\"block mt-1.5 text-2xs leading-snug max-w-[70ch]\"\n >\n {admin\n ? \"The key passes the admin-gated routes and reads through the default_admin RLS policies — far wider than the resources above. It still cannot manage API keys.\"\n : \"Without it the key carries only the service role, and RLS grants it nothing unless a collection policy names that role.\"}\n </Typography>\n </div>\n </div>\n\n {/* ── Limits ── */}\n <div>\n <SectionLabel>Limits</SectionLabel>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-3 items-end\">\n {/* Each control gets its own cell: `Select.Root` renders no DOM\n node, so an unwrapped Select puts its label and its control\n into two separate grid items. */}\n <div>\n <Select\n label=\"Expires\"\n value={expiresIn}\n onValueChange={setExpiresIn}\n size=\"small\"\n fullWidth\n position=\"popper\"\n renderValue={(v) =>\n v === \"never\" ? \"Never\"\n : v === \"7d\" ? \"In 7 days\"\n : v === \"30d\" ? \"In 30 days\"\n : v === \"90d\" ? \"In 90 days\" : \"In 1 year\"\n }\n >\n <SelectItem value=\"never\">Never</SelectItem>\n <SelectItem value=\"7d\">In 7 days</SelectItem>\n <SelectItem value=\"30d\">In 30 days</SelectItem>\n <SelectItem value=\"90d\">In 90 days</SelectItem>\n <SelectItem value=\"1y\">In 1 year</SelectItem>\n </Select>\n </div>\n <div>\n {/* Labelled above rather than floating inside, so it lines\n up with the Select beside it — a Select can only label\n above, and the two idioms in one row do not align. */}\n <label\n htmlFor=\"api-key-rate-limit\"\n className=\"block text-sm font-medium ml-3.5 mb-1 text-text-secondary dark:text-text-secondary-dark\"\n >\n Rate limit\n </label>\n <TextField\n id=\"api-key-rate-limit\"\n value={rateLimit}\n onChange={(e) => setRateLimit(e.target.value.replace(/\\D/g, \"\"))}\n placeholder=\"1000\"\n size=\"small\"\n endAdornment={\n <span className=\"text-2xs text-text-secondary dark:text-text-secondary-dark whitespace-nowrap\">\n / 15 min\n </span>\n }\n />\n </div>\n </div>\n <Typography\n variant=\"caption\"\n color=\"secondary\"\n gutterBottom={false}\n className=\"block mt-1.5 text-2xs\"\n >\n Leave the rate limit empty for the server default of 1000 requests per 15-minute window.\n </Typography>\n </div>\n\n </DialogContent>\n\n <DialogActions>\n <Button variant=\"text\" onClick={onClose} disabled={creating}>Cancel</Button>\n <Tooltip title={submitBlockedReason}>\n <span>\n <Button\n color=\"primary\"\n onClick={handleCreate}\n disabled={!canSubmit}\n startIcon={creating ? <CircularProgress size=\"smallest\"/> : undefined}\n >\n {creating ? \"Creating…\" : \"Create key\"}\n </Button>\n </span>\n </Tooltip>\n </DialogActions>\n </Dialog>\n );\n}\n","\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Button,\n Card,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n iconSize,\n KeyRoundIcon,\n RefreshCwIcon,\n ShieldIcon,\n Tooltip,\n Typography,\n CopyIcon,\n PlusIcon as AddIcon,\n Trash2Icon as DeleteIcon,\n AlertCircleIcon,\n CheckCircleIcon\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController } from \"@rebasepro/app\";\nimport type { ApiKeyMasked, ApiKeyWithSecret, RebaseClient } from \"@rebasepro/types\";\n\nimport { CreateApiKeyDialog } from \"./CreateApiKeyDialog\";\nimport { permissionSummary, resourceLabel, resourcePhrase } from \"./permissions\";\n\n/* ═══════════════════════════════════════════════════════════════\n Helpers\n\n The row types come from `@rebasepro/types`: this view used to declare its\n own copies, and they had already drifted — neither carried `admin`, so the\n panel could not tell an admin key from a scoped one.\n ═══════════════════════════════════════════════════════════════ */\n\nfunction formatRelative(iso: string | null | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const now = Date.now();\n const diff = d.getTime() - now;\n const abs = Math.abs(diff);\n if (abs < 60000) return diff > 0 ? \"in <1m\" : \"<1m ago\";\n if (abs < 3600000) { const m = Math.round(abs / 60000); return diff > 0 ? `in ${m}m` : `${m}m ago`; }\n if (abs < 86400000) { const h = Math.round(abs / 3600000); return diff > 0 ? `in ${h}h` : `${h}h ago`; }\n return d.toLocaleDateString();\n}\n\nfunction isExpired(key: ApiKeyMasked): boolean {\n return !!(key.expires_at && new Date(key.expires_at) < new Date());\n}\n\nfunction keyStatus(key: ApiKeyMasked): { label: string; color: string } {\n if (key.revoked_at) return { label: \"Revoked\", color: \"text-red-500\" };\n if (isExpired(key)) return { label: \"Expired\", color: \"text-amber-500\" };\n return { label: \"Active\", color: \"text-emerald-500\" };\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Main Component\n ═══════════════════════════════════════════════════════════════ */\n\nexport function ApiKeysView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const [keys, setKeys] = useState<ApiKeyMasked[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [showCreate, setShowCreate] = useState(false);\n const [showSecret, setShowSecret] = useState<ApiKeyWithSecret | null>(null);\n const [revoking, setRevoking] = useState<string | null>(null);\n const [confirmRevoke, setConfirmRevoke] = useState<ApiKeyMasked | null>(null);\n\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const loadKeys = useCallback(async () => {\n const c = clientRef.current;\n if (!c?.apiKeys) { setLoading(false); return; }\n try {\n const res = await c.apiKeys.listKeys();\n setKeys(res.keys);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => { loadKeys(); }, [loadKeys]);\n\n const handleRevoke = async (id: string) => {\n const c = clientRef.current;\n if (!c?.apiKeys) return;\n setRevoking(id);\n try {\n await c.apiKeys.revokeKey(id);\n snackbarRef.current.open({ type: \"success\", message: \"API key revoked\" });\n await loadKeys();\n if (selectedId === id) setSelectedId(null);\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\", message: e instanceof Error ? e.message : String(e) });\n } finally { setRevoking(null); }\n };\n\n const handleCreated = (keyWithSecret: ApiKeyWithSecret) => {\n setShowCreate(false);\n setShowSecret(keyWithSecret);\n loadKeys();\n };\n\n const selectedKey = keys.find(k => k.id === selectedId);\n const activeKeys = keys.filter(k => !k.revoked_at && !isExpired(k));\n const inactiveKeys = keys.filter(k => k.revoked_at || isExpired(k));\n\n if (loading) return <div className=\"flex items-center justify-center h-full\"><CircularProgress/></div>;\n\n return (\n <>\n <div className=\"flex h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* ── Key List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div 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)}>\n <div className=\"flex items-center gap-2\">\n <KeyRoundIcon size={iconSize.smallest} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">API Keys</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{activeKeys.length}</Chip>\n </div>\n <div className=\"flex items-center gap-1\">\n <IconButton size=\"small\" onClick={loadKeys} title=\"Refresh\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n <Button size=\"small\" color=\"primary\" onClick={() => setShowCreate(true)} startIcon={<AddIcon size={iconSize.smallest}/>}>\n New\n </Button>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {activeKeys.length === 0 && inactiveKeys.length === 0 && (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center p-6\">\n <KeyRoundIcon size={iconSize.medium} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"body2\" color=\"secondary\">No API keys yet</Typography>\n <Typography variant=\"caption\" color=\"disabled\">Create a key to enable machine-to-machine authentication</Typography>\n </div>\n )}\n {activeKeys.map(key => (\n <KeyListItem key={key.id} apiKey={key} selected={selectedId === key.id} onClick={() => setSelectedId(key.id)}/>\n ))}\n {inactiveKeys.length > 0 && (\n <>\n <div className=\"px-2 pt-3 pb-1\">\n <Typography variant=\"caption\" color=\"disabled\" className=\"text-[10px] uppercase tracking-wider font-medium\">Revoked / Expired</Typography>\n </div>\n {inactiveKeys.map(key => (\n <KeyListItem key={key.id} apiKey={key} selected={selectedId === key.id} onClick={() => setSelectedId(key.id)}/>\n ))}\n </>\n )}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selectedKey ? (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" color=\"disabled\">Select an API key to view details</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <KeyRoundIcon size={iconSize.small} className=\"text-primary shrink-0\"/>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selectedKey.name}</Typography>\n {selectedKey.admin && <AdminChip/>}\n </div>\n <Typography variant=\"caption\" color=\"secondary\" className=\"font-mono text-[11px]\">{selectedKey.key_prefix}•••</Typography>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {!selectedKey.revoked_at && (\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n onClick={() => setConfirmRevoke(selectedKey)}\n disabled={revoking === selectedKey.id}\n startIcon={revoking === selectedKey.id ? <CircularProgress size=\"smallest\"/> : <DeleteIcon size={iconSize.smallest}/>}\n >\n Revoke\n </Button>\n )}\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"px-5 py-4 bg-surface-50 dark:bg-surface-900/50\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Status\" value={keyStatus(selectedKey).label} className={keyStatus(selectedKey).color}/>\n <StatCard label=\"Created\" value={formatRelative(selectedKey.created_at)}/>\n <StatCard label=\"Last Used\" value={formatRelative(selectedKey.last_used_at)}/>\n <StatCard label=\"Expires\" value={selectedKey.expires_at ? formatRelative(selectedKey.expires_at) : \"Never\"}/>\n </div>\n <div className=\"grid grid-cols-2 md:grid-cols-3 gap-3 mt-3\">\n <StatCard\n label=\"Role\"\n value={selectedKey.admin ? \"Admin\" : \"Service\"}\n className={selectedKey.admin ? \"text-amber-600 dark:text-amber-400\" : undefined}\n />\n <StatCard label=\"Rate Limit\" value={selectedKey.rate_limit ? `${selectedKey.rate_limit}/15min` : \"Default (1000/15min)\"}/>\n <StatCard label=\"Created By\" value={selectedKey.created_by} mono/>\n </div>\n </div>\n\n {/* Permissions */}\n <div className={cls(\"flex items-center gap-2 px-5 py-2 border-y bg-white dark:bg-surface-950\", defaultBorderMixin)}>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">Permissions</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">\n {selectedKey.permissions.length}\n </Chip>\n </div>\n <div className=\"flex-1 overflow-y-auto px-5 py-3\">\n {selectedKey.admin && (\n <div className=\"flex items-start gap-2 mb-3 px-3 py-2 rounded-lg border border-amber-500/40 bg-amber-500/[0.06]\">\n <ShieldIcon size={iconSize.smallest} className=\"mt-[3px] shrink-0 text-amber-600 dark:text-amber-400\"/>\n <Typography variant=\"caption\" className=\"text-[12px] leading-snug text-amber-700 dark:text-amber-300\">\n This key holds the <span className=\"font-semibold\">admin role</span>: it also passes the\n admin-gated routes — users, roles, cron, backups, logs — and reads through the\n <span className=\"font-mono\"> default_admin</span> RLS policies, beyond the resources listed here.\n </Typography>\n </div>\n )}\n {selectedKey.permissions.length === 0 ? (\n <Typography variant=\"body2\" color=\"disabled\">No permissions configured</Typography>\n ) : (\n <div className=\"space-y-2\">\n {selectedKey.permissions.map((perm, idx) => (\n <div key={idx} className={cls(\"flex items-center gap-3 px-3 py-2 rounded-lg border\", defaultBorderMixin)}>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"text-[13px] font-medium truncate\">\n {resourceLabel(perm.collection)}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[11px]\">\n {resourcePhrase(perm.collection)}\n </Typography>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n {perm.operations.map(op => (\n <Chip key={op} size=\"smallest\" className={cls(\n op === \"read\" && \"bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300\",\n op === \"write\" && \"bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300\",\n op === \"delete\" && \"bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300\"\n )}>{op}</Chip>\n ))}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n\n {/* Revoke Confirmation Dialog */}\n <Dialog\n open={confirmRevoke !== null}\n onOpenChange={(open) => {\n if (!open && !revoking) setConfirmRevoke(null);\n }}\n >\n <DialogTitle hidden>Revoke Confirmation</DialogTitle>\n <DialogContent>\n <Typography variant=\"subtitle1\" className=\"font-semibold mb-2\">\n Revoke "{confirmRevoke?.name}"?\n </Typography>\n <Typography variant=\"body2\" color=\"secondary\">\n Requests authenticated with this key will stop working immediately. This action cannot be undone.\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"text\"\n onClick={() => setConfirmRevoke(null)}\n disabled={revoking !== null}\n >\n Cancel\n </Button>\n <Button\n color=\"error\"\n disabled={revoking !== null}\n startIcon={revoking !== null ? <CircularProgress size=\"smallest\"/> : <DeleteIcon size={iconSize.smallest}/>}\n onClick={async () => {\n if (!confirmRevoke) return;\n await handleRevoke(confirmRevoke.id);\n setConfirmRevoke(null);\n }}\n >\n Revoke\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* Create Dialog */}\n {showCreate && (\n <CreateApiKeyDialog\n onClose={() => setShowCreate(false)}\n onCreated={handleCreated}\n />\n )}\n\n {/* Secret Display Dialog */}\n {showSecret && (\n <SecretDisplayDialog\n keyWithSecret={showSecret}\n onClose={() => setShowSecret(null)}\n />\n )}\n </>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n List item\n ═══════════════════════════════════════════════════════════════ */\n\n/**\n * Marks a key that carries the admin role.\n *\n * Not cosmetic: an admin key reaches the admin routes and the `default_admin`\n * RLS policies, and without this it is indistinguishable in the list from a\n * read-only one.\n */\nfunction AdminChip() {\n return (\n <Tooltip title=\"Carries the admin role: the admin-gated routes and the default_admin RLS policies\">\n <Chip\n size=\"smallest\"\n className=\"shrink-0 bg-amber-500/12 dark:bg-amber-500/12 text-amber-700 dark:text-amber-300 border-amber-500/30 dark:border-amber-500/30\"\n >\n <ShieldIcon size={10}/>\n admin\n </Chip>\n </Tooltip>\n );\n}\n\nfunction KeyListItem({ apiKey, selected, onClick }: { apiKey: ApiKeyMasked; selected: boolean; onClick: () => void }) {\n const status = keyStatus(apiKey);\n return (\n <div\n onClick={onClick}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selected\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\",\n status.label === \"Active\" ? \"bg-emerald-400\" :\n status.label === \"Expired\" ? \"bg-amber-400\" : \"bg-red-400\"\n )}/>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-1.5 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{apiKey.name}</Typography>\n {apiKey.admin && <AdminChip/>}\n </div>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px] font-mono\">{apiKey.key_prefix}•••</Typography>\n </div>\n <div className=\"shrink-0\">\n <Typography variant=\"caption\" color=\"disabled\" className=\"text-[10px]\">{permissionSummary(apiKey.permissions)}</Typography>\n </div>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Stat card\n ═══════════════════════════════════════════════════════════════ */\n\nfunction StatCard({ label, value, mono, className }: { label: string; value: string; mono?: boolean; className?: string }) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px]\",\n mono && \"font-mono\",\n className\n )}>{value}</Typography>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Secret Display Dialog — shown exactly once after creation\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SecretDisplayDialog({ keyWithSecret, onClose }: { keyWithSecret: ApiKeyWithSecret; onClose: () => void }) {\n const snackbar = useSnackbarController();\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(keyWithSecret.key);\n setCopied(true);\n snackbar.open({ type: \"success\", message: \"API key copied to clipboard\" });\n setTimeout(() => setCopied(false), 2000);\n } catch {\n snackbar.open({ type: \"error\", message: \"Failed to copy\" });\n }\n };\n\n return (\n <Dialog open onOpenChange={(open) => { if (!open) onClose(); }} maxWidth=\"md\">\n <DialogTitle>\n <div className=\"flex items-center gap-2\">\n <CheckCircleIcon size={iconSize.small} className=\"text-emerald-500\"/>\n API Key Created\n </div>\n </DialogTitle>\n <DialogContent>\n <div className=\"p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 mb-4\">\n <div className=\"flex items-center gap-2 mb-1\">\n <AlertCircleIcon size={iconSize.smallest} className=\"text-amber-600 dark:text-amber-400\"/>\n <Typography variant=\"caption\" className=\"font-semibold text-amber-700 dark:text-amber-400\">\n Copy your key now — it won't be shown again\n </Typography>\n </div>\n <Typography variant=\"caption\" className=\"text-amber-600 dark:text-amber-300\">\n This is the only time the full API key will be displayed. Store it securely.\n </Typography>\n </div>\n\n <div className={cls(\"flex items-center gap-2 p-3 rounded-lg border bg-surface-50 dark:bg-surface-900\", defaultBorderMixin)}>\n <code className=\"flex-1 text-[12px] font-mono break-all text-surface-700 dark:text-surface-300 select-all\">\n {keyWithSecret.key}\n </code>\n <Tooltip title={copied ? \"Copied!\" : \"Copy\"}>\n <IconButton size=\"small\" onClick={handleCopy}>\n {copied\n ? <CheckCircleIcon size={iconSize.smallest} className=\"text-emerald-500\"/>\n : <CopyIcon size={iconSize.smallest}/>\n }\n </IconButton>\n </Tooltip>\n </div>\n\n <div className=\"mt-4 space-y-1\">\n <Typography variant=\"caption\" color=\"secondary\">\n <strong>Name:</strong> {keyWithSecret.name}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\">\n <strong>Access:</strong> {permissionSummary(keyWithSecret.permissions)}\n </Typography>\n {keyWithSecret.admin && (\n <Typography variant=\"caption\" className=\"flex items-center gap-1.5 text-amber-700 dark:text-amber-300\">\n <ShieldIcon size={iconSize.smallest} className=\"shrink-0\"/>\n <span><strong>Admin role granted</strong> — the admin routes and the default_admin policies</span>\n </Typography>\n )}\n </div>\n </DialogContent>\n <DialogActions>\n <Button color=\"primary\" onClick={onClose}>Done</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BackupsView-DNS6LdVg.js","names":[],"sources":["../src/components/Backups/BackupsView.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n DatabaseIcon,\n defaultBorderMixin,\n DownloadIcon,\n iconSize,\n IconButton,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController } from \"@rebasepro/app\";\nimport type { BackupInfo, RebaseClient } from \"@rebasepro/types\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatDate(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n if (isNaN(d.getTime())) return \"—\";\n return d.toLocaleString();\n}\n\nexport function BackupsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n\n const [backups, setBackups] = useState<BackupInfo[]>([]);\n const [destinationKind, setDestinationKind] = useState<string>(\"local\");\n const [configured, setConfigured] = useState(true);\n const [loading, setLoading] = useState(true);\n const [downloading, setDownloading] = useState<string | null>(null);\n\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const load = useCallback(async () => {\n const c = clientRef.current;\n if (!c?.backups) {\n setLoading(false);\n setConfigured(false);\n return;\n }\n try {\n const res = await c.backups.list();\n setBackups(res.backups);\n setDestinationKind(res.destinationKind);\n setConfigured(res.configured);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n load();\n }, [load]);\n\n const handleDownload = async (backup: BackupInfo) => {\n const c = clientRef.current;\n if (!c?.backups) return;\n setDownloading(backup.key);\n try {\n const blob = await c.backups.download(backup.key);\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = backup.name;\n document.body.appendChild(a);\n a.click();\n a.remove();\n URL.revokeObjectURL(url);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDownloading(null);\n }\n };\n\n // Backups API not available on this client (e.g. non-Postgres backend).\n if (!client?.backups) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">Backups Not Available</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n The backups API is not exposed by this backend.\n </Typography>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <DatabaseIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">Backups</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{backups.length}</Chip>\n <Chip size=\"smallest\" className=\"bg-surface-100 dark:bg-surface-800 text-surface-500 dark:text-surface-400 uppercase font-mono text-[10px]\">{destinationKind}</Chip>\n </div>\n <IconButton size=\"small\" onClick={load} title=\"Refresh\">\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n\n <div className=\"flex-1 overflow-y-auto p-5\">\n {!configured ? (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>Backups are not configured.</strong> Set\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]\">BACKUP_DESTINATION</code>\n (a local path or an <code className=\"font-mono text-[12px]\">s3://</code> / <code className=\"font-mono text-[12px]\">gs://</code> URL) and create backups with\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]\">rebase db backup</code>\n or a scheduled cron. See docs/backups.md.\n </Typography>\n </Alert>\n ) : backups.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n No backups found yet. Run <code className=\"font-mono text-[12px]\">rebase db backup</code> or wait for the next scheduled run.\n </Typography>\n </div>\n ) : (\n <div className=\"space-y-2 max-w-3xl\">\n {backups.map(backup => (\n <div\n key={backup.key}\n className={cls(\"flex items-center gap-3 px-4 py-3 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}\n >\n <DatabaseIcon size={iconSize.small} className=\"text-surface-400 shrink-0\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium font-mono text-[12px]\">{backup.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[11px]\">\n {formatDate(backup.createdAt)} · {formatSize(backup.sizeBytes)}\n </Typography>\n </div>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => handleDownload(backup)}\n disabled={downloading === backup.key}\n startIcon={downloading === backup.key\n ? <CircularProgress size=\"smallest\"/>\n : <DownloadIcon size={iconSize.smallest}/>}\n >\n {downloading === backup.key ? \"Downloading…\" : \"Download\"}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BranchesView-CM3B-ER3.js","names":[],"sources":["../src/components/Branches/BranchesView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n CopyIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n GitBranchIcon,\n IconButton,\n iconSize,\n Paper,\n PlusIcon,\n RefreshCwIcon,\n Select,\n SelectItem,\n TextField,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseContext, useSnackbarController, ConfirmationDialog } from \"@rebasepro/app\";\nimport { isBranchAdmin } from \"@rebasepro/types\";\nimport type { BranchInfo } from \"@rebasepro/types\";\nimport { formatRelativeTime } from \"@rebasepro/utils\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatRelative(date: Date | string | undefined): string {\n if (!date) return \"—\";\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return \"—\";\n return formatRelativeTime(d) ?? d.toLocaleDateString();\n}\n\nexport function BranchesView() {\n const { databaseAdmin } = useRebaseContext();\n const snackbar = useSnackbarController();\n\n const [branches, setBranches] = useState<BranchInfo[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedBranch, setSelectedBranch] = useState<string | null>(null);\n\n // Create dialog\n const [createOpen, setCreateOpen] = useState(false);\n const [newBranchName, setNewBranchName] = useState(\"\");\n const [sourceBranch, setSourceBranch] = useState<string | undefined>(undefined);\n const [creating, setCreating] = useState(false);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n // Refs\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : undefined;\n\n const loadBranches = useCallback(async () => {\n if (!branchAdmin) {\n setLoading(false);\n return;\n }\n try {\n const result = await branchAdmin.listBranches();\n setBranches(result);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setLoading(false);\n }\n }, [branchAdmin]);\n\n useEffect(() => {\n loadBranches();\n }, [loadBranches]);\n\n const handleCreate = async () => {\n if (!branchAdmin || !newBranchName.trim()) return;\n setCreating(true);\n try {\n await branchAdmin.createBranch(newBranchName.trim(), sourceBranch ? { source: sourceBranch } : undefined);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${newBranchName.trim()}\" created successfully`\n });\n setCreateOpen(false);\n setNewBranchName(\"\");\n setSourceBranch(undefined);\n await loadBranches();\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setCreating(false);\n }\n };\n\n const handleDelete = async () => {\n if (!branchAdmin || !deleteTarget) return;\n setDeleting(true);\n try {\n await branchAdmin.deleteBranch(deleteTarget);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${deleteTarget}\" deleted`\n });\n if (selectedBranch === deleteTarget) setSelectedBranch(null);\n setDeleteTarget(null);\n await loadBranches();\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDeleting(false);\n }\n };\n\n // Not supported\n if (!branchAdmin) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">Database Branching Not Available</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n Branching requires a PostgreSQL backend with an admin connection configured.\n Set <code className=\"text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono\">adminConnectionString</code> in your server configuration.\n </Typography>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n const selected = branches.find(b => b.name === selectedBranch);\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* ── Branch List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div 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)}>\n <div className=\"flex items-center gap-2\">\n <GitBranchIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">Branches</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{branches.length}</Chip>\n </div>\n <div className=\"flex items-center gap-1\">\n <IconButton size=\"small\" onClick={loadBranches} title=\"Refresh\">\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n <IconButton size=\"small\" onClick={() => setCreateOpen(true)} title=\"Create branch\" className=\"text-primary\">\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {branches.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center px-4\">\n <CopyIcon size={iconSize.small} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"body2\" color=\"disabled\" className=\"text-[13px]\">\n No branches yet. Create one to start working with an isolated database copy.\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => setCreateOpen(true)}\n startIcon={<PlusIcon size={iconSize.smallest}/>}\n >\n Create Branch\n </Button>\n </div>\n ) : (\n branches.map(branch => (\n <div\n key={branch.name}\n onClick={() => setSelectedBranch(branch.name)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedBranch === branch.name\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950\"\n )}\n >\n <div className=\"w-2 h-2 rounded-full shrink-0 bg-emerald-500\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{branch.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px]\">\n from {branch.parentDatabase} · {formatRelative(branch.createdAt)}\n </Typography>\n </div>\n {branch.sizeBytes !== undefined && (\n <Typography variant=\"caption\" color=\"disabled\" className=\"font-mono text-[10px] shrink-0\">\n {formatSize(branch.sizeBytes)}\n </Typography>\n )}\n </div>\n ))\n )}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selected ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n {branches.length === 0 ? \"Create a branch to get started\" : \"Select a branch to view details\"}\n </Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-emerald-500\"/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selected.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">\n Created from <span className=\"font-mono text-[11px]\">{selected.parentDatabase}</span>\n </Typography>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n onClick={() => setDeleteTarget(selected.name)}\n startIcon={<Trash2Icon size={iconSize.smallest}/>}\n >\n Delete\n </Button>\n </div>\n </div>\n\n {/* Info Cards */}\n <div className=\"px-5 py-4 bg-surface-50 dark:bg-surface-900/50\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Branch Name\" value={selected.name} mono/>\n <StatCard label=\"Source Database\" value={selected.parentDatabase} mono/>\n <StatCard label=\"Created\" value={formatRelative(selected.createdAt)}/>\n <StatCard label=\"Size\" value={formatSize(selected.sizeBytes)}/>\n </div>\n </div>\n\n {/* Usage Info */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>How to use this branch:</strong> Switch your application's database connection to\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]\">{selected.name}</code>\n to work with an isolated copy of your data. Changes made to this branch won't affect your main database.\n </Typography>\n </Alert>\n <div className=\"mt-4 p-4 rounded-lg border bg-surface-50 dark:bg-surface-900 border-surface-200 dark:border-surface-700\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium\">Connection Details</Typography>\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Database:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{selected.name}</Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Branched from:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{selected.parentDatabase}</Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Created at:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">\n {selected.createdAt instanceof Date\n ? selected.createdAt.toLocaleString()\n : new Date(selected.createdAt).toLocaleString()}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n {/* ── Create Dialog ── */}\n <Dialog open={createOpen} onOpenChange={setCreateOpen}>\n <DialogTitle>Create New Branch</DialogTitle>\n <DialogContent className=\"space-y-4 min-w-[400px]\">\n <Typography variant=\"body2\" color=\"secondary\" className=\"text-[13px]\">\n Create an isolated database copy. The branch will be a full clone of the source database at this point in time.\n </Typography>\n <div>\n <TextField\n label=\"Branch Name\"\n value={newBranchName}\n onChange={(e) => setNewBranchName(e.target.value)}\n placeholder=\"e.g. feature-auth, staging, preview-pr-42\"\n size=\"small\"\n autoFocus\n />\n </div>\n {branches.length > 0 && (\n <div>\n <Select\n label=\"Source Database\"\n value={sourceBranch ?? \"__main__\"}\n onValueChange={(v) => setSourceBranch(v === \"__main__\" ? undefined : v)}\n placeholder=\"Default (main database)\"\n size=\"small\"\n >\n <SelectItem value=\"__main__\">Default (main database)</SelectItem>\n {branches.map(b => (\n <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>\n ))}\n </Select>\n </div>\n )}\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setCreateOpen(false)} disabled={creating}>\n Cancel\n </Button>\n <Button\n color=\"primary\"\n onClick={handleCreate}\n disabled={!newBranchName.trim() || creating}\n startIcon={creating ? <CircularProgress size=\"smallest\"/> : <PlusIcon size={iconSize.smallest}/>}\n >\n {creating ? \"Creating...\" : \"Create Branch\"}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* ── Delete Confirm ── */}\n <ConfirmationDialog\n open={!!deleteTarget}\n onAccept={handleDelete}\n onCancel={() => setDeleteTarget(null)}\n title=\"Delete Branch\"\n body={\n <Typography variant=\"body2\">\n Are you sure you want to permanently delete the branch <strong>{deleteTarget}</strong>?\n This action cannot be undone and all data in this branch will be lost.\n </Typography>\n }\n loading={deleting}\n />\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono }: {\n label: string; value: string; mono?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px] truncate\",\n mono && \"font-mono\"\n )}>{value}</Typography>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CronJobsView-BHtJZeJZ.js","names":[],"sources":["../src/components/CronJobs/CronJobsView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef } from \"react\";\nimport {\n AlertCircleIcon,\n Button,\n CalendarIcon,\n Card,\n CheckCircleIcon,\n Chip,\n CircularProgress,\n cls,\n defaultBorderMixin,\n HistoryIcon,\n IconButton,\n iconSize,\n Paper,\n PauseIcon,\n PlayIcon,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController } from \"@rebasepro/app\";\nimport type { CronJobStatus, CronJobLogEntry } from \"@rebasepro/types\";\nimport type { RebaseClient } from \"@rebasepro/types\";\n\nfunction formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;\n return `${(ms / 60000).toFixed(1)}m`;\n}\n\nfunction formatRelative(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n const now = Date.now();\n const diff = d.getTime() - now;\n const abs = Math.abs(diff);\n if (abs < 60000) return diff > 0 ? \"in <1m\" : \"<1m ago\";\n if (abs < 3600000) { const m = Math.round(abs / 60000); return diff > 0 ? `in ${m}m` : `${m}m ago`; }\n if (abs < 86400000) { const h = Math.round(abs / 3600000); return diff > 0 ? `in ${h}h` : `${h}h ago`; }\n return d.toLocaleString();\n}\n\nconst stateColors: Record<string, string> = {\n idle: \"bg-emerald-500\",\nrunning: \"bg-blue-500\",\nsuccess: \"bg-emerald-500\",\n error: \"bg-red-500\",\ndisabled: \"bg-surface-400\"\n};\n\nexport function CronJobsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const [jobs, setJobs] = useState<CronJobStatus[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [logs, setLogs] = useState<CronJobLogEntry[]>([]);\n const [logsLoading, setLogsLoading] = useState(false);\n const [triggering, setTriggering] = useState<string | null>(null);\n\n // Refs so effects never re-fire due to identity changes\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n // ── Fetch jobs on mount + poll every 15s ──\n useEffect(() => {\n let cancelled = false;\n\n async function load() {\n const c = clientRef.current;\n if (!c?.cron) {\n setLoading(false);\n return;\n }\n try {\n const res = await c.cron.listJobs();\n if (!cancelled) setJobs(res.jobs);\n } catch (e: unknown) {\n if (!cancelled) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n }\n } finally {\n if (!cancelled) setLoading(false);\n }\n }\n\n load();\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const scheduleNext = () => {\n if (cancelled) return;\n timeoutId = setTimeout(async () => {\n if (document.visibilityState === \"visible\") {\n await load();\n }\n scheduleNext();\n }, 15_000);\n };\n\n scheduleNext();\n\n const handleVisibility = () => {\n if (document.visibilityState === \"visible\") {\n load();\n }\n };\n document.addEventListener(\"visibilitychange\", handleVisibility);\n\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n document.removeEventListener(\"visibilitychange\", handleVisibility);\n };\n }, []); // runs once\n\n // ── Fetch logs when selection changes ──\n useEffect(() => {\n if (!selectedId) {\n setLogs([]);\n return;\n }\n let cancelled = false;\n const c = clientRef.current;\n if (!c?.cron) return;\n\n setLogsLoading(true);\n c.cron.getJobLogs(selectedId, { limit: 25 })\n .then(res => { if (!cancelled) setLogs(res.logs); })\n .catch((e: unknown) => {\n if (cancelled) return;\n // Same reasoning as `refreshLogs`: an empty log list is a claim.\n setLogs([]);\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n })\n .finally(() => { if (!cancelled) setLogsLoading(false); });\n\n return () => { cancelled = true; };\n }, [selectedId]);\n\n // ── Imperative helpers (not in any dep array) ──\n async function refreshJobs() {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n const res = await c.cron.listJobs();\n setJobs(res.jobs);\n } catch (e: unknown) {\n // Swallowed before, which left the list showing whatever it last\n // held — or nothing — after a failed refresh. \"No cron jobs\" and\n // \"could not read the cron jobs\" are not the same statement, and\n // this view has a snackbar precisely so they can be told apart; the\n // initial load already uses it.\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n }\n }\n\n async function refreshLogs(id: string) {\n const c = clientRef.current;\n if (!c?.cron) return;\n setLogsLoading(true);\n try {\n const res = await c.cron.getJobLogs(id, { limit: 25 });\n setLogs(res.logs);\n } catch (e: unknown) {\n // Clearing the list silently reads as \"this job has never run\".\n setLogs([]);\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n }\n finally { setLogsLoading(false); }\n }\n\n const handleTrigger = async (id: string) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n setTriggering(id);\n try {\n await c.cron.triggerJob(id);\n snackbarRef.current.open({ type: \"success\",\nmessage: \"Job triggered\" });\n await refreshJobs();\n if (selectedId === id) refreshLogs(id);\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally { setTriggering(null); }\n };\n\n const handleToggle = async (id: string, enabled: boolean) => {\n const c = clientRef.current;\n if (!c?.cron) return;\n try {\n await c.cron.toggleJob(id, enabled);\n snackbarRef.current.open({ type: \"success\",\nmessage: enabled ? \"Job enabled\" : \"Job paused\" });\n await refreshJobs();\n } catch (e: unknown) {\n snackbarRef.current.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n }\n };\n\n const selectedJob = jobs.find(j => j.id === selectedId);\n\n if (loading) return <div className=\"flex items-center justify-center h-full\"><CircularProgress/></div>;\n\n if (jobs.length === 0) return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <CalendarIcon size={iconSize.medium} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">No Cron Jobs Registered</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n Create a file in your <code className=\"text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono\">crons/</code> directory that default-exports a <code className=\"text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono\">CronJobDefinition</code>.\n </Typography>\n </div>\n );\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* ── Job List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div 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)}>\n <div className=\"flex items-center gap-2\">\n <CalendarIcon size={iconSize.smallest} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">Cron Jobs</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{jobs.length}</Chip>\n </div>\n <IconButton size=\"small\" onClick={refreshJobs} title=\"Refresh\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {jobs.map(job => (\n <div\n key={job.id}\n onClick={() => setSelectedId(job.id)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedId === job.id\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\", stateColors[job.state] || \"bg-surface-400\")}/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{job.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px] font-mono\">{job.schedule}</Typography>\n </div>\n {job.state === \"running\" && <CircularProgress size=\"smallest\"/>}\n </div>\n ))}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selectedJob ? (\n <div className=\"flex items-center justify-center h-full\">\n <Typography variant=\"body2\" color=\"disabled\">Select a cron job to view details</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className={cls(\"w-2.5 h-2.5 rounded-full\", stateColors[selectedJob.state])}/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selectedJob.name}</Typography>\n {selectedJob.description && <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">{selectedJob.description}</Typography>}\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <IconButton title={selectedJob.enabled ? \"Pause job\" : \"Enable job\"} size=\"small\" onClick={() => handleToggle(selectedJob.id, !selectedJob.enabled)}>\n {selectedJob.enabled ? <PauseIcon size={iconSize.small}/> : <PlayIcon size={iconSize.smallest}/>}\n </IconButton>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => handleTrigger(selectedJob.id)}\n disabled={triggering === selectedJob.id}\n startIcon={triggering === selectedJob.id ? <CircularProgress size=\"smallest\"/> : <PlayIcon size={iconSize.smallest}/>}\n >\n Run Now\n </Button>\n </div>\n </div>\n\n {/* Stats Cards */}\n <div className=\"px-5 py-4 bg-surface-50 dark:bg-surface-900/50\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Schedule\" value={selectedJob.schedule} mono/>\n <StatCard label=\"Last Run\" value={formatRelative(selectedJob.lastRunAt)}/>\n <StatCard label=\"Next Run\" value={selectedJob.enabled ? formatRelative(selectedJob.nextRunAt) : \"Paused\"}/>\n <StatCard label=\"Duration\" value={selectedJob.lastDurationMs !== undefined ? formatDuration(selectedJob.lastDurationMs) : \"—\"}/>\n </div>\n <div className=\"grid grid-cols-3 gap-3 mt-3\">\n <StatCard label=\"Status\" value={selectedJob.state.toUpperCase()} chipColor={selectedJob.state === \"error\" ? \"red\" : selectedJob.state === \"disabled\" ? \"gray\" : \"green\"}/>\n <StatCard label=\"Total Runs\" value={String(selectedJob.totalRuns)}/>\n <StatCard label=\"Failures\" value={String(selectedJob.totalFailures)} highlight={selectedJob.totalFailures > 0}/>\n </div>\n {selectedJob.lastError && (\n <div className=\"mt-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <div className=\"flex items-center gap-2 mb-1\">\n <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500\"/>\n <Typography variant=\"caption\" className=\"font-semibold text-red-700 dark:text-red-400\">Last Error</Typography>\n </div>\n <Typography variant=\"caption\" className=\"font-mono text-red-600 dark:text-red-300 text-[11px] break-all\">{selectedJob.lastError}</Typography>\n </div>\n )}\n </div>\n\n {/* Logs Section */}\n <div className={cls(\"flex items-center justify-between px-5 py-2 border-y bg-white dark:bg-surface-950\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <HistoryIcon size={iconSize.smallest} className=\"text-surface-400\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">Execution History</Typography>\n </div>\n <IconButton size=\"small\" onClick={() => refreshLogs(selectedJob.id)} title=\"Refresh logs\"><RefreshCwIcon size={iconSize.smallest}/></IconButton>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n {logsLoading ? (\n <div className=\"flex justify-center p-8\"><CircularProgress size=\"small\"/></div>\n ) : logs.length === 0 ? (\n <div className=\"flex items-center justify-center h-32\">\n <Typography variant=\"body2\" color=\"disabled\">No executions yet</Typography>\n </div>\n ) : (\n <div className=\"divide-y divide-surface-100 dark:divide-surface-950\">\n {logs.map((log, idx) => (\n <LogRow key={idx} log={log}/>\n ))}\n </div>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono, chipColor, highlight }: {\n label: string; value: string; mono?: boolean; chipColor?: string; highlight?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px]\",\n mono && \"font-mono\",\n highlight && \"text-red-500 dark:text-red-400\",\n chipColor === \"red\" && \"text-red-500\",\n chipColor === \"green\" && \"text-emerald-500\",\n chipColor === \"gray\" && \"text-surface-400\"\n )}>{value}</Typography>\n </div>\n );\n}\n\nfunction LogRow({ log }: { log: CronJobLogEntry }) {\n const [expanded, setExpanded] = useState(false);\n return (\n <div className=\"px-5 py-2.5 hover:bg-surface-50 dark:hover:bg-surface-950/50 transition-colors\">\n <div className=\"flex items-center gap-3 cursor-pointer\" onClick={() => setExpanded(!expanded)}>\n {log.success\n ? <CheckCircleIcon size={iconSize.smallest} className=\"text-emerald-500 shrink-0\"/>\n : <AlertCircleIcon size={iconSize.smallest} className=\"text-red-500 shrink-0\"/>}\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-surface-500\">{new Date(log.startedAt).toLocaleString()}</Typography>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n {log.manual && <Chip size=\"smallest\" className=\"bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300\">manual</Chip>}\n <Typography variant=\"caption\" className=\"font-mono text-[11px]\">{formatDuration(log.durationMs)}</Typography>\n <svg className={cls(\"w-3 h-3 transition-transform text-surface-400\", expanded && \"rotate-180\")} fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"/></svg>\n </div>\n </div>\n {expanded && (\n <div className=\"mt-2 ml-6 space-y-2\">\n {log.error && (\n <div className=\"p-2 rounded bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800/50\">\n <Typography variant=\"caption\" className=\"font-mono text-[11px] text-red-600 dark:text-red-300 break-all\">{log.error}</Typography>\n </div>\n )}\n {log.logs.length > 0 && (\n <div className=\"p-2 rounded bg-surface-50 dark:bg-surface-900 border border-surface-200 dark:border-surface-700 max-h-40 overflow-auto\">\n {log.logs.map((line, i) => (\n <div key={i} className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 leading-relaxed\">{line}</div>\n ))}\n </div>\n )}\n {log.result !== undefined && (\n <div className=\"p-2 rounded bg-surface-50 dark:bg-surface-900 border border-surface-200 dark:border-surface-700\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-1 block\">Result</Typography>\n <pre className=\"font-mono text-[11px] text-surface-600 dark:text-surface-400 whitespace-pre-wrap break-all\">{JSON.stringify(log.result, null, 2)}</pre>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|