@rebasepro/studio 0.21.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rebasepro/studio
|
|
2
2
|
|
|
3
|
-
Developer tools layer for Rebase — provides
|
|
3
|
+
Developer tools layer for Rebase — provides 11 lazy-loaded tools (SQL Console, JS Console, RLS Editor, Storage, Cron Jobs, Schema Visualizer, Branches, Backups, API Explorer, Logs Explorer, API Keys) plus a customizable home page.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -9,13 +9,13 @@ pnpm add @rebasepro/studio
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
ESM-only: `"type": "module"` with no CommonJS build, so it is loaded with
|
|
12
|
-
`import`.
|
|
13
|
-
`require(esm)
|
|
12
|
+
`import`. It needs Node `>=22.22.0` (its `engines` floor), where `require()`
|
|
13
|
+
of it resolves too: Node has supported `require(esm)` since 22.12.
|
|
14
14
|
|
|
15
15
|
### Peer Dependencies
|
|
16
16
|
|
|
17
|
-
- `react`
|
|
18
|
-
- `react-dom`
|
|
17
|
+
- `react` ^19.2.7
|
|
18
|
+
- `react-dom` ^19.2.7
|
|
19
19
|
- `react-router` ^8.3.0
|
|
20
20
|
- `@rebasepro/cms` (optional)
|
|
21
21
|
|
|
@@ -34,10 +34,12 @@ ESM-only: `"type": "module"` with no CommonJS build, so it is loaded with
|
|
|
34
34
|
| `cron` | Cron Jobs | Compute | Monitor scheduled tasks |
|
|
35
35
|
| `schema-visualizer` | Schema Visualizer | Database | Interactive database ERD |
|
|
36
36
|
| `branches` | Branches | Database | Create and manage database branches |
|
|
37
|
+
| `backups` | Backups | Database | Download database backups |
|
|
37
38
|
| `api` | API Explorer | API | Interactive API docs and testing |
|
|
38
39
|
| `logs` | Logs Explorer | Database | Real-time system and query logs |
|
|
40
|
+
| `api-keys` | API Keys | Access Control | Create and manage scoped API keys |
|
|
39
41
|
|
|
40
|
-
All
|
|
42
|
+
All 11 tools are enabled by default. The `schema` tool (collection editor) is auto-injected by the CMS when `collectionEditor` is enabled — it is **not** registered here.
|
|
41
43
|
|
|
42
44
|
## Key Exports
|
|
43
45
|
|
|
@@ -48,7 +50,7 @@ All 9 tools are enabled by default. The `schema` tool (collection editor) is aut
|
|
|
48
50
|
| `StudioBridgeProvider` | Component | Re-exported from `@rebasepro/app` |
|
|
49
51
|
| `StudioBridgeContext` | Context | Re-exported from `@rebasepro/app` |
|
|
50
52
|
| `useStudioCollectionRegistry` | Hook | Access the collection registry |
|
|
51
|
-
| `useStudioSidePanelController` | Hook | Control the side
|
|
53
|
+
| `useStudioSidePanelController` | Hook | Control the side entity panel |
|
|
52
54
|
| `useStudioUrlController` | Hook | URL state management |
|
|
53
55
|
| `useStudioNavigationState` | Hook | Navigation state |
|
|
54
56
|
| `useStudioBreadcrumbs` | Hook | Breadcrumb management |
|
|
@@ -71,7 +73,7 @@ beside them, pass an `AppView` to `<RebaseStudio devViews>`.
|
|
|
71
73
|
```tsx
|
|
72
74
|
import { RebaseStudio } from "@rebasepro/studio";
|
|
73
75
|
|
|
74
|
-
// Inside your Rebase app — enable all
|
|
76
|
+
// Inside your Rebase app — enable all 11 tools (default)
|
|
75
77
|
<RebaseStudio />
|
|
76
78
|
|
|
77
79
|
// Or pick specific tools
|
|
@@ -2,7 +2,7 @@ import { t as classifyLoadFailure } from "./load-failure-C6OMJSGC.js";
|
|
|
2
2
|
import { t as LoadFailureView } from "./load-failure-view-CBTB_h-h.js";
|
|
3
3
|
import { useRebaseClient, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
-
import { Alert, Button, Chip, CircularProgress, DatabaseIcon, DownloadIcon, IconButton, RefreshCwIcon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
5
|
+
import { Alert, Button, Chip, CircularProgress, DatabaseIcon, DownloadIcon, IconButton, RefreshCwIcon, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/Backups/BackupsView.tsx
|
|
8
8
|
function formatSize(bytes) {
|
|
@@ -18,6 +18,13 @@ function formatDate(iso) {
|
|
|
18
18
|
if (isNaN(d.getTime())) return "—";
|
|
19
19
|
return d.toLocaleString();
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The name the roles sidecar is saved under: the dump's, with the suffix
|
|
23
|
+
* `rebase db restore` looks for beside it.
|
|
24
|
+
*/
|
|
25
|
+
function rolesFileName(dumpName) {
|
|
26
|
+
return dumpName.replace(/\.dump$/, "") + ".globals.sql";
|
|
27
|
+
}
|
|
21
28
|
function BackupsView() {
|
|
22
29
|
const client = useRebaseClient();
|
|
23
30
|
const snackbar = useSnackbarController();
|
|
@@ -55,16 +62,16 @@ function BackupsView() {
|
|
|
55
62
|
useEffect(() => {
|
|
56
63
|
load();
|
|
57
64
|
}, [load]);
|
|
58
|
-
const handleDownload = async (
|
|
65
|
+
const handleDownload = async (key, fileName) => {
|
|
59
66
|
const c = clientRef.current;
|
|
60
67
|
if (!c?.backups) return;
|
|
61
|
-
setDownloading(
|
|
68
|
+
setDownloading(key);
|
|
62
69
|
try {
|
|
63
|
-
const blob = await c.backups.download(
|
|
70
|
+
const blob = await c.backups.download(key);
|
|
64
71
|
const url = URL.createObjectURL(blob);
|
|
65
72
|
const a = document.createElement("a");
|
|
66
73
|
a.href = url;
|
|
67
|
-
a.download =
|
|
74
|
+
a.download = fileName;
|
|
68
75
|
document.body.appendChild(a);
|
|
69
76
|
a.click();
|
|
70
77
|
a.remove();
|
|
@@ -181,40 +188,61 @@ function BackupsView() {
|
|
|
181
188
|
})]
|
|
182
189
|
}) : /* @__PURE__ */ jsx("div", {
|
|
183
190
|
className: "space-y-2 max-w-3xl",
|
|
184
|
-
children: backups.map((backup) =>
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
191
|
+
children: backups.map((backup) => {
|
|
192
|
+
const globalsKey = backup.globalsKey;
|
|
193
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
194
|
+
className: cls("flex items-center gap-3 px-4 py-3 rounded-lg border bg-surface-card", defaultBorderMixin),
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsx(DatabaseIcon, {
|
|
197
|
+
size: iconSize.small,
|
|
198
|
+
className: "text-surface-400 shrink-0"
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ jsxs("div", {
|
|
201
|
+
className: "flex-1 min-w-0",
|
|
202
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
203
|
+
variant: "body2",
|
|
204
|
+
className: "truncate font-medium font-mono text-[12px]",
|
|
205
|
+
children: backup.name
|
|
206
|
+
}), /* @__PURE__ */ jsxs(Typography, {
|
|
207
|
+
variant: "caption",
|
|
208
|
+
color: "secondary",
|
|
209
|
+
className: "text-[11px]",
|
|
210
|
+
children: [
|
|
211
|
+
formatDate(backup.createdAt),
|
|
212
|
+
" · ",
|
|
213
|
+
formatSize(backup.sizeBytes)
|
|
214
|
+
]
|
|
215
|
+
})]
|
|
216
|
+
}),
|
|
217
|
+
globalsKey ? /* @__PURE__ */ jsx(Tooltip, {
|
|
218
|
+
title: t("studio_backups_roles_file_hint"),
|
|
219
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
220
|
+
size: "small",
|
|
221
|
+
variant: "text",
|
|
222
|
+
onClick: () => handleDownload(globalsKey, rolesFileName(backup.name)),
|
|
223
|
+
disabled: downloading === globalsKey,
|
|
224
|
+
startIcon: downloading === globalsKey ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest }),
|
|
225
|
+
children: downloading === globalsKey ? t("studio_backups_downloading") : t("studio_backups_roles_file")
|
|
226
|
+
})
|
|
227
|
+
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
228
|
+
title: t("studio_backups_no_roles_file_hint"),
|
|
229
|
+
children: /* @__PURE__ */ jsx(Chip, {
|
|
230
|
+
size: "smallest",
|
|
231
|
+
colorScheme: "orangeDarker",
|
|
232
|
+
children: t("studio_backups_no_roles_file")
|
|
233
|
+
})
|
|
234
|
+
}),
|
|
235
|
+
/* @__PURE__ */ jsx(Button, {
|
|
236
|
+
size: "small",
|
|
237
|
+
variant: "outlined",
|
|
238
|
+
onClick: () => handleDownload(backup.key, backup.name),
|
|
239
|
+
disabled: downloading === backup.key,
|
|
240
|
+
startIcon: downloading === backup.key ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest }),
|
|
241
|
+
children: downloading === backup.key ? t("studio_backups_downloading") : t("download")
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
}, backup.key);
|
|
245
|
+
})
|
|
218
246
|
})
|
|
219
247
|
})]
|
|
220
248
|
});
|
|
@@ -222,4 +250,4 @@ function BackupsView() {
|
|
|
222
250
|
//#endregion
|
|
223
251
|
export { BackupsView };
|
|
224
252
|
|
|
225
|
-
//# sourceMappingURL=BackupsView-
|
|
253
|
+
//# sourceMappingURL=BackupsView-DYTAD6aW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackupsView-DYTAD6aW.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 Tooltip,\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\n/**\n * The name the roles sidecar is saved under: the dump's, with the suffix\n * `rebase db restore` looks for beside it.\n */\nfunction rolesFileName(dumpName: string): string {\n return dumpName.replace(/\\.dump$/, \"\") + \".globals.sql\";\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 (key: string, fileName: string) => {\n const c = clientRef.current;\n if (!c?.backups) return;\n setDownloading(key);\n try {\n const blob = await c.backups.download(key);\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = fileName;\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 // The roles sidecar travels with the dump: a restore\n // into a new Postgres needs both files.\n const globalsKey = backup.globalsKey;\n return (\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 {globalsKey ? (\n <Tooltip title={t(\"studio_backups_roles_file_hint\")}>\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={() => handleDownload(globalsKey, rolesFileName(backup.name))}\n disabled={downloading === globalsKey}\n startIcon={downloading === globalsKey\n ? <CircularProgress size=\"smallest\"/>\n : <DownloadIcon size={iconSize.smallest}/>}\n >\n {downloading === globalsKey ? t(\"studio_backups_downloading\") : t(\"studio_backups_roles_file\")}\n </Button>\n </Tooltip>\n ) : (\n <Tooltip title={t(\"studio_backups_no_roles_file_hint\")}>\n <Chip size=\"smallest\" colorScheme=\"orangeDarker\">{t(\"studio_backups_no_roles_file\")}</Chip>\n </Tooltip>\n )}\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => handleDownload(backup.key, backup.name)}\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 })}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/index.es.js
CHANGED
|
@@ -557,7 +557,7 @@ var StorageView = lazyChunk(() => import("./StorageView-C52dSZNf.js").then((m) =
|
|
|
557
557
|
var CronJobsView = lazyChunk(() => import("./CronJobsView-JwRjQCde.js").then((m) => ({ default: m.CronJobsView })));
|
|
558
558
|
var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-DtDzIUNy.js").then((m) => ({ default: m.SchemaVisualizer })));
|
|
559
559
|
var BranchesView = lazyChunk(() => import("./BranchesView-D6Sz1a5f.js").then((m) => ({ default: m.BranchesView })));
|
|
560
|
-
var BackupsView = lazyChunk(() => import("./BackupsView-
|
|
560
|
+
var BackupsView = lazyChunk(() => import("./BackupsView-DYTAD6aW.js").then((m) => ({ default: m.BackupsView })));
|
|
561
561
|
var ApiExplorer = lazyChunk(() => import("./ApiExplorer-D-jLWAaY.js").then((m) => ({ default: m.ApiExplorer })));
|
|
562
562
|
var LogsExplorer = lazyChunk(() => import("./LogsExplorer-Xq8K4oo6.js").then((m) => ({ default: m.LogsExplorer })));
|
|
563
563
|
var ApiKeysView = lazyChunk(() => import("./ApiKeysView-OfJLH2x7.js").then((m) => ({ default: m.ApiKeysView })));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/studio",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "Rebase Studio — the SQL editor, schema visualiser, RLS policy editor and API explorer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rebase",
|
|
@@ -35,19 +35,19 @@
|
|
|
35
35
|
"pgsql-ast-parser": "12.0.2",
|
|
36
36
|
"prism-react-renderer": "^2.4.1",
|
|
37
37
|
"react-dropzone": "^19.1.1",
|
|
38
|
-
"@rebasepro/cms-types": "0.21.
|
|
39
|
-
"@rebasepro/app": "0.21.
|
|
40
|
-
"@rebasepro/client": "0.21.
|
|
41
|
-
"@rebasepro/common": "0.21.
|
|
42
|
-
"@rebasepro/
|
|
43
|
-
"@rebasepro/
|
|
44
|
-
"@rebasepro/
|
|
38
|
+
"@rebasepro/cms-types": "0.21.1",
|
|
39
|
+
"@rebasepro/app": "0.21.1",
|
|
40
|
+
"@rebasepro/client": "0.21.1",
|
|
41
|
+
"@rebasepro/common": "0.21.1",
|
|
42
|
+
"@rebasepro/ui": "0.21.1",
|
|
43
|
+
"@rebasepro/types": "0.21.1",
|
|
44
|
+
"@rebasepro/utils": "0.21.1"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"react": "^19.2.7",
|
|
48
48
|
"react-dom": "^19.2.7",
|
|
49
49
|
"react-router": "^8.3.0",
|
|
50
|
-
"@rebasepro/cms": "0.21.
|
|
50
|
+
"@rebasepro/cms": "0.21.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"@rebasepro/cms": {
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|