@rebasepro/studio 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{ApiExplorer-D-jLWAaY.js → ApiExplorer-CCtczj3F.js} +16 -32
  2. package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
  3. package/dist/ApiKeysView-B66TdzhH.js +1738 -0
  4. package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
  5. package/dist/BackupsView-DmSMzAbT.js +368 -0
  6. package/dist/BackupsView-DmSMzAbT.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
  8. package/dist/BranchesView-DP1a87E-.js.map +1 -0
  9. package/dist/{CronJobsView-JwRjQCde.js → CronJobsView-TDErHTuK.js} +143 -68
  10. package/dist/CronJobsView-TDErHTuK.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
  12. package/dist/JSEditor-B59pwg4c.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
  14. package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
  15. package/dist/{MonacoEditor-D0_QljcX.js → MonacoEditor-CUxy5_k5.js} +11 -9
  16. package/dist/MonacoEditor-CUxy5_k5.js.map +1 -0
  17. package/dist/{RLSEditor-B-qrgdHy.js → RLSEditor-BlLLV6xs.js} +350 -98
  18. package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
  19. package/dist/{SQLEditor-sdtoZv5F.js → SQLEditor-1An7w0j6.js} +496 -224
  20. package/dist/SQLEditor-1An7w0j6.js.map +1 -0
  21. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
  22. package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
  23. package/dist/{StorageView-C52dSZNf.js → StorageView-CbsHuCBf.js} +382 -228
  24. package/dist/StorageView-CbsHuCBf.js.map +1 -0
  25. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  26. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  27. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  28. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  29. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  30. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  31. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  32. package/dist/components/RLSEditor/policyRules.d.ts +49 -0
  33. package/dist/components/SQLEditor/MonacoEditor.d.ts +13 -1
  34. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
  35. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
  36. package/dist/components/load-failure.d.ts +1 -1
  37. package/dist/components/useRunAsUsers.d.ts +25 -0
  38. package/dist/index.es.js +11 -11
  39. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  40. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  41. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  42. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  43. package/dist/utils/sql_utils.d.ts +126 -36
  44. package/package.json +9 -9
  45. package/dist/ApiExplorer-D-jLWAaY.js.map +0 -1
  46. package/dist/ApiKeysView-OfJLH2x7.js +0 -1239
  47. package/dist/ApiKeysView-OfJLH2x7.js.map +0 -1
  48. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  49. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  50. package/dist/BackupsView-DYTAD6aW.js +0 -253
  51. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  52. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  53. package/dist/CronJobsView-JwRjQCde.js.map +0 -1
  54. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  55. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  56. package/dist/MonacoEditor-D0_QljcX.js.map +0 -1
  57. package/dist/RLSEditor-B-qrgdHy.js.map +0 -1
  58. package/dist/SQLEditor-sdtoZv5F.js.map +0 -1
  59. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  60. package/dist/StorageView-C52dSZNf.js.map +0 -1
  61. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  62. package/dist/load-failure-C6OMJSGC.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageView-CbsHuCBf.js","names":[],"sources":["../src/components/StorageView/StorageView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useCallback, useMemo, useRef } from \"react\";\nimport {\n ArrowLeftIcon,\n Button,\n Checkbox,\n CheckIcon,\n Chip,\n CircularProgress,\n cls,\n CopyIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n FileIcon,\n FileTextIcon,\n FileUpload,\n FolderIcon,\n FolderPlusIcon,\n HomeIcon,\n IconButton,\n iconSize,\n ImageIcon,\n LayoutGridIcon,\n ListIcon,\n LoadingButton,\n Music2Icon,\n PlusIcon,\n RefreshCwIcon,\n ResizablePanels,\n Select,\n SelectItem,\n TextField,\n Tooltip,\n Trash2Icon,\n Typography,\n UploadCloudIcon,\n VideoIcon,\n XIcon,\n isKeyHandled\n} from \"@rebasepro/ui\";\nimport { useStorageSource, useStorageSources, useSnackbarController, ErrorView, useApiBase, useApiConfig, useTranslation } from \"@rebasepro/app\";\nimport { DEFAULT_STORAGE_SOURCE_KEY, isPublicStorageKey, type DownloadConfig, type StorageReference, type StorageSource } from \"@rebasepro/types\";\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { useSearchParams } from \"react-router\";\nimport { useDropzone } from \"react-dropzone\";\n\n// ──────────────────────────────────────────────\n// Types\n// ──────────────────────────────────────────────\n\ninterface StorageFile {\n name: string;\n fullPath: string;\n isFolder: boolean;\n /** Only populated when metadata is fetched */\n size?: number;\n contentType?: string;\n downloadUrl?: string;\n}\n\n/**\n * A link to one file, as the preview shows, downloads and copies it.\n *\n * Minted when it is used rather than taken from the listing: a private file's\n * URL carries a download token (five minutes by default) minted when the folder\n * was listed, and a preview, a download or a copied link built from that one\n * stopped working while the page stayed open.\n */\ninterface FileLink {\n url: string;\n /**\n * How long the link's token was minted for, in seconds: a link to a private\n * file, which opens it for whoever holds the link until then. Absent when\n * the source does not say.\n */\n expiresInSeconds?: number;\n /** A public file's link: no token, open to anyone, and it does not expire. */\n isPublic: boolean;\n}\n\nfunction fileLinkOf(config: DownloadConfig, fullPath: string): FileLink | null {\n if (!config.url) return null;\n const metadata = config.metadata;\n return {\n url: config.url,\n expiresInSeconds: metadata?.token && typeof metadata.tokenExpiresIn === \"number\" ? metadata.tokenExpiresIn : undefined,\n // The SDK skips the metadata round trip only for a key under the public\n // prefix, and the server says so on every other public object.\n isPublic: metadata ? metadata.public === true : isPublicStorageKey(fullPath)\n };\n}\n\n/** A URL the browser can open from anywhere: relative ones are this page's origin's. */\nfunction absoluteUrl(url: string): string {\n return url.startsWith(\"http\") ? url : `${window.location.origin}${url.startsWith(\"/\") ? \"\" : \"/\"}${url}`;\n}\n\n// ──────────────────────────────────────────────\n// Helpers\n// ──────────────────────────────────────────────\n\n/**\n * The query parameter holding the folder being browsed.\n *\n * Namespaced because Studio does not always own the URL it renders under: the\n * SaaS console embeds this view in a page that keeps its own state in `?tab=`\n * and `?sub=`, and a bare `path` is a name any host app might already be using.\n */\nconst STORAGE_PATH_PARAM = \"storagePath\";\n\nfunction formatFileSize(bytes: number): string {\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 getFileIcon(contentType?: string) {\n if (!contentType) return FileTextIcon;\n if (contentType.startsWith(\"image/\")) return ImageIcon;\n if (contentType.startsWith(\"video/\")) return VideoIcon;\n if (contentType.startsWith(\"audio/\")) return Music2Icon;\n return FileTextIcon;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : \"\";\n}\n\nfunction breadcrumbSegments(path: string, rootLabel: string): { label: string; path: string }[] {\n if (!path || path === \"/\") return [{ label: rootLabel,\npath: \"\" }];\n const parts = path.split(\"/\").filter(Boolean);\n const segments = [{ label: rootLabel,\npath: \"\" }];\n let accumulated = \"\";\n for (const part of parts) {\n accumulated = accumulated ? `${accumulated}/${part}` : part;\n segments.push({ label: part,\npath: accumulated });\n }\n return segments;\n}\n\n/**\n * Everything directly under `prefix`, across every page.\n *\n * A listing answers a page at a time — a thousand entries on the built-in\n * controllers — and hands back a token for the next. Reading only the first\n * showed a large folder cut short, and deleted the first page of it.\n */\nasync function listAllPages(\n source: StorageSource,\n prefix: string\n): Promise<{ items: StorageReference[]; prefixes: StorageReference[] }> {\n const items: StorageReference[] = [];\n const prefixes: StorageReference[] = [];\n let pageToken: string | undefined;\n do {\n const page = pageToken\n ? await source.listObjects(prefix, { pageToken })\n : await source.listObjects(prefix);\n items.push(...(page.items ?? []));\n prefixes.push(...(page.prefixes ?? []));\n pageToken = page.nextPageToken;\n } while (pageToken);\n return { items, prefixes };\n}\n\n/**\n * Delete a folder: every file in it, every folder below it, and the folder\n * itself — the directory on local disk, or the zero-byte `name/` marker that\n * \"New folder\" writes on S3 and GCS, which no listing shows as an entry.\n */\nasync function deleteFolderRecursive(source: StorageSource, prefix: string): Promise<void> {\n const { items, prefixes } = await listAllPages(source, prefix);\n for (const item of items) {\n await source.deleteObject(item.fullPath);\n }\n for (const sub of prefixes) {\n await deleteFolderRecursive(source, sub.fullPath);\n }\n try {\n await source.deleteObject(prefix.endsWith(\"/\") ? prefix : `${prefix}/`);\n } catch {\n // A folder with no object of its own (most object-store folders) has\n // nothing to delete here. Whether it is gone is read back by the\n // caller from the folder that held it.\n }\n}\n\n// ──────────────────────────────────────────────\n// Upload Dialog\n// ──────────────────────────────────────────────\n\nfunction UploadDialog({\n open,\n currentPath,\n onClose,\n onUpload\n}: {\n open: boolean;\n currentPath: string;\n onClose: () => void;\n onUpload: (files: File[]) => Promise<void>;\n}) {\n const { t } = useTranslation();\n const [uploading, setUploading] = useState(false);\n const [selectedFiles, setSelectedFiles] = useState<File[]>([]);\n const [error, setError] = useState<string | null>(null);\n\n const handleFilesAdded = useCallback((files: File[]) => {\n setSelectedFiles(prev => [...prev, ...files]);\n }, []);\n\n const handleRemoveFile = useCallback((index: number) => {\n setSelectedFiles(prev => prev.filter((_, i) => i !== index));\n }, []);\n\n const handleUpload = useCallback(async () => {\n if (selectedFiles.length === 0) return;\n setUploading(true);\n setError(null);\n try {\n await onUpload(selectedFiles);\n setSelectedFiles([]);\n onClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : t(\"studio_storage_upload_failed\"));\n } finally {\n setUploading(false);\n }\n }, [selectedFiles, onUpload, onClose, t]);\n\n const handleClose = useCallback(() => {\n if (!uploading) {\n setSelectedFiles([]);\n setError(null);\n onClose();\n }\n }, [uploading, onClose]);\n\n return (\n <Dialog open={open} onOpenChange={(o) => !o && handleClose()} maxWidth=\"md\">\n <DialogTitle>\n {t(\"studio_storage_upload_dialog_title\")}\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark mt-0.5 block\">\n {t(\"studio_storage_upload_to\")} <span className=\"font-mono text-primary\">/{currentPath || t(\"studio_storage_root_folder\")}</span>\n </Typography>\n </DialogTitle>\n <DialogContent className=\"space-y-4\">\n <FileUpload\n onFilesAdded={handleFilesAdded}\n size=\"large\"\n uploadDescription={\n <div className=\"flex flex-col items-center justify-center pointer-events-none\">\n <UploadCloudIcon className=\"text-surface-accent-400 mb-2 w-8 h-8\"/>\n <Typography variant=\"label\">\n {t(\"studio_storage_drop_or_browse\")}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\">\n {t(\"studio_storage_any_file_type\")}\n </Typography>\n </div>\n }\n />\n\n {error && (\n <Typography variant=\"caption\" className=\"text-red-500 block whitespace-pre-line\">\n {error}\n </Typography>\n )}\n\n {selectedFiles.length > 0 && (\n <div className=\"space-y-2\">\n <Typography variant=\"caption\" color=\"secondary\">\n {t(\"studio_storage_selected_files\", { count: selectedFiles.length })}\n </Typography>\n <div className=\"max-h-40 overflow-auto space-y-1\">\n {selectedFiles.map((file, index) => (\n <div\n key={`${file.name}-${index}`}\n className=\"flex items-center justify-between p-2 rounded bg-surface-raised\"\n >\n <div className=\"flex-1 min-w-0 mr-2\">\n <Typography variant=\"body2\" className=\"truncate\">\n {file.name}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\">\n {formatFileSize(file.size)}\n </Typography>\n </div>\n <IconButton\n size=\"small\"\n onClick={(e) => {\n e.stopPropagation();\n handleRemoveFile(index);\n }}\n disabled={uploading}\n >\n <XIcon size={14}/>\n </IconButton>\n </div>\n ))}\n </div>\n </div>\n )}\n </DialogContent>\n\n <DialogActions>\n <Button variant=\"text\" onClick={handleClose} disabled={uploading}>\n {t(\"studio_storage_cancel\")}\n </Button>\n <Button\n variant=\"filled\"\n onClick={handleUpload}\n disabled={selectedFiles.length === 0 || uploading}\n startIcon={uploading ? <CircularProgress size=\"smallest\"/> : <UploadCloudIcon size={14}/>}\n >\n {uploading\n ? t(\"studio_storage_uploading\")\n : selectedFiles.length > 0\n ? t(\"studio_storage_upload_count\", { count: selectedFiles.length })\n : t(\"studio_storage_upload\")}\n </Button>\n </DialogActions>\n </Dialog>\n );\n}\n\n// ──────────────────────────────────────────────\n// FileIcon preview panel\n// ──────────────────────────────────────────────\n\nfunction FilePreviewPanel({\n file,\n onClose,\n onDelete,\n link,\n mintLink\n}: {\n file: StorageFile;\n onClose: () => void;\n onDelete: () => void;\n /** The link minted when the file was opened, shown and previewed. */\n link: FileLink | null;\n /** A fresh link for a download or a copy, minted at the click. */\n mintLink: () => Promise<FileLink | null>;\n}) {\n const { t } = useTranslation();\n const snackbarController = useSnackbarController();\n const isImage = file.contentType?.startsWith(\"image/\");\n const isVideo = file.contentType?.startsWith(\"video/\");\n const isAudio = file.contentType?.startsWith(\"audio/\");\n const FileIconComponent = getFileIcon(file.contentType);\n const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);\n const [urlCopied, setUrlCopied] = useState(false);\n\n const reportLinkError = (e: unknown) => {\n snackbarController.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n };\n\n const handleDownload = () => {\n mintLink()\n .then(fresh => { if (fresh) window.open(fresh.url, \"_blank\"); })\n .catch(reportLinkError);\n };\n\n const handleCopy = () => {\n mintLink()\n .then(async fresh => {\n if (!fresh) return;\n await navigator.clipboard.writeText(absoluteUrl(fresh.url));\n setUrlCopied(true);\n setTimeout(() => setUrlCopied(false), 2000);\n })\n .catch(reportLinkError);\n };\n\n return (\n <>\n <div className={cls(\n \"flex flex-col h-full border-l\",\n defaultBorderMixin,\n \"bg-surface-card\"\n )}>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between p-3 border-b shrink-0\", defaultBorderMixin)}>\n <Typography variant=\"body2\" className=\"font-medium truncate flex-1 mr-2\">\n {file.name}\n </Typography>\n <div className=\"flex items-center gap-0.5\">\n {link && (\n <Tooltip title={t(\"studio_storage_download\")}>\n <IconButton aria-label={t(\"studio_storage_download\")}\n size=\"small\"\n onClick={handleDownload}\n >\n <DownloadIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n )}\n <Tooltip title={t(\"studio_storage_delete\")}>\n <IconButton aria-label={t(\"studio_storage_delete\")}\n size=\"small\"\n onClick={() => setDeleteDialogOpen(true)}\n className=\"text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20\"\n >\n <Trash2Icon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n <IconButton size=\"small\" onClick={onClose}>\n <XIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n\n {/* Preview */}\n <div className=\"flex-1 overflow-auto\">\n <div className={cls(\"flex flex-col items-center justify-center min-h-[200px] p-4 bg-surface-sheet border-b\", defaultBorderMixin)}>\n {(() => {\n const ext = getExtension(file.name)?.toLowerCase() || \"\";\n const isImage = file.contentType?.startsWith(\"image/\") || [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\"].includes(ext);\n const isVideo = file.contentType?.startsWith(\"video/\") || [\"mp4\", \"webm\", \"ogg\", \"mov\"].includes(ext);\n const isAudio = file.contentType?.startsWith(\"audio/\") || [\"mp3\", \"wav\", \"ogg\", \"m4a\"].includes(ext);\n const previewUrl = link?.url;\n\n if (isImage && previewUrl) {\n return (\n <img\n src={previewUrl}\n alt={file.name}\n className=\"max-w-full max-h-[400px] object-contain rounded-md shadow-sm\"\n />\n );\n } else if (isVideo && previewUrl) {\n return (\n <video\n src={previewUrl}\n className=\"max-w-full max-h-[400px] rounded-md\"\n controls\n />\n );\n } else if (isAudio && previewUrl) {\n return (\n <div className=\"flex flex-col items-center gap-4\">\n <Music2Icon className=\"text-surface-accent-400 w-10 h-10\"/>\n <audio src={previewUrl} controls className=\"w-full max-w-xs\"/>\n </div>\n );\n } else {\n return (\n <div className=\"flex flex-col items-center gap-3 text-surface-accent-400\">\n <FileIconComponent className=\"w-10 h-10\"/>\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark\">\n {t(\"studio_storage_no_preview\")}\n </Typography>\n </div>\n );\n }\n })()}\n </div>\n </div>\n\n {/* Metadata */}\n <div className=\"p-4 space-y-3\">\n <div>\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark text-[10px] uppercase tracking-wider font-semibold mb-1 block\">\n {t(\"studio_storage_file_info\")}\n </Typography>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px]\">\n {t(\"studio_storage_name\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-[13px] break-all\">\n {file.name}\n </Typography>\n </div>\n <div>\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px]\">\n {t(\"studio_storage_type\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {file.contentType || t(\"studio_storage_type_unknown\")}\n </Typography>\n </div>\n {file.size !== undefined && (\n <div>\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px]\">\n {t(\"studio_storage_size\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-[13px]\">\n {formatFileSize(file.size)}\n </Typography>\n </div>\n )}\n <div>\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px]\">\n {t(\"studio_storage_extension\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-[13px] font-mono\">\n {getExtension(file.name) || \"—\"}\n </Typography>\n </div>\n <div className=\"col-span-2\">\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px]\">\n {t(\"studio_storage_path\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-[13px] font-mono break-all\">\n {file.fullPath}\n </Typography>\n </div>\n </div>\n\n {link && (\n <div className=\"pt-2\">\n <Typography variant=\"caption\" className=\"text-surface-accent-500 text-[11px] block mb-1\">\n {t(\"studio_storage_url\")}\n </Typography>\n <div\n className={cls(\n \"flex items-center gap-2 p-2 rounded cursor-pointer transition-colors\",\n \"bg-surface-raised hover:bg-surface-raised-hover\"\n )}\n onClick={handleCopy}\n >\n <Typography variant=\"caption\" className=\"font-mono text-[11px] truncate flex-1 min-w-0 text-primary\">\n {absoluteUrl(link.url)}\n </Typography>\n <Tooltip title={urlCopied ? t(\"studio_storage_url_copied\") : t(\"studio_storage_copy_url\")}>\n <div className=\"shrink-0\">\n {urlCopied\n ? <CheckIcon size={14} className=\"text-green-500\"/>\n : <CopyIcon size={14} className=\"text-surface-accent-400\"/>\n }\n </div>\n </Tooltip>\n </div>\n {link.isPublic ? (\n <Typography variant=\"caption\" color=\"secondary\" className=\"block mt-1 text-[11px]\">\n {t(\"studio_storage_url_public\")}\n </Typography>\n ) : link.expiresInSeconds !== undefined ? (\n <Typography variant=\"caption\" color=\"secondary\" className=\"block mt-1 text-[11px]\">\n {t(\"studio_storage_url_temporary\", { minutes: Math.max(1, Math.round(link.expiresInSeconds / 60)) })}\n </Typography>\n ) : null}\n </div>\n )}\n </div>\n </div>\n\n {/* Delete Confirmation */}\n <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>\n <DialogTitle hidden>{t(\"studio_storage_delete_file_title\")}</DialogTitle>\n <DialogContent>\n <Typography variant=\"subtitle1\" className=\"mb-2\">\n {t(\"studio_storage_delete_file_question\")}\n </Typography>\n <Typography className=\"text-surface-accent-600 dark:text-surface-accent-400\">\n {t(\"studio_storage_delete_file_body\", { name: file.name })}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setDeleteDialogOpen(false)}>\n {t(\"studio_storage_cancel\")}\n </Button>\n <Button\n variant=\"filled\"\n color=\"error\"\n onClick={() => {\n setDeleteDialogOpen(false);\n onDelete();\n }}\n >\n {t(\"studio_storage_delete\")}\n </Button>\n </DialogActions>\n </Dialog>\n </>\n );\n}\n\n// ──────────────────────────────────────────────\n// Main StorageView Export\n// ──────────────────────────────────────────────\n\nexport const StorageView = () => {\n const { t } = useTranslation();\n const defaultStorageSource = useStorageSource();\n const storageSources = useStorageSources();\n const snackbarController = useSnackbarController();\n\n // Available backends to browse. Always includes the default; named\n // sources come from `<Rebase storageSources={...}>`.\n const sourceKeys = useMemo(() => {\n const keys = Object.keys(storageSources.sources);\n if (!keys.includes(DEFAULT_STORAGE_SOURCE_KEY)) keys.unshift(DEFAULT_STORAGE_SOURCE_KEY);\n return keys;\n }, [storageSources.sources]);\n\n const [selectedSourceKey, setSelectedSourceKey] = useState<string>(DEFAULT_STORAGE_SOURCE_KEY);\n\n const storageSource = storageSources.sources[selectedSourceKey] ?? defaultStorageSource;\n\n // Navigation\n const [searchParams, setSearchParams] = useSearchParams();\n // Accepts the historical unqualified `path` so links already shared keep\n // working; only the namespaced one is ever written.\n const currentPath = searchParams.get(STORAGE_PATH_PARAM) ?? searchParams.get(\"path\") ?? \"\";\n const [loading, setLoading] = useState(true);\n /** Why the listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Contents\n const [folders, setFolders] = useState<StorageFile[]>([]);\n const [files, setFiles] = useState<StorageFile[]>([]);\n\n // Selection and preview\n const [selectedFile, setSelectedFile] = useState<StorageFile | null>(null);\n const [selectedLink, setSelectedLink] = useState<FileLink | null>(null);\n /** The latest link asked for the preview: an earlier one answering later is dropped. */\n const linkRequestRef = useRef(0);\n\n // Upload\n const [uploadDialogOpen, setUploadDialogOpen] = useState(false);\n\n // View mode\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n // Multi-selection\n const [selectedPaths, setSelectedPaths] = useState<Set<string>>(new Set());\n const lastClickedRef = useRef<string | null>(null);\n\n // Bulk / folder delete\n const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);\n const [deleteDialogTarget, setDeleteDialogTarget] = useState<\"selection\" | StorageFile | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n // New folder\n const [newFolderDialogOpen, setNewFolderDialogOpen] = useState(false);\n const [newFolderName, setNewFolderName] = useState(\"\");\n const [creatingFolder, setCreatingFolder] = useState(false);\n const apiConfig = useApiConfig();\n const apiBase = useApiBase();\n\n const storageSourceRef = React.useRef(storageSource);\n useEffect(() => {\n storageSourceRef.current = storageSource;\n }, [storageSource]);\n\n /**\n * The latest listing asked for. Only its answer is shown: a listing awaits\n * every file's metadata, so an earlier one — of the source or folder the\n * view has since left — can answer after a later one, and showed that\n * source's files under the one selected, where their actions ran.\n */\n const listingRequestRef = useRef(0);\n\n // ── Fetch directory contents ──\n const fetchContents = useCallback(async (path: string) => {\n const request = ++listingRequestRef.current;\n const isCurrent = () => request === listingRequestRef.current;\n // One source for the whole listing, metadata included.\n const source = storageSourceRef.current;\n setLoading(true);\n setFailure(null);\n try {\n const result = await listAllPages(source, path);\n\n const folderItems: StorageFile[] = result.prefixes.map(ref => ({\n name: ref.name,\n fullPath: ref.fullPath,\n isFolder: true\n }));\n\n // Build file items and fetch metadata for each\n const fileItems: StorageFile[] = await Promise.all(\n result.items.map(async (ref) => {\n try {\n const downloadConfig = await source.getSignedUrl(ref.fullPath);\n return {\n name: ref.name,\n fullPath: ref.fullPath,\n isFolder: false,\n size: downloadConfig.metadata?.size,\n contentType: downloadConfig.metadata?.contentType,\n downloadUrl: downloadConfig.url ?? undefined\n };\n } catch {\n return {\n name: ref.name,\n fullPath: ref.fullPath,\n isFolder: false\n };\n }\n })\n );\n\n if (!isCurrent()) return;\n setFolders(folderItems);\n setFiles(fileItems);\n } catch (e) {\n if (!isCurrent()) return;\n console.error(\"Storage list error:\", e);\n // A refusal from the project's own `storageAuthorize` hook is not a\n // fault — see `load-failure.ts`. Rendering both the same way told\n // a customer with a working project that their storage was broken.\n setFailure(classifyLoadFailure(e));\n } finally {\n if (isCurrent()) setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n // `selectedSourceKey` is a dep so switching backend re-lists; the\n // ref-sync effect above runs first, so `storageSourceRef` is current.\n fetchContents(currentPath);\n }, [currentPath, fetchContents, selectedSourceKey]);\n\n /** A link to `file`, minted now through the source being browsed — see {@link FileLink}. */\n const mintFileLink = useCallback(async (file: StorageFile): Promise<FileLink | null> =>\n fileLinkOf(await storageSourceRef.current.getSignedUrl(file.fullPath), file.fullPath), []);\n\n /** Open `file` in the preview, with a link minted for it now. */\n const openPreview = useCallback((file: StorageFile) => {\n const request = ++linkRequestRef.current;\n setSelectedFile(file);\n setSelectedLink(null);\n mintFileLink(file)\n .then(link => { if (request === linkRequestRef.current) setSelectedLink(link); })\n .catch(() => { if (request === linkRequestRef.current) setSelectedLink(null); });\n }, [mintFileLink]);\n\n /** A fresh link to the previewed file, for a download or a copy. It replaces the one shown. */\n const refreshPreviewLink = useCallback(async (): Promise<FileLink | null> => {\n if (!selectedFile) return null;\n const request = ++linkRequestRef.current;\n const link = await mintFileLink(selectedFile);\n if (request === linkRequestRef.current) setSelectedLink(link);\n return link;\n }, [selectedFile, mintFileLink]);\n\n // What was previewed and selected belongs to the listing it was picked in.\n const clearSelection = useCallback(() => {\n setSelectedFile(null);\n setSelectedLink(null);\n setSelectedPaths(new Set());\n lastClickedRef.current = null;\n }, []);\n\n // Navigate to path\n //\n // Updates only this view's own parameter and leaves the rest of the query\n // string alone. It used to assign the whole thing (`setSearchParams({})`),\n // which is fine when Studio owns the URL but destructive when it is\n // embedded: the SaaS console keeps the open tab in `?tab=`, so opening a\n // folder erased it and bounced the user out of Storage entirely. The key is\n // namespaced for the same reason — a bare `path` is a name a host app can\n // easily be using for something else.\n const handleNavigate = useCallback((path: string) => {\n setSearchParams(prev => {\n const next = new URLSearchParams(prev);\n if (path) next.set(STORAGE_PATH_PARAM, path);\n else next.delete(STORAGE_PATH_PARAM);\n return next;\n }, { replace: true });\n clearSelection();\n }, [setSearchParams, clearSelection]);\n\n // Switching source keeps the folder path, not the files picked in it: the\n // preview's and the selection's actions run through the selected source,\n // so a file opened in one would be deleted, by its path, from the other.\n const handleSourceChange = useCallback((key: string) => {\n setSelectedSourceKey(key);\n clearSelection();\n }, [clearSelection]);\n\n // Navigate up one level\n const handleNavigateUp = useCallback(() => {\n const parts = currentPath.split(\"/\").filter(Boolean);\n parts.pop();\n handleNavigate(parts.join(\"/\"));\n }, [currentPath, handleNavigate]);\n\n // All items (folders + files) in display order, for shift-range select\n const allItems = useMemo(() => [...folders, ...files], [folders, files]);\n\n // ── Multi-select click handler ──\n const handleItemClick = useCallback((item: StorageFile, e: React.MouseEvent) => {\n const path = item.fullPath;\n if (e.metaKey || e.ctrlKey) {\n // Toggle individual item\n setSelectedPaths(prev => {\n const next = new Set(prev);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n return next;\n });\n lastClickedRef.current = path;\n } else if (e.shiftKey && lastClickedRef.current) {\n // Range select\n const allPaths = allItems.map(i => i.fullPath);\n const anchorIdx = allPaths.indexOf(lastClickedRef.current);\n const currentIdx = allPaths.indexOf(path);\n if (anchorIdx >= 0 && currentIdx >= 0) {\n const [start, end] = anchorIdx < currentIdx ? [anchorIdx, currentIdx] : [currentIdx, anchorIdx];\n setSelectedPaths(prev => {\n const next = new Set(prev);\n for (let i = start; i <= end; i++) next.add(allPaths[i]);\n return next;\n });\n }\n } else {\n // Exclusive select\n setSelectedPaths(new Set([path]));\n lastClickedRef.current = path;\n // Also open preview if it's a file\n if (!item.isFolder) {\n openPreview(item);\n } else {\n setSelectedFile(null);\n setSelectedLink(null);\n }\n }\n }, [allItems, openPreview]);\n\n // Double-click: open folder or preview file\n const handleItemDoubleClick = useCallback((item: StorageFile) => {\n if (item.isFolder) {\n handleNavigate(item.fullPath);\n } else {\n openPreview(item);\n }\n }, [handleNavigate, openPreview]);\n\n // Upload files\n const handleUpload = useCallback(async (uploadFiles: File[]) => {\n for (const file of uploadFiles) {\n const key = currentPath ? `${currentPath}/${file.name}` : file.name;\n await storageSourceRef.current.putObject({\n file,\n key\n });\n }\n snackbarController.open({\n type: \"success\",\n message: t(\"studio_storage_files_uploaded\", { count: uploadFiles.length })\n });\n await fetchContents(currentPath);\n }, [currentPath, snackbarController, fetchContents, t]);\n\n // Create new folder\n const handleCreateFolder = useCallback(async () => {\n if (!newFolderName.trim() || !apiConfig?.apiUrl) return;\n\n // Validate folder name\n const name = newFolderName.trim();\n if (name.includes(\"/\") || name.includes(\"\\\\\")) {\n snackbarController.open({ type: \"error\",\nmessage: t(\"studio_storage_folder_name_has_slash\") });\n return;\n }\n\n // Check if folder already exists\n const existingFolder = folders.find(f => f.name === name);\n if (existingFolder) {\n snackbarController.open({ type: \"error\",\nmessage: t(\"studio_storage_folder_exists\", { name }) });\n return;\n }\n\n setCreatingFolder(true);\n try {\n const folderPath = currentPath ? `default/${currentPath}/${name}` : `default/${name}`;\n const token = apiConfig.getAuthToken ? await apiConfig.getAuthToken() : null;\n const response = await fetch(`${apiBase}/storage/folder`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token ? { \"Authorization\": `Bearer ${token}` } : {})\n },\n // The selected source, as every other action here routes by\n // it: without `storageId` the folder went to the default\n // backend whichever one was being browsed.\n body: JSON.stringify({\n path: folderPath,\n ...(selectedSourceKey !== DEFAULT_STORAGE_SOURCE_KEY ? { storageId: selectedSourceKey } : {})\n })\n });\n\n if (!response.ok) {\n const err = await response.json().catch(() => ({ error: t(\"studio_storage_create_folder_failed\") }));\n throw new Error(err.error || t(\"studio_storage_create_folder_failed\"));\n }\n\n snackbarController.open({ type: \"success\",\nmessage: t(\"studio_storage_folder_created\", { name }) });\n setNewFolderDialogOpen(false);\n setNewFolderName(\"\");\n await fetchContents(currentPath);\n } catch (e) {\n snackbarController.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally {\n setCreatingFolder(false);\n }\n }, [newFolderName, currentPath, apiConfig, apiBase, snackbarController, fetchContents, folders, selectedSourceKey, t]);\n\n // Drag-and-drop on main view\n const handleDropFiles = useCallback(async (droppedFiles: File[]) => {\n if (droppedFiles.length === 0) return;\n try {\n for (const file of droppedFiles) {\n const key = currentPath ? `${currentPath}/${file.name}` : file.name;\n await storageSourceRef.current.putObject({ file,\nkey });\n }\n snackbarController.open({\n type: \"success\",\n message: t(\"studio_storage_files_uploaded\", { count: droppedFiles.length })\n });\n await fetchContents(currentPath);\n } catch (e) {\n snackbarController.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n }\n }, [currentPath, snackbarController, fetchContents, t]);\n\n const {\n getRootProps: getDropRootProps,\n getInputProps: getDropInputProps,\n isDragActive\n } = useDropzone({\n onDrop: handleDropFiles,\n noClick: true,\n noKeyboard: true,\n noDragEventsBubbling: true\n });\n\n /**\n * Delete files and folders from the source being browsed, then read back\n * the folder they were listed in and return the ones still there.\n *\n * Nothing is reported deleted on the strength of the requests alone: a\n * delete can answer success and leave the object behind — a directory that\n * is not empty yet, a folder marker, a hook — and the folder listing is\n * what the user sees next. One source for the whole operation, taken when\n * it starts.\n */\n const deleteItems = useCallback(async (items: StorageFile[], folder: string): Promise<StorageFile[]> => {\n const source = storageSourceRef.current;\n for (const item of items) {\n if (item.isFolder) {\n await deleteFolderRecursive(source, item.fullPath);\n } else {\n await source.deleteObject(item.fullPath);\n }\n }\n const listed = await listAllPages(source, folder);\n const stillListed = new Set([...listed.items, ...listed.prefixes].map(ref => ref.fullPath));\n return items.filter(item => stillListed.has(item.fullPath));\n }, []);\n\n /** What a delete left behind, said as the error it is rather than a success. */\n const reportStillThere = useCallback((remaining: StorageFile[]) => {\n snackbarController.open({\n type: \"error\",\n message: t(\"studio_storage_delete_incomplete\", {\n count: remaining.length,\n names: remaining.map(item => item.name).join(\", \")\n })\n });\n }, [snackbarController, t]);\n\n // Delete a single file\n const handleDeleteFile = useCallback(async (file: StorageFile) => {\n try {\n const remaining = await deleteItems([file], currentPath);\n if (remaining.length > 0) {\n reportStillThere(remaining);\n } else {\n snackbarController.open({ type: \"success\",\nmessage: t(\"studio_storage_file_deleted\", { name: file.name }) });\n }\n setSelectedFile(null);\n setSelectedLink(null);\n setSelectedPaths(prev => {\n const next = new Set(prev);\n next.delete(file.fullPath);\n return next;\n });\n fetchContents(currentPath);\n } catch (e) {\n snackbarController.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n }\n }, [currentPath, snackbarController, fetchContents, deleteItems, reportStillThere, t]);\n\n // Bulk delete (selected items)\n const handleBulkDelete = useCallback(async () => {\n setDeleting(true);\n try {\n const items = allItems.filter(i => selectedPaths.has(i.fullPath));\n const remaining = await deleteItems(items, currentPath);\n if (remaining.length > 0) {\n reportStillThere(remaining);\n } else {\n snackbarController.open({ type: \"success\",\nmessage: t(\"studio_storage_items_deleted\", { count: items.length }) });\n }\n setSelectedPaths(new Set());\n setSelectedFile(null);\n setSelectedLink(null);\n await fetchContents(currentPath);\n } catch (e) {\n snackbarController.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally {\n setDeleting(false);\n setDeleteDialogOpen(false);\n setDeleteDialogTarget(null);\n }\n }, [allItems, selectedPaths, currentPath, snackbarController, fetchContents, deleteItems, reportStillThere, t]);\n\n // Confirm delete for a single file or folder\n const handleConfirmDeleteItem = useCallback(async () => {\n if (!deleteDialogTarget || deleteDialogTarget === \"selection\") return;\n setDeleting(true);\n try {\n const remaining = await deleteItems([deleteDialogTarget], currentPath);\n if (remaining.length > 0) {\n reportStillThere(remaining);\n } else {\n snackbarController.open({ type: \"success\",\nmessage: deleteDialogTarget.isFolder\n ? t(\"studio_storage_folder_deleted\", { name: deleteDialogTarget.name })\n : t(\"studio_storage_file_deleted\", { name: deleteDialogTarget.name }) });\n }\n if (!deleteDialogTarget.isFolder && selectedFile?.fullPath === deleteDialogTarget.fullPath) {\n setSelectedFile(null);\n setSelectedLink(null);\n }\n setSelectedPaths(prev => {\n const next = new Set(prev);\n next.delete(deleteDialogTarget.fullPath);\n return next;\n });\n await fetchContents(currentPath);\n } catch (e) {\n snackbarController.open({ type: \"error\",\nmessage: e instanceof Error ? e.message : String(e) });\n } finally {\n setDeleting(false);\n setDeleteDialogOpen(false);\n setDeleteDialogTarget(null);\n }\n }, [deleteDialogTarget, selectedFile, currentPath, snackbarController, fetchContents, deleteItems, reportStillThere, t]);\n\n // Select all / deselect\n const handleSelectAll = useCallback(() => {\n if (selectedPaths.size === allItems.length) {\n setSelectedPaths(new Set());\n } else {\n setSelectedPaths(new Set(allItems.map(i => i.fullPath)));\n }\n }, [allItems, selectedPaths]);\n\n // ── Keyboard shortcuts ──\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n // Don't handle shortcuts when a dialog is open\n if (deleteDialogOpen || uploadDialogOpen || newFolderDialogOpen) return;\n // Nor a key a layer above already acted on — Escape that closed a\n // menu must not also clear the selection.\n if (isKeyHandled(e)) return;\n // Cmd/Ctrl+A: select all\n if ((e.metaKey || e.ctrlKey) && e.key === \"a\") {\n e.preventDefault();\n handleSelectAll();\n }\n // Escape: deselect\n if (e.key === \"Escape\") {\n setSelectedPaths(new Set());\n setSelectedFile(null);\n setSelectedLink(null);\n }\n // Delete / Backspace: delete selected\n if ((e.key === \"Delete\" || e.key === \"Backspace\") && selectedPaths.size > 0 && !e.metaKey && !e.ctrlKey) {\n // Don't trigger if user is typing in an input\n if ((e.target as HTMLElement)?.tagName === \"INPUT\" || (e.target as HTMLElement)?.tagName === \"TEXTAREA\") return;\n e.preventDefault();\n setDeleteDialogTarget(\"selection\");\n setDeleteDialogOpen(true);\n }\n };\n window.addEventListener(\"keydown\", handler);\n return () => window.removeEventListener(\"keydown\", handler);\n }, [handleSelectAll, selectedPaths, deleteDialogOpen, uploadDialogOpen, newFolderDialogOpen]);\n\n // Handle refresh\n const handleRefresh = useCallback(() => {\n fetchContents(currentPath);\n }, [currentPath, fetchContents]);\n\n const segments = breadcrumbSegments(currentPath, t(\"studio_storage_root\"));\n\n\n // ── Render file grid/list ──\n const renderContents = () => {\n if (loading) {\n return (\n <div className=\"flex-grow flex items-center justify-center\">\n <div className=\"text-center\">\n <CircularProgress size=\"medium\"/>\n <Typography variant=\"body2\" className=\"mt-4 text-text-secondary dark:text-text-secondary-dark font-mono tracking-tight animate-pulse\">\n {t(\"studio_storage_loading\")}\n </Typography>\n </div>\n </div>\n );\n }\n\n if (failure?.kind === \"denied\") {\n return (\n <div className=\"flex-grow flex items-center justify-center p-6 overflow-auto\">\n <div className=\"max-w-md text-center\">\n <Typography variant=\"subtitle2\" className=\"block\">\n {t(\"studio_storage_denied_title\")}\n </Typography>\n <Typography variant=\"body2\" className=\"text-text-secondary dark:text-text-secondary-dark block mt-2\">\n {t(\"studio_storage_denied_hint\")}\n </Typography>\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark block mt-3 font-mono break-all\">\n {failure.detail}\n </Typography>\n </div>\n </div>\n );\n }\n\n if (failure) {\n return (\n <div className=\"flex-grow flex items-center justify-center p-6 overflow-auto\">\n <ErrorView\n title={t(\"studio_storage_read_failed\")}\n error={failure.detail}\n onRetry={failure.retryable ? handleRefresh : undefined}\n />\n </div>\n );\n }\n\n\n if (allItems.length === 0) {\n return (\n <div className=\"flex-grow flex items-center justify-center text-text-disabled dark:text-text-disabled-dark\">\n <div className=\"text-center\">\n <svg className=\"w-12 h-12 mx-auto mb-4 opacity-50\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1} d=\"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z\"/>\n </svg>\n <Typography variant=\"body2\">\n {t(\"studio_storage_empty\")}\n </Typography>\n <div className=\"flex items-center gap-2 mt-3\">\n <Button variant=\"text\" onClick={() => {\n setNewFolderName(\"\");\n setNewFolderDialogOpen(true);\n }}>\n <FolderPlusIcon size={iconSize.smallest}/>\n {t(\"studio_storage_new_folder\")}\n </Button>\n <Button onClick={() => setUploadDialogOpen(true)}>\n <PlusIcon size={iconSize.smallest}/>\n {t(\"studio_storage_upload_files\")}\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n if (viewMode === \"list\") {\n return (\n <div className=\"flex-grow overflow-auto\">\n <table className=\"w-full\">\n <thead>\n <tr className={cls(\"border-b text-left text-[10px] uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark\", defaultBorderMixin)}>\n <th className=\"pl-3 pr-0 py-2 w-8\">\n <Checkbox\n size=\"small\"\n checked={allItems.length > 0 && selectedPaths.size === allItems.length}\n indeterminate={selectedPaths.size > 0 && selectedPaths.size < allItems.length}\n onCheckedChange={handleSelectAll}\n />\n </th>\n <th className=\"px-2 py-2 font-semibold\">{t(\"studio_storage_name\")}</th>\n <th className=\"px-4 py-2 font-semibold w-24\">{t(\"studio_storage_type\")}</th>\n <th className=\"px-4 py-2 font-semibold w-24 text-right\">{t(\"studio_storage_size\")}</th>\n <th className=\"px-2 py-2 w-10\"/>\n </tr>\n </thead>\n <tbody>\n {folders.map(folder => {\n const isChecked = selectedPaths.has(folder.fullPath);\n return (\n <tr\n key={folder.fullPath}\n data-storage-item\n className={cls(\n \"cursor-pointer transition-colors border-b group\",\n defaultBorderMixin,\n isChecked\n ? \"bg-primary/5 dark:bg-primary/10\"\n : \"hover:bg-surface-hover\"\n )}\n onClick={(e) => handleItemClick(folder, e)}\n onDoubleClick={() => handleItemDoubleClick(folder)}\n >\n <td className=\"pl-3 pr-0 py-2.5\" onClick={(e) => e.stopPropagation()}>\n <Checkbox\n size=\"small\"\n checked={isChecked}\n onCheckedChange={() => {\n setSelectedPaths(prev => {\n const next = new Set(prev);\n if (next.has(folder.fullPath)) next.delete(folder.fullPath);\n else next.add(folder.fullPath);\n return next;\n });\n }}\n />\n </td>\n <td className=\"px-2 py-2.5\">\n <div className=\"flex items-center gap-2\">\n <FolderIcon size={iconSize.smallest} className=\"text-amber-500 dark:text-amber-400 shrink-0\"/>\n <Typography variant=\"body2\" className=\"text-[13px] font-medium truncate\">\n {folder.name}\n </Typography>\n </div>\n </td>\n <td className=\"px-4 py-2.5\">\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark\">\n {t(\"studio_storage_folder\")}\n </Typography>\n </td>\n <td className=\"px-4 py-2.5 text-right\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark\">\n —\n </Typography>\n </td>\n <td className=\"px-2 py-2.5\"/>\n </tr>\n );\n })}\n {files.map(file => {\n const FileIconComp = getFileIcon(file.contentType);\n const isChecked = selectedPaths.has(file.fullPath);\n return (\n <tr\n key={file.fullPath}\n data-storage-item\n className={cls(\n \"cursor-pointer transition-colors border-b group\",\n defaultBorderMixin,\n isChecked\n ? \"bg-primary/5 dark:bg-primary/10\"\n : \"hover:bg-surface-hover\"\n )}\n onClick={(e) => handleItemClick(file, e)}\n onDoubleClick={() => handleItemDoubleClick(file)}\n >\n <td className=\"pl-3 pr-0 py-2.5\" onClick={(e) => e.stopPropagation()}>\n <Checkbox\n size=\"small\"\n checked={isChecked}\n onCheckedChange={() => {\n setSelectedPaths(prev => {\n const next = new Set(prev);\n if (next.has(file.fullPath)) next.delete(file.fullPath);\n else next.add(file.fullPath);\n return next;\n });\n }}\n />\n </td>\n <td className=\"px-2 py-2.5\">\n <div className=\"flex items-center gap-2\">\n <FileIconComp size={iconSize.smallest} className=\"text-surface-accent-400 shrink-0\"/>\n <Typography variant=\"body2\" className=\"text-[13px] truncate\">\n {file.name}\n </Typography>\n </div>\n </td>\n <td className=\"px-4 py-2.5\">\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark\">\n {getExtension(file.name) || file.contentType?.split(\"/\")[1]?.toUpperCase() || \"—\"}\n </Typography>\n </td>\n <td className=\"px-4 py-2.5 text-right\">\n <Typography variant=\"caption\" className=\"text-text-secondary dark:text-text-secondary-dark font-mono text-[11px]\">\n {file.size !== undefined ? formatFileSize(file.size) : \"—\"}\n </Typography>\n </td>\n <td className=\"px-2 py-2.5\" onClick={(e) => e.stopPropagation()}>\n <IconButton\n size=\"smallest\"\n className=\"opacity-0 group-hover:opacity-100 transition-opacity\"\n onClick={() => {\n setDeleteDialogTarget(file);\n setDeleteDialogOpen(true);\n }}\n >\n <Trash2Icon size={14}/>\n </IconButton>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n }\n\n // Grid view\n return (\n <div className=\"flex-grow overflow-auto p-4\">\n {/* Folder cards */}\n {folders.length > 0 && (\n <div className=\"mb-4\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider font-semibold text-text-disabled dark:text-text-disabled-dark mb-2 block\">\n {t(\"studio_storage_folders\")}\n </Typography>\n <div className=\"grid gap-3 grid-cols-[repeat(auto-fill,minmax(140px,1fr))]\">\n {folders.map(folder => {\n const isChecked = selectedPaths.has(folder.fullPath);\n return (\n <div\n key={folder.fullPath}\n data-storage-item\n className={cls(\n \"rounded-lg p-3 cursor-pointer border\",\n \"transition-colors duration-150\",\n defaultBorderMixin,\n \"hover:bg-surface-hover hover:shadow-sm\",\n \"flex items-center gap-2\",\n isChecked && \"ring-2 ring-primary bg-primary/5 dark:bg-primary/10\"\n )}\n onClick={(e) => handleItemClick(folder, e)}\n onDoubleClick={() => handleItemDoubleClick(folder)}\n >\n <FolderIcon size={iconSize.smallest} className=\"text-amber-500 dark:text-amber-400 shrink-0\"/>\n <Typography variant=\"body2\" className=\"text-[13px] font-medium truncate\">\n {folder.name}\n </Typography>\n </div>\n );\n })}\n </div>\n </div>\n )}\n\n {/* FileIcon cards */}\n {files.length > 0 && (\n <div>\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider font-semibold text-text-disabled dark:text-text-disabled-dark mb-2 block\">\n {t(\"studio_storage_files_heading\", { count: files.length })}\n </Typography>\n <div className=\"grid gap-3 grid-cols-[repeat(auto-fill,minmax(140px,1fr))]\">\n {files.map(file => {\n const FileIconComp = getFileIcon(file.contentType);\n const ext = getExtension(file.name)?.toLowerCase() || \"\";\n const isImage = file.contentType?.startsWith(\"image/\") || [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\"].includes(ext);\n const isChecked = selectedPaths.has(file.fullPath);\n\n return (\n <div\n key={file.fullPath}\n data-storage-item\n className={cls(\n \"rounded-lg overflow-hidden cursor-pointer border\",\n \"transition-shadow duration-150\",\n defaultBorderMixin,\n \"hover:shadow-md\",\n isChecked && \"ring-2 ring-primary\"\n )}\n onClick={(e) => handleItemClick(file, e)}\n onDoubleClick={() => handleItemDoubleClick(file)}\n >\n {/* Thumbnail or icon */}\n <div className=\"aspect-square relative overflow-hidden bg-surface-raised flex items-center justify-center\">\n {isImage && file.downloadUrl ? (\n <img\n src={file.downloadUrl}\n alt={file.name}\n className=\"w-full h-full object-cover\"\n loading=\"lazy\"\n />\n ) : (\n <FileIconComp className=\"text-surface-accent-400 dark:text-surface-accent-500 w-8 h-8\"/>\n )}\n\n {/* Extension badge */}\n {getExtension(file.name) && (\n <div className=\"absolute bottom-1.5 right-1.5 px-1.5 py-0.5 rounded text-[9px] font-semibold uppercase bg-black/50 text-white backdrop-blur-sm\">\n {getExtension(file.name)}\n </div>\n )}\n </div>\n\n {/* Name & size */}\n <div className=\"p-2.5\">\n <Typography variant=\"body2\" className=\"text-[12px] font-medium truncate text-surface-900 dark:text-white\">\n {file.name}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate block mt-0.5 text-[11px]\">\n {file.size !== undefined ? formatFileSize(file.size) : \"—\"}\n </Typography>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </div>\n );\n };\n\n return (\n <div className=\"flex h-full w-full bg-surface-card overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <div className=\"flex h-full w-full\">\n {/* Main content */}\n <div className=\"flex-grow flex flex-col min-w-0 h-full\">\n {/* Toolbar */}\n <div className={cls(\"flex items-center justify-between pr-2 border-b bg-surface-card shrink-0 h-10\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-1.5 flex-grow overflow-hidden px-3 py-2\">\n {/* Breadcrumbs — always visible */}\n {currentPath && (\n <Tooltip title={t(\"studio_storage_go_up\")}>\n <IconButton aria-label={t(\"studio_storage_go_up\")} size=\"small\" onClick={handleNavigateUp}>\n <ArrowLeftIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n )}\n <div className=\"flex items-center gap-0.5 overflow-x-auto no-scrollbar\">\n {segments.map((seg, i) => (\n <React.Fragment key={seg.path}>\n {i > 0 && (\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark mx-0.5\">/</Typography>\n )}\n <Button\n variant=\"text\"\n size=\"small\"\n className={cls(\n \"px-1.5 py-0.5 min-h-0 min-w-0 h-6 text-xs whitespace-nowrap normal-case font-normal\",\n i === segments.length - 1\n ? \"text-text-primary dark:text-text-primary-dark font-medium\"\n : \"text-text-secondary dark:text-text-secondary-dark\"\n )}\n onClick={() => handleNavigate(seg.path)}\n >\n {seg.label}\n </Button>\n </React.Fragment>\n ))}\n </div>\n\n <div className=\"flex-1\"/>\n\n {/* Selection actions or file count */}\n {selectedPaths.size > 0 ? (\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <Typography variant=\"body2\" className=\"text-[13px] font-medium whitespace-nowrap\">\n {t(\"studio_storage_selected_count\", { count: selectedPaths.size })}\n </Typography>\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => {\n setDeleteDialogTarget(\"selection\");\n setDeleteDialogOpen(true);\n }}\n >\n <Trash2Icon size={14} className=\"mr-1\"/>\n {t(\"studio_storage_delete\")}\n </Button>\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => {\n setSelectedPaths(new Set());\n setSelectedFile(null);\n setSelectedLink(null);\n }}\n >\n <XIcon size={14} className=\"mr-1\"/>\n {t(\"studio_storage_deselect\")}\n </Button>\n </div>\n ) : !loading ? (\n <Chip size=\"small\" className=\"shrink-0 text-[10px]\">\n {t(\"studio_storage_file_count\", { count: files.length })}\n {folders.length > 0 ? `, ${t(\"studio_storage_folder_count\", { count: folders.length })}` : \"\"}\n </Chip>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center justify-end gap-1.5 pr-1\">\n\n {/* Backend picker — only shown when more than one storage source is available */}\n {sourceKeys.length > 1 && (\n <Select\n size=\"small\"\n position=\"item-aligned\"\n value={selectedSourceKey}\n onValueChange={(value) => {\n if (value && value !== selectedSourceKey) handleSourceChange(value);\n }}\n renderValue={(key) => {\n const label = storageSources.registry[key]?.label;\n return label ?? (key === DEFAULT_STORAGE_SOURCE_KEY ? t(\"studio_storage_default_source\") : key);\n }}>\n {sourceKeys.map((key) => (\n <SelectItem key={key} value={key}>\n {storageSources.registry[key]?.label\n ?? (key === DEFAULT_STORAGE_SOURCE_KEY ? t(\"studio_storage_default_source\") : key)}\n </SelectItem>\n ))}\n </Select>\n )}\n\n <Tooltip title={t(\"studio_storage_grid_view\")}>\n <IconButton aria-label={t(\"studio_storage_grid_view\")}\n size=\"small\"\n onClick={() => setViewMode(\"grid\")}\n className={cls(viewMode === \"grid\" && \"bg-surface-raised\")}\n >\n <LayoutGridIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n <Tooltip title={t(\"studio_storage_list_view\")}>\n <IconButton aria-label={t(\"studio_storage_list_view\")}\n size=\"small\"\n onClick={() => setViewMode(\"list\")}\n className={cls(viewMode === \"list\" && \"bg-surface-raised\")}\n >\n <ListIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n\n <div className={cls(\"h-4 w-px mx-0.5\", defaultBorderMixin, \"bg-surface-raised\")}/>\n\n <Tooltip title={t(\"studio_storage_refresh\")}>\n <IconButton aria-label={t(\"studio_storage_refresh\")} size=\"small\" onClick={handleRefresh} disabled={loading}>\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n\n <Tooltip title={t(\"studio_storage_new_folder\")}>\n <IconButton aria-label={t(\"studio_storage_new_folder\")}\n size=\"small\"\n onClick={() => {\n setNewFolderName(\"\");\n setNewFolderDialogOpen(true);\n }}\n >\n <FolderPlusIcon size={iconSize.smallest}/>\n </IconButton>\n </Tooltip>\n <Button\n size=\"small\"\n color=\"primary\"\n onClick={() => setUploadDialogOpen(true)}\n >\n <UploadCloudIcon size={iconSize.smallest} className=\"mr-1\"/>\n {t(\"studio_storage_upload\")}\n </Button>\n </div>\n </div>\n\n {/* File grid / list — drop zone */}\n <div {...getDropRootProps()}\n className=\"flex-grow flex flex-col overflow-hidden min-h-0 relative\"\n onClick={(e) => {\n const target = e.target as HTMLElement;\n if (!target.closest(\"[data-storage-item]\") && selectedPaths.size > 0) {\n setSelectedPaths(new Set());\n setSelectedFile(null);\n setSelectedLink(null);\n }\n }}\n >\n <input {...getDropInputProps()} />\n {renderContents()}\n {/* Drag overlay */}\n {isDragActive && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center bg-primary/5 dark:bg-primary/10 backdrop-blur-[2px]\">\n <div className=\"flex flex-col items-center gap-2 p-6 rounded-xl border-2 border-dashed border-primary bg-surface-scrim\">\n <UploadCloudIcon className=\"w-10 h-10 text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"text-primary font-semibold\">\n {t(\"studio_storage_drop_to_upload\")}\n </Typography>\n <Typography variant=\"caption\" color=\"secondary\">\n {t(\"studio_storage_upload_to\")} /{currentPath || t(\"studio_storage_root_folder\")}\n </Typography>\n </div>\n </div>\n )}\n </div>\n\n {/* Status bar */}\n <div className={cls(\"px-4 py-1.5 border-t bg-surface-sheet flex items-center justify-between shrink-0\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-4 text-[11px]\">\n <span className=\"text-text-disabled dark:text-text-disabled-dark font-semibold uppercase tracking-tighter\">\n {t(\"studio_storage_path\")}\n </span>\n <span className=\"font-mono text-text-secondary dark:text-text-secondary-dark\">\n /{currentPath || \"\"}\n </span>\n </div>\n {selectedPaths.size > 0 ? (\n <div className=\"text-[11px] text-text-secondary dark:text-text-secondary-dark\">\n {t(\"studio_storage_items_selected\", { count: selectedPaths.size })}\n </div>\n ) : selectedFile ? (\n <div className=\"text-[11px] text-text-secondary dark:text-text-secondary-dark\">\n {t(\"studio_storage_selected_label\")} <span className=\"font-mono\">{selectedFile.name}</span>\n </div>\n ) : null}\n </div>\n </div>\n\n {/* Preview panel */}\n {selectedFile && (\n <div className=\"w-80 lg:w-96 shrink-0\">\n <FilePreviewPanel\n file={selectedFile}\n link={selectedLink}\n mintLink={refreshPreviewLink}\n onClose={() => {\n setSelectedFile(null);\n setSelectedLink(null);\n }}\n onDelete={() => handleDeleteFile(selectedFile)}\n />\n </div>\n )}\n </div>\n\n {/* Upload Dialog */}\n <UploadDialog\n open={uploadDialogOpen}\n currentPath={currentPath}\n onClose={() => setUploadDialogOpen(false)}\n onUpload={handleUpload}\n />\n\n {/* Delete confirmation dialog */}\n <Dialog\n open={deleteDialogOpen}\n onOpenChange={(open) => {\n if (!open && !deleting) {\n setDeleteDialogOpen(false);\n setDeleteDialogTarget(null);\n }\n }}\n >\n <DialogTitle hidden>{t(\"studio_storage_delete_confirmation_title\")}</DialogTitle>\n <DialogContent>\n <Typography variant=\"subtitle1\" className=\"font-semibold mb-2\">\n {deleteDialogTarget === \"selection\"\n ? t(\"studio_storage_delete_items_question\", { count: selectedPaths.size })\n : deleteDialogTarget?.isFolder\n ? t(\"studio_storage_delete_folder_question\", { name: deleteDialogTarget.name })\n : deleteDialogTarget\n ? t(\"studio_storage_delete_file_question\")\n : t(\"studio_storage_delete_question\")}\n </Typography>\n <Typography variant=\"body2\" color=\"secondary\">\n {deleteDialogTarget === \"selection\"\n ? t(\"studio_storage_delete_selection_body\")\n : deleteDialogTarget && !deleteDialogTarget.isFolder\n ? t(\"studio_storage_delete_file_body\", { name: deleteDialogTarget.name })\n : t(\"studio_storage_delete_folder_body\")}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"text\"\n onClick={() => {\n setDeleteDialogOpen(false);\n setDeleteDialogTarget(null);\n }}\n disabled={deleting}\n >\n {t(\"studio_storage_cancel\")}\n </Button>\n <LoadingButton\n color=\"error\"\n loading={deleting}\n onClick={deleteDialogTarget === \"selection\" ? handleBulkDelete : handleConfirmDeleteItem}\n >\n <Trash2Icon size={14} className=\"mr-1\"/>\n {t(\"studio_storage_delete\")}\n </LoadingButton>\n </DialogActions>\n </Dialog>\n\n {/* New Folder Dialog */}\n <Dialog\n open={newFolderDialogOpen}\n onOpenChange={(open) => {\n if (!open && !creatingFolder) {\n setNewFolderDialogOpen(false);\n setNewFolderName(\"\");\n }\n }}\n >\n <DialogTitle hidden>{t(\"studio_storage_new_folder_title\")}</DialogTitle>\n <DialogContent>\n <TextField\n autoFocus\n size=\"small\"\n label={t(\"studio_storage_folder_name\")}\n value={newFolderName}\n onChange={(e) => setNewFolderName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && newFolderName.trim()) {\n e.preventDefault();\n handleCreateFolder();\n }\n }}\n disabled={creatingFolder}\n placeholder={t(\"studio_storage_folder_name_placeholder\")}\n />\n {currentPath && (\n <Typography variant=\"caption\" color=\"secondary\" className=\"mt-2\">\n {t(\"studio_storage_created_in\")} <span className=\"font-mono\">/{currentPath}/</span>\n </Typography>\n )}\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"text\"\n onClick={() => {\n setNewFolderDialogOpen(false);\n setNewFolderName(\"\");\n }}\n disabled={creatingFolder}\n >\n {t(\"studio_storage_cancel\")}\n </Button>\n <LoadingButton\n color=\"primary\"\n loading={creatingFolder}\n disabled={!newFolderName.trim()}\n onClick={handleCreateFolder}\n >\n <FolderPlusIcon size={14} className=\"mr-1\"/>\n {t(\"studio_storage_create\")}\n </LoadingButton>\n </DialogActions>\n </Dialog>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import type { ParsedEndpoint } from "./types.js";
