@rebasepro/studio 0.19.1 → 0.19.2-canary.g09316f6
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/dist/{ApiExplorer-9iwGvNnt.js → ApiExplorer-CND5O4Z5.js} +11 -11
- package/dist/ApiExplorer-CND5O4Z5.js.map +1 -0
- package/dist/{ApiKeysView-EIvegrUk.js → ApiKeysView-OfJLH2x7.js} +14 -14
- package/dist/ApiKeysView-OfJLH2x7.js.map +1 -0
- package/dist/{AuthSimulationSelector-Bougd521.js → AuthSimulationSelector-BewmhMBw.js} +2 -2
- package/dist/{AuthSimulationSelector-Bougd521.js.map → AuthSimulationSelector-BewmhMBw.js.map} +1 -1
- package/dist/{BackupsView-DVkKzaPR.js → BackupsView-D0ZDqTEn.js} +6 -6
- package/dist/{BackupsView-DVkKzaPR.js.map → BackupsView-D0ZDqTEn.js.map} +1 -1
- package/dist/{BranchesView-Df8hQ8wE.js → BranchesView-D6Sz1a5f.js} +11 -11
- package/dist/BranchesView-D6Sz1a5f.js.map +1 -0
- package/dist/{CronJobsView-CLOGjb-L.js → CronJobsView-JwRjQCde.js} +12 -12
- package/dist/CronJobsView-JwRjQCde.js.map +1 -0
- package/dist/{JSEditor-75FTeu6P.js → JSEditor-qiuwmcvk.js} +21 -21
- package/dist/JSEditor-qiuwmcvk.js.map +1 -0
- package/dist/{LogsExplorer-DI8SVpya.js → LogsExplorer-Xq8K4oo6.js} +3 -3
- package/dist/{LogsExplorer-DI8SVpya.js.map → LogsExplorer-Xq8K4oo6.js.map} +1 -1
- package/dist/{RLSEditor-Sa1D34ny.js → RLSEditor-C958zVWW.js} +34 -34
- package/dist/RLSEditor-C958zVWW.js.map +1 -0
- package/dist/{SQLEditor-BfNA8xM3.js → SQLEditor-sdtoZv5F.js} +37 -37
- package/dist/SQLEditor-sdtoZv5F.js.map +1 -0
- package/dist/{SchemaVisualizer-DTDMYL6o.js → SchemaVisualizer-DtDzIUNy.js} +30 -27
- package/dist/SchemaVisualizer-DtDzIUNy.js.map +1 -0
- package/dist/{StorageView-Ed9Q_VlB.js → StorageView-C52dSZNf.js} +16 -16
- package/dist/StorageView-C52dSZNf.js.map +1 -0
- package/dist/index.es.js +19 -19
- package/dist/index.es.js.map +1 -1
- package/package.json +9 -9
- package/dist/ApiExplorer-9iwGvNnt.js.map +0 -1
- package/dist/ApiKeysView-EIvegrUk.js.map +0 -1
- package/dist/BranchesView-Df8hQ8wE.js.map +0 -1
- package/dist/CronJobsView-CLOGjb-L.js.map +0 -1
- package/dist/JSEditor-75FTeu6P.js.map +0 -1
- package/dist/RLSEditor-Sa1D34ny.js.map +0 -1
- package/dist/SQLEditor-BfNA8xM3.js.map +0 -1
- package/dist/SchemaVisualizer-DTDMYL6o.js.map +0 -1
- package/dist/StorageView-Ed9Q_VlB.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKeysView-OfJLH2x7.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-hover\"\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-400\"\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-field\",\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, useTranslation } from \"@rebasepro/app\";\nimport type { ApiKeyMasked, ApiKeyWithSecret, RebaseClient } from \"@rebasepro/types\";\n\nimport { CreateApiKeyDialog } from \"./CreateApiKeyDialog\";\nimport { permissionSummary, resourceLabel, resourcePhrase } from \"./permissions\";\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\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\ntype KeyStatusKind = \"active\" | \"expired\" | \"revoked\";\n\nfunction keyStatus(key: ApiKeyMasked): { kind: KeyStatusKind; color: string } {\n if (key.revoked_at) return { kind: \"revoked\", color: \"text-red-500\" };\n if (isExpired(key)) return { kind: \"expired\", color: \"text-amber-500\" };\n return { kind: \"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 { t } = useTranslation();\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 /** Why the key listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | 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 setFailure(null);\n } catch (e: unknown) {\n // \"No API keys yet\" is a claim about the project. A refused listing\n // is a claim about the caller, and only one of the two is an\n // invitation to create a key.\n setFailure(classifyLoadFailure(e));\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-surface-card\">\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-sheet 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\">{t(\"studio_tool_api_keys\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised 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 {failure && (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_api_keys_read_failed\")}\n deniedTitle={t(\"studio_api_keys_denied_title\")}\n deniedHint={t(\"studio_api_keys_denied_hint\")}\n onRetry={loadKeys}\n />\n )}\n {!failure && 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\">{t(\"studio_api_keys_empty_title\")}</Typography>\n <Typography variant=\"caption\" color=\"disabled\">{t(\"studio_api_keys_empty_hint\")}</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\">{t(\"studio_api_keys_revoke\")}d / 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\">{t(\"studio_api_keys_select_hint\")}</Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-surface-card 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 {t(\"studio_api_keys_revoke\")}\n </Button>\n )}\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"px-5 py-4 bg-surface-sheet\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label={t(\"studio_api_keys_stat_status\")} value={t(`studio_api_keys_status_${keyStatus(selectedKey).kind}`)} className={keyStatus(selectedKey).color}/>\n <StatCard label={t(\"created\")} value={formatRelative(selectedKey.created_at)}/>\n <StatCard label={t(\"studio_api_keys_stat_last_used\")} value={formatRelative(selectedKey.last_used_at)}/>\n <StatCard label={t(\"studio_api_keys_stat_expires\")} value={selectedKey.expires_at ? formatRelative(selectedKey.expires_at) : t(\"studio_api_keys_never\")}/>\n </div>\n <div className=\"grid grid-cols-2 md:grid-cols-3 gap-3 mt-3\">\n <StatCard\n label={t(\"role\")}\n value={selectedKey.admin ? t(\"admin\") : t(\"studio_api_keys_role_service\")}\n className={selectedKey.admin ? \"text-amber-600 dark:text-amber-400\" : undefined}\n />\n <StatCard label={t(\"studio_api_keys_stat_rate_limit\")} value={selectedKey.rate_limit ? `${selectedKey.rate_limit}/15min` : t(\"studio_api_keys_rate_limit_default\")}/>\n <StatCard label={t(\"studio_api_keys_stat_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-surface-card\", defaultBorderMixin)}>\n <Typography variant=\"subtitle2\" className=\"font-semibold text-[13px]\">{t(\"studio_api_keys_permissions\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised 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\">{t(\"studio_api_keys_no_permissions\")}</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>{t(\"studio_api_keys_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 {t(\"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-hover\"\n )}\n >\n <div className={cls(\"w-2 h-2 rounded-full shrink-0\",\n status.kind === \"active\" ? \"bg-emerald-400\" :\n status.kind === \"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-surface-card\", 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 { t } = useTranslation();\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: t(\"studio_api_keys_copy_failed\") });\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-sheet\", 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 ? t(\"copied\") : t(\"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>{t(\"studio_api_keys_name_label\")}</strong> {keyWithSecret.name}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\">\n <strong>{t(\"studio_api_keys_access_label\")}</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>{t(\"studio_api_keys_admin_granted\")}</strong> — {t(\"studio_api_keys_admin_granted_hint\")}</span>\n </Typography>\n )}\n </div>\n </DialogContent>\n <DialogActions>\n <Button color=\"primary\" onClick={onClose}>{t(\"studio_api_keys_done\")}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ function AuthSimulationSelector({ authMode, setAuthMode, selectedUser, setSelect
|
|
|
33
33
|
children: "No Auth"
|
|
34
34
|
}),
|
|
35
35
|
authMode === "jwt" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36
|
-
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-
|
|
36
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-hairline-strong mx-1" }),
|
|
37
37
|
/* @__PURE__ */ jsx(Typography, {
|
|
38
38
|
variant: "caption",
|
|
39
39
|
className: "text-text-secondary dark:text-text-secondary-dark text-xs font-medium",
|
|
@@ -53,4 +53,4 @@ function AuthSimulationSelector({ authMode, setAuthMode, selectedUser, setSelect
|
|
|
53
53
|
//#endregion
|
|
54
54
|
export { AuthSimulationSelector as t };
|
|
55
55
|
|
|
56
|
-
//# sourceMappingURL=AuthSimulationSelector-
|
|
56
|
+
//# sourceMappingURL=AuthSimulationSelector-BewmhMBw.js.map
|
package/dist/{AuthSimulationSelector-Bougd521.js.map → AuthSimulationSelector-BewmhMBw.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthSimulationSelector-
|
|
1
|
+
{"version":3,"file":"AuthSimulationSelector-BewmhMBw.js","names":[],"sources":["../src/components/AuthSimulationSelector.tsx"],"sourcesContent":["import React from \"react\";\nimport { Button, cls, iconSize, KeyRoundIcon, Typography } from \"@rebasepro/ui\";\nimport { UserSelectPopover, SelectableUser } from \"@rebasepro/app\";\n\nexport interface AuthSimulationSelectorProps {\n authMode: \"jwt\" | \"none\";\n setAuthMode: (mode: \"jwt\" | \"none\") => void;\n selectedUser: SelectableUser | null;\n setSelectedUser: (user: SelectableUser | null) => void;\n users: SelectableUser[];\n loading?: boolean;\n currentUser: SelectableUser | null;\n}\n\nexport function AuthSimulationSelector({\n authMode,\n setAuthMode,\n selectedUser,\n setSelectedUser,\n users,\n loading,\n currentUser\n}: AuthSimulationSelectorProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-3\">\n <KeyRoundIcon size={iconSize.small} className=\"text-text-secondary dark:text-text-secondary-dark\" />\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-medium\"\n >\n Auth:\n </Typography>\n <Button\n size=\"small\"\n variant={authMode === \"jwt\" ? \"outlined\" : \"outlined\"}\n color={authMode === \"jwt\" ? \"primary\" : \"neutral\"}\n onClick={() => setAuthMode(\"jwt\")}\n className=\"rounded-full !px-3 !py-1 !text-xs\"\n >\n JWT Token\n </Button>\n <Button\n size=\"small\"\n variant={authMode === \"none\" ? \"outlined\" : \"outlined\"}\n color={authMode === \"none\" ? \"error\" : \"neutral\"}\n onClick={() => setAuthMode(\"none\")}\n className=\"rounded-full !px-3 !py-1 !text-xs\"\n >\n No Auth\n </Button>\n\n {authMode === \"jwt\" && (\n <>\n <div className=\"w-px h-4 bg-hairline-strong mx-1\" />\n <Typography\n variant=\"caption\"\n className=\"text-text-secondary dark:text-text-secondary-dark text-xs font-medium\"\n >\n Run as:\n </Typography>\n <UserSelectPopover\n selectedUser={selectedUser}\n onUserSelected={setSelectedUser}\n users={users}\n loading={loading}\n currentUser={currentUser}\n />\n </>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAcA,SAAgB,uBAAuB,EACnC,UACA,aACA,cACA,iBACA,OACA,SACA,eAC4B;CAC5B,OACI,qBAAC,OAAD;EAAK,WAAU;YAAf;GACI,oBAAC,cAAD;IAAc,MAAM,SAAS;IAAO,WAAU;GAAqD,CAAA;GACnG,oBAAC,YAAD;IACI,SAAQ;IACR,WAAU;cACb;GAEW,CAAA;GACZ,oBAAC,QAAD;IACI,MAAK;IACL,SAAS,aAAa,QAAQ,aAAa;IAC3C,OAAO,aAAa,QAAQ,YAAY;IACxC,eAAe,YAAY,KAAK;IAChC,WAAU;cACb;GAEO,CAAA;GACR,oBAAC,QAAD;IACI,MAAK;IACL,SAAS,aAAa,SAAS,aAAa;IAC5C,OAAO,aAAa,SAAS,UAAU;IACvC,eAAe,YAAY,MAAM;IACjC,WAAU;cACb;GAEO,CAAA;GAEP,aAAa,SACV,qBAAA,UAAA,EAAA,UAAA;IACI,oBAAC,OAAD,EAAK,WAAU,mCAAoC,CAAA;IACnD,oBAAC,YAAD;KACI,SAAQ;KACR,WAAU;eACb;IAEW,CAAA;IACZ,oBAAC,mBAAD;KACkB;KACd,gBAAgB;KACT;KACE;KACI;IAChB,CAAA;GACH,EAAA,CAAA;EAEL;;AAEb"}
|
|
@@ -103,9 +103,9 @@ function BackupsView() {
|
|
|
103
103
|
children: /* @__PURE__ */ jsx(CircularProgress, {})
|
|
104
104
|
});
|
|
105
105
|
return /* @__PURE__ */ jsxs("div", {
|
|
106
|
-
className: "flex flex-col h-full w-full overflow-hidden bg-
|
|
106
|
+
className: "flex flex-col h-full w-full overflow-hidden bg-surface-card",
|
|
107
107
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
108
|
-
className: cls("flex items-center justify-between px-5 py-2.5 border-b bg-surface-
|
|
108
|
+
className: cls("flex items-center justify-between px-5 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
|
|
109
109
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
110
110
|
className: "flex items-center gap-2",
|
|
111
111
|
children: [
|
|
@@ -120,12 +120,12 @@ function BackupsView() {
|
|
|
120
120
|
}),
|
|
121
121
|
/* @__PURE__ */ jsx(Chip, {
|
|
122
122
|
size: "smallest",
|
|
123
|
-
className: "bg-surface-
|
|
123
|
+
className: "bg-surface-raised text-surface-600 dark:text-surface-300",
|
|
124
124
|
children: backups.length
|
|
125
125
|
}),
|
|
126
126
|
/* @__PURE__ */ jsx(Chip, {
|
|
127
127
|
size: "smallest",
|
|
128
|
-
className: "bg-surface-
|
|
128
|
+
className: "bg-surface-raised text-surface-500 dark:text-surface-400 uppercase font-mono text-[10px]",
|
|
129
129
|
children: destinationKind
|
|
130
130
|
})
|
|
131
131
|
]
|
|
@@ -182,7 +182,7 @@ function BackupsView() {
|
|
|
182
182
|
}) : /* @__PURE__ */ jsx("div", {
|
|
183
183
|
className: "space-y-2 max-w-3xl",
|
|
184
184
|
children: backups.map((backup) => /* @__PURE__ */ jsxs("div", {
|
|
185
|
-
className: cls("flex items-center gap-3 px-4 py-3 rounded-lg border bg-
|
|
185
|
+
className: cls("flex items-center gap-3 px-4 py-3 rounded-lg border bg-surface-card", defaultBorderMixin),
|
|
186
186
|
children: [
|
|
187
187
|
/* @__PURE__ */ jsx(DatabaseIcon, {
|
|
188
188
|
size: iconSize.small,
|
|
@@ -222,4 +222,4 @@ function BackupsView() {
|
|
|
222
222
|
//#endregion
|
|
223
223
|
export { BackupsView };
|
|
224
224
|
|
|
225
|
-
//# sourceMappingURL=BackupsView-
|
|
225
|
+
//# sourceMappingURL=BackupsView-D0ZDqTEn.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackupsView-DVkKzaPR.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, useTranslation } from \"@rebasepro/app\";\nimport type { BackupInfo, RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\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 const { t } = useTranslation();\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 /** Why the listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | 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 setFailure(null);\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 // The snackbar is gone in seconds, and the list underneath it is\n // empty — which is how a refused listing came to read \"No backups\n // found yet\" about a database that has backups. Keep the reason on\n // screen instead.\n setFailure(classifyLoadFailure(e));\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\">{t(\"studio_tool_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 {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_backups_read_failed\")}\n deniedTitle={t(\"studio_backups_denied_title\")}\n deniedHint={t(\"studio_backups_denied_hint\")}\n onRetry={load}\n />\n ) : !configured ? (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>{t(\"studio_backups_not_configured_title\")}</strong>{\" \"}\n {t(\"studio_backups_not_configured_body\")}{\" \"}\n <a\n href=\"https://rebase.pro/docs/backend/jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\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 ? t(\"studio_backups_downloading\") : t(\"download\")}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BackupsView-D0ZDqTEn.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, useTranslation } from \"@rebasepro/app\";\nimport type { BackupInfo, RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\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 const { t } = useTranslation();\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 /** Why the listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | 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 setFailure(null);\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 // The snackbar is gone in seconds, and the list underneath it is\n // empty — which is how a refused listing came to read \"No backups\n // found yet\" about a database that has backups. Keep the reason on\n // screen instead.\n setFailure(classifyLoadFailure(e));\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-surface-card\">\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-2.5 border-b bg-surface-sheet 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\">{t(\"studio_tool_backups\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised text-surface-600 dark:text-surface-300\">{backups.length}</Chip>\n <Chip size=\"smallest\" className=\"bg-surface-raised 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 {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_backups_read_failed\")}\n deniedTitle={t(\"studio_backups_denied_title\")}\n deniedHint={t(\"studio_backups_denied_hint\")}\n onRetry={load}\n />\n ) : !configured ? (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>{t(\"studio_backups_not_configured_title\")}</strong>{\" \"}\n {t(\"studio_backups_not_configured_body\")}{\" \"}\n <a\n href=\"https://rebase.pro/docs/backend/jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\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-surface-card\", 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 ? t(\"studio_backups_downloading\") : t(\"download\")}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -155,12 +155,12 @@ function BranchesView() {
|
|
|
155
155
|
});
|
|
156
156
|
const selected = branches.find((b) => b.name === selectedBranch);
|
|
157
157
|
return /* @__PURE__ */ jsxs("div", {
|
|
158
|
-
className: "flex h-full w-full overflow-hidden bg-
|
|
158
|
+
className: "flex h-full w-full overflow-hidden bg-surface-card",
|
|
159
159
|
children: [
|
|
160
160
|
/* @__PURE__ */ jsxs("div", {
|
|
161
161
|
className: cls("flex flex-col w-[340px] min-w-[280px] border-r h-full", defaultBorderMixin),
|
|
162
162
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
163
|
-
className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-
|
|
163
|
+
className: cls("flex items-center justify-between px-4 py-2.5 border-b bg-surface-sheet min-h-[48px]", defaultBorderMixin),
|
|
164
164
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
165
165
|
className: "flex items-center gap-2",
|
|
166
166
|
children: [
|
|
@@ -175,7 +175,7 @@ function BranchesView() {
|
|
|
175
175
|
}),
|
|
176
176
|
/* @__PURE__ */ jsx(Chip, {
|
|
177
177
|
size: "smallest",
|
|
178
|
-
className: "bg-surface-
|
|
178
|
+
className: "bg-surface-raised text-surface-600 dark:text-surface-300",
|
|
179
179
|
children: branches.length
|
|
180
180
|
})
|
|
181
181
|
]
|
|
@@ -225,7 +225,7 @@ function BranchesView() {
|
|
|
225
225
|
]
|
|
226
226
|
}) : branches.map((branch) => /* @__PURE__ */ jsxs("div", {
|
|
227
227
|
onClick: () => setSelectedBranch(branch.name),
|
|
228
|
-
className: cls("flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all", selectedBranch === branch.name ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30" : "hover:bg-surface-
|
|
228
|
+
className: cls("flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all", selectedBranch === branch.name ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30" : "hover:bg-surface-hover"),
|
|
229
229
|
children: [
|
|
230
230
|
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full shrink-0 bg-emerald-500" }),
|
|
231
231
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -270,7 +270,7 @@ function BranchesView() {
|
|
|
270
270
|
})]
|
|
271
271
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
272
272
|
/* @__PURE__ */ jsxs("div", {
|
|
273
|
-
className: cls("flex items-center justify-between px-5 py-3 border-b bg-
|
|
273
|
+
className: cls("flex items-center justify-between px-5 py-3 border-b bg-surface-card min-h-[56px]", defaultBorderMixin),
|
|
274
274
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
275
275
|
className: "flex items-center gap-3 min-w-0",
|
|
276
276
|
children: [/* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-emerald-500" }), /* @__PURE__ */ jsxs("div", {
|
|
@@ -302,7 +302,7 @@ function BranchesView() {
|
|
|
302
302
|
})]
|
|
303
303
|
}),
|
|
304
304
|
/* @__PURE__ */ jsx("div", {
|
|
305
|
-
className: "px-5 py-4 bg-surface-
|
|
305
|
+
className: "px-5 py-4 bg-surface-sheet",
|
|
306
306
|
children: /* @__PURE__ */ jsxs("div", {
|
|
307
307
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
308
308
|
children: [
|
|
@@ -338,19 +338,19 @@ function BranchesView() {
|
|
|
338
338
|
/* @__PURE__ */ jsx("strong", { children: "How to use this branch:" }),
|
|
339
339
|
" point this checkout at it with",
|
|
340
340
|
/* @__PURE__ */ jsxs("code", {
|
|
341
|
-
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-
|
|
341
|
+
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]",
|
|
342
342
|
children: ["rebase db branch switch ", selected.name]
|
|
343
343
|
}),
|
|
344
344
|
"— every later command uses it, and",
|
|
345
345
|
/* @__PURE__ */ jsx("code", {
|
|
346
|
-
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-
|
|
346
|
+
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]",
|
|
347
347
|
children: "rebase db branch switch --off"
|
|
348
348
|
}),
|
|
349
349
|
"goes back to the main database. Changes made here don't affect it."
|
|
350
350
|
]
|
|
351
351
|
})
|
|
352
352
|
}), /* @__PURE__ */ jsxs("div", {
|
|
353
|
-
className: "mt-4 p-4 rounded-lg border bg-surface-
|
|
353
|
+
className: "mt-4 p-4 rounded-lg border bg-surface-sheet border-hairline-strong",
|
|
354
354
|
children: [/* @__PURE__ */ jsx(Typography, {
|
|
355
355
|
variant: "caption",
|
|
356
356
|
className: "text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium",
|
|
@@ -499,7 +499,7 @@ function BranchesView() {
|
|
|
499
499
|
}
|
|
500
500
|
function StatCard({ label, value, mono }) {
|
|
501
501
|
return /* @__PURE__ */ jsxs("div", {
|
|
502
|
-
className: cls("px-3 py-2 rounded-lg border bg-
|
|
502
|
+
className: cls("px-3 py-2 rounded-lg border bg-surface-card", defaultBorderMixin),
|
|
503
503
|
children: [/* @__PURE__ */ jsx(Typography, {
|
|
504
504
|
variant: "caption",
|
|
505
505
|
color: "secondary",
|
|
@@ -515,4 +515,4 @@ function StatCard({ label, value, mono }) {
|
|
|
515
515
|
//#endregion
|
|
516
516
|
export { BranchesView };
|
|
517
517
|
|
|
518
|
-
//# sourceMappingURL=BranchesView-
|
|
518
|
+
//# sourceMappingURL=BranchesView-D6Sz1a5f.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BranchesView-D6Sz1a5f.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, useTranslation } from \"@rebasepro/app\";\nimport { isBranchAdmin } from \"@rebasepro/types\";\nimport type { BranchInfo } from \"@rebasepro/types\";\nimport { formatRelativeTime } from \"@rebasepro/utils\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\n\n/**\n * The prefix the driver puts on every branch database.\n *\n * Stated here rather than derived from the row, because `BranchInfo` carries\n * the *name* a person typed and not the database it became — and the detail\n * pane used to print the name under \"Database:\", which is a connection string\n * to nothing. Kept in step with `BranchService.BRANCH_DB_PREFIX` and the CLI's\n * `branch-pointer.ts`, which say the same thing for the same reason.\n */\nconst BRANCH_DB_PREFIX = \"rb_\";\nimport { LoadFailureView } from \"../load-failure-view\";\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 const { t } = useTranslation();\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 * Why the last create attempt was refused, kept on screen.\n *\n * The server refuses branching outright on the managed development\n * database — PGlite serves one database, so the \"branch\" would be the\n * parent. That refusal is the whole answer to what the reader just tried to\n * do, and a snackbar is gone in four seconds; the same argument\n * `load-failure.ts` makes for listings.\n */\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n /** Why the branch listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\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 setFailure(null);\n } catch (e: unknown) {\n // \"No branches yet. Create one…\" over a refused listing invites the\n // reader to create a branch they are not allowed to see, and then\n // hides the one they already have.\n setFailure(classifyLoadFailure(e));\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 setCreateError(null);\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 const message = e instanceof Error ? e.message : String(e);\n setCreateError(message);\n snackbarRef.current.open({ type: \"error\",\n message });\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\">{t(\"studio_branches_unavailable_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_branches_unavailable_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/branching\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\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-surface-card\">\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-sheet 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\">{t(\"studio_tool_branches\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-raised 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 {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_branches_read_failed\")}\n deniedTitle={t(\"studio_branches_denied_title\")}\n deniedHint={t(\"studio_branches_denied_hint\")}\n onRetry={loadBranches}\n />\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-hover\"\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-surface-card 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-sheet\">\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> point this checkout at it with\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch {selected.name}</code>\n — every later command uses it, and\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-well font-mono text-[12px]\">rebase db branch switch --off</code>\n goes back to the main database. Changes made here don't affect it.\n </Typography>\n </Alert>\n <div className=\"mt-4 p-4 rounded-lg border bg-surface-sheet border-hairline-strong\">\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 {/* The *database*, not the branch name. They are not the\n same string — the driver prefixes every branch database\n with `rb_` — and a connection string built from the name\n on this pane pointed at a database that does not\n exist. */}\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Database:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{`${BRANCH_DB_PREFIX}${selected.name}`}</Typography>\n </div>\n <div className=\"flex items-start gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Connect with:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px] break-all\">\n {`DATABASE_URL=…/${BRANCH_DB_PREFIX}${selected.name}`}\n </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={(open) => {\n setCreateOpen(open);\n if (!open) setCreateError(null);\n }}>\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 {createError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px]\">{createError}</Typography>\n </Alert>\n )}\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-surface-card\", 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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"}
|