3
- import type { User } from "@rebasepro/types";
3
+ import { type User } from "@rebasepro/types";
4
4
  interface TryItPanelProps {
5
5
  endpoint: ParsedEndpoint;
6
6
  apiUrl: string;
@@ -11,6 +11,11 @@ interface TryItPanelProps {
11
11
  /**
12
12
  * Interactive "Try It" panel that lets the user execute API requests
13
13
  * using their current JWT token directly from the Studio.
14
+ *
15
+ * An administrator can run a request as another user: it is sent with their
16
+ * own token and the {@link IMPERSONATE_HEADER} header, and the server runs it
17
+ * as that user — their uid and roles, under row-level security — or refuses
18
+ * it. It never runs as the administrator instead.
14
19
  */
15
20
  export declare function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath }: TryItPanelProps): React.JSX.Element;
16
21
  export {};
@@ -1,6 +1,25 @@
1
1
  import React from "react";
2
- import type { ApiKeyWithSecret } from "@rebasepro/types";
3
- export declare function CreateApiKeyDialog({ onClose, onCreated }: {
2
+ import { type ApiKeyKind, type ApiKeyWithSecret } from "@rebasepro/types";
3
+ import { type ScopeListing, type ScopePlane } from "./scopes.js";
4
+ /** The scope catalogue as the panel holds it: still loading, read, or not readable. */
5
+ export type ScopeListingState = {
6
+ status: "loading";
7
+ } | {
8
+ status: "ready";
9
+ listing: ScopeListing;
10
+ } | {
11
+ status: "failed";
12
+ message: string;
13
+ };
14
+ export declare function PlaneIcon({ plane, className }: {
15
+ plane: ScopePlane;
16
+ className?: string;
17
+ }): React.JSX.Element;
18
+ export declare function CreateApiKeyDialog({ kind, scopeListing, onRetryScopes, onClose, onCreated }: {
19
+ /** A service key for the project, or a personal key for the signed-in account. */
20
+ kind: ApiKeyKind;
21
+ scopeListing: ScopeListingState;
22
+ onRetryScopes: () => void;
4
23
  onClose: () => void;
5
24
  onCreated: (key: ApiKeyWithSecret) => void;
6
25
  }): React.JSX.Element;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * How the key panel words scopes: plane headings, "every collection", the
3
+ * title over a minting refusal. One place, so the detail panel, the create
4
+ * dialog and the secret confirmation cannot drift apart.
5
+ *
6
+ * Every string is a translation key. A built-in scope's label and description
7
+ * are keys too, derived from the scope (`data:read` →
8
+ * `studio_scope_data_read_label`); an app's own scopes are worded as the app
9
+ * declared them, in the server's catalogue (`GET /auth/scopes`).
10
+ *
11
+ * @module
12
+ */
13
+ import { type ScopeSummary } from "@rebasepro/types";
14
+ import type { HeldScope, KeyErrorCode, ScopePlane } from "./scopes.js";
15
+ /** The panel's `t`, from `useTranslation()`. */
16
+ export type Translate = (key: string, vars?: Record<string, string | number>) => string;
17
+ /** `data:read` → `studio_scope_data_read_label`. */
18
+ export declare function scopeLabelKey(scope: string): string;
19
+ /** `data:read` → `studio_scope_data_read_description`. */
20
+ export declare function scopeDescriptionKey(scope: string): string;
21
+ /**
22
+ * The catalogue in the panel's language. Built-in scopes are translated; an
23
+ * app scope keeps the label its app declared, and a built-in scope this panel
24
+ * has no key for (one the server added since) keeps the server's wording
25
+ * rather than rendering blank or as a key.
26
+ */
27
+ export declare function localizeScopes(t: Translate, catalogue: readonly ScopeSummary[]): ScopeSummary[];
28
+ export declare function planeLabel(t: Translate, plane: ScopePlane): string;
29
+ export declare function planeHint(t: Translate, plane: ScopePlane): string;
30
+ /** "Every collection", "Every storage source", "Every function", "Any project". */
31
+ export declare function everyTargetLabel(t: Translate, targetKind: string): string;
32
+ /** One target as a person reads it: the default storage source is "Default bucket", not `(default)`. */
33
+ export declare function targetLabel(t: Translate, targetKind: string | undefined, target: string): string;
34
+ /** A held scope as one line: "Read data — posts, authors", "Read data — every collection", "Read server logs". */
35
+ export declare function heldScopeLine(t: Translate, held: HeldScope): string;
36
+ /** The title over a refused create; the server's own message goes underneath. */
37
+ export declare function keyErrorTitle(t: Translate, code: KeyErrorCode | null): string;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The pure half of the API-key panel: reading a key's scopes back as groups a
3
+ * person can scan, and turning the create dialog's picker into the scope list
4
+ * the server mints.
5
+ *
6
+ * The vocabulary itself — what a scope is, which ones take a target, which
7
+ * plane each belongs to — is `@rebasepro/types`' `scopes.ts`, the same module
8
+ * the server enforces with. Nothing here re-derives it; this file only shapes
9
+ * it for the screen, so it can be tested without rendering one.
10
+ *
11
+ * @module
12
+ */
13
+ import { type ScopeSummary } from "@rebasepro/types";
14
+ /** Which plane a scope belongs to, as `GET /auth/scopes` names it. */
15
+ export type ScopePlane = ScopeSummary["plane"];
16
+ /** The planes in the order the panel shows them. */
17
+ export declare const SCOPE_PLANES: readonly ScopePlane[];
18
+ /** One scope a key holds, with every target it holds it on. */
19
+ export interface HeldScope {
20
+ /** `resource:action`, without a target. */
21
+ scope: string;
22
+ label: string;
23
+ description: string;
24
+ plane: ScopePlane;
25
+ /** What a target names — `collection`, `bucket`, `function`, or an app's own word. */
26
+ targetKind?: string;
27
+ /** `"all"` for the unqualified grant, otherwise the targets it is narrowed to. */
28
+ targets: "all" | string[];
29
+ }
30
+ export interface ScopeGroup {
31
+ plane: ScopePlane;
32
+ scopes: HeldScope[];
33
+ }
34
+ /**
35
+ * A key's scopes, grouped by plane and folded per scope: `data:read:posts` and
36
+ * `data:read:authors` read as one "Read data" row narrowed to two collections,
37
+ * and `data:read` beside them as the same row on every collection.
38
+ *
39
+ * Ordered the way the catalogue lists scopes; a scope the catalogue does not
40
+ * know (an app scope removed since the key was minted) keeps its raw name and
41
+ * lands at the end of the plane it would belong to.
42
+ */
43
+ export declare function groupKeyScopes(scopes: readonly string[], catalogue?: readonly ScopeSummary[]): ScopeGroup[];
44
+ /** Never offered: a key that manages keys could mint its own successor. */
45
+ export declare function isKeyManagementScope(scope: string): boolean;
46
+ /**
47
+ * The catalogue entries a caller may put on a key: the ones they hold, on at
48
+ * least one target, minus key management. The server refuses anything else
49
+ * (`SCOPE_EXCEEDS_CREATOR`, `KEY_MANAGEMENT_SCOPE`), so offering it would only
50
+ * be a way to fail.
51
+ */
52
+ export declare function grantableScopes(catalogue: readonly ScopeSummary[], held: readonly string[]): ScopeSummary[];
53
+ /** For one chosen scope: every target, or only the listed ones. */
54
+ export type TargetChoice = {
55
+ mode: "all";
56
+ } | {
57
+ mode: "some";
58
+ targets: string[];
59
+ };
60
+ /** The chosen scopes, keyed by `resource:action`. A scope absent here is not chosen. */
61
+ export type ScopeSelection = Readonly<Record<string, TargetChoice>>;
62
+ /** `data:read` on everything — the narrowest useful key, and what the dialog opens with. */
63
+ export declare function defaultSelection(grantable: readonly ScopeSummary[]): ScopeSelection;
64
+ /**
65
+ * The scope strings a selection mints, in catalogue order when one is given.
66
+ *
67
+ * A scope on every target is sent unqualified; one narrowed to targets is sent
68
+ * once per target. A scope narrowed to no target at all grants nothing and is
69
+ * left out — {@link incompleteScopes} is how the dialog says so.
70
+ */
71
+ export declare function buildScopeList(selection: ScopeSelection, order?: readonly string[]): string[];
72
+ /** Scopes switched to "only some" with nothing chosen yet. */
73
+ export declare function incompleteScopes(selection: ScopeSelection): string[];
74
+ /** Free text in a target field: commas or whitespace separate entries. */
75
+ export declare function splitList(text: string): string[];
76
+ /**
77
+ * The roles a service key runs as beside `service`: the picked ones, then any
78
+ * typed ones, deduplicated. `service` itself is every service key's identity
79
+ * already, so listing it adds nothing and is dropped, as the server does.
80
+ */
81
+ export declare function combineRoles(picked: readonly string[], typed: string): string[];
82
+ export declare const EXPIRY_CHOICES: readonly ["never", "7d", "30d", "90d", "1y"];
83
+ export type ExpiryChoice = (typeof EXPIRY_CHOICES)[number];
84
+ export declare function isExpiryChoice(value: string): value is ExpiryChoice;
85
+ /** The `expires_at` an expiry choice sends: an ISO instant, or null for never. */
86
+ export declare function expiresAtFor(choice: ExpiryChoice, now?: number): string | null;
87
+ /** The `rate_limit` a rate-limit field sends: a positive integer, or null for the server default. */
88
+ export declare function rateLimitFrom(text: string): number | null;
89
+ /** The minting refusals the dialog names, beside the server's own message. */
90
+ export declare const KEY_ERROR_CODES: readonly ["INVALID_SCOPES", "SCOPE_EXCEEDS_CREATOR", "ROLE_EXCEEDS_CREATOR", "UNKNOWN_SCOPE_TARGET", "KEY_MANAGEMENT_SCOPE"];
91
+ export type KeyErrorCode = (typeof KEY_ERROR_CODES)[number];
92
+ export interface KeyError {
93
+ /** One of {@link KEY_ERROR_CODES}, or null for anything else. */
94
+ code: KeyErrorCode | null;
95
+ /** The server's message, never re-worded. */
96
+ message: string;
97
+ }
98
+ export declare function readKeyError(error: unknown): KeyError;
99
+ /** The backend answered `403 PERSONAL_KEYS_DISABLED`: the app has not switched them on. */
100
+ export declare function isPersonalKeysDisabled(error: unknown): boolean;
101
+ /** What `GET /auth/scopes` answers. */
102
+ export interface ScopeListing {
103
+ scopes: ScopeSummary[];
104
+ held: string[];
105
+ }
106
+ /**
107
+ * Read `GET /auth/scopes`. Null for anything that is not that shape — a backend
108
+ * older than scopes answers this route with something else, and offering a
109
+ * picker built from it would offer scopes the server does not know.
110
+ */
111
+ export declare function readScopeListing(body: unknown): ScopeListing | null;
112
+ /** Function names out of `GET /functions` — `{ functions: [{ name }] }`. */
113
+ export declare function functionNamesFrom(body: unknown): string[];
@@ -7,6 +7,10 @@ export interface AuthSimulationSelectorProps {
7
7
  setSelectedUser: (user: SelectableUser | null) => void;
8
8
  users: SelectableUser[];
9
9
  loading?: boolean;
10
+ /** For a caller that searches `users` on the server — see `UserSelectPopover`. */
11
+ onUserSearchTextChange?: (searchText: string) => void;
12
+ /** How many users that search matched, when `users` is only its first page — see `UserSelectPopover`. */
13
+ userTotalCount?: number;
10
14
  currentUser: SelectableUser | null;
11
15
  }
12
- export declare function AuthSimulationSelector({ authMode, setAuthMode, selectedUser, setSelectedUser, users, loading, currentUser }: AuthSimulationSelectorProps): React.JSX.Element;
16
+ export declare function AuthSimulationSelector({ authMode, setAuthMode, selectedUser, setSelectedUser, users, loading, onUserSearchTextChange, userTotalCount, currentUser }: AuthSimulationSelectorProps): React.JSX.Element;
@@ -52,6 +52,15 @@ export interface LogTail {
52
52
  * a tail with a hole in it that says so beats one that silently lies.
53
53
  */
54
54
  dropped: number;
55
+ /**
56
+ * The server process whose log this is, as its stream names it — `null`
57
+ * until the stream says, and against a server that does not.
58
+ *
59
+ * Each process keeps its own log in memory, so with several replicas (or
60
+ * split runtime roles) this view shows one of them, and a reconnect can
61
+ * land on another.
62
+ */
63
+ instance: string | null;
55
64
  }
56
65
  /**
57
66
  * Tail the server's log buffer.
@@ -0,0 +1,44 @@
1
+ import type { CollectionConfig, PostgresPolicy } from "@rebasepro/types";
2
+ /** A part of a policy the database can hold differently from its declaration. */
3
+ export type PolicyDriftField = "roles" | "command" | "mode" | "USING" | "WITH CHECK";
4
+ /** A row of the RLS editor's policy list. */
5
+ export interface ListedPolicy extends PostgresPolicy {
6
+ /**
7
+ * What the project's code compiles this policy to, when it declares one by
8
+ * this name. For a policy the database also has, the row's own fields are
9
+ * the database's and this is what they are compared against.
10
+ */
11
+ declared?: PostgresPolicy;
12
+ /** Where the database's policy differs from {@link declared}. Empty when it does not. */
13
+ drift?: PolicyDriftField[];
14
+ /**
15
+ * Rebase adds this policy itself — the admin baseline, a tenancy or
16
+ * auth-write gate, a junction's derived policies — so no rule of the
17
+ * author's holds it, and the next server start re-creates it.
18
+ */
19
+ generated?: boolean;
20
+ }
21
+ /**
22
+ * Where a live policy differs from the one the code compiles to.
23
+ *
24
+ * The same comparison `rebase doctor` makes: the `TO` list, the command, the
25
+ * mode, and whether each clause is there at all. Never the clause text —
26
+ * Postgres rewrites an expression when it stores it, so the text of a policy
27
+ * that matches its declaration exactly still differs from the declaration's.
28
+ * A clause that is missing is not a rewrite, though: a `USING` that is gone
29
+ * changes who the policy admits.
30
+ */
31
+ export declare function policyDrift(declared: PostgresPolicy, live: PostgresPolicy): PolicyDriftField[];
32
+ /**
33
+ * The policies of one table: every policy the database has, as the database
34
+ * has it, and every policy the code declares for the table that it does not.
35
+ *
36
+ * A policy in both keeps the database's fields — this list is what Postgres
37
+ * enforces — and carries the declaration beside them, with where the two
38
+ * differ.
39
+ *
40
+ * @param live The table's `pg_policies` rows.
41
+ * @param collection The collection the table belongs to, if any.
42
+ * @param collections Every collection, to resolve the ones a rule refers to.
43
+ */
44
+ export declare function listPolicies(live: readonly PostgresPolicy[], collection: CollectionConfig | null, tableName: string, collections: readonly CollectionConfig[]): ListedPolicy[];