@rebasepro/studio 0.17.3 → 0.18.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/LICENSE +0 -1
- package/README.md +12 -4
- package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
- package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
- package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
- package/dist/BackupsView-DVkKzaPR.js.map +1 -0
- package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
- package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
- package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
- package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
- package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
- package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
- package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
- package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
- package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
- package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
- package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
- package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
- package/dist/components/RLSEditor/saveRules.d.ts +38 -0
- package/dist/components/RebaseStudio.d.ts +1 -1
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
- package/dist/components/load-failure-view.d.ts +17 -0
- package/dist/components/load-failure.d.ts +47 -0
- package/dist/index.es.js +56 -36
- package/dist/index.es.js.map +1 -1
- package/dist/load-failure-C6OMJSGC.js +31 -0
- package/dist/load-failure-C6OMJSGC.js.map +1 -0
- package/dist/load-failure-view-CBTB_h-h.js +48 -0
- package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
- package/package.json +36 -21
- package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
- package/dist/BackupsView-DNS6LdVg.js.map +0 -1
- package/dist/BranchesView-CM3B-ER3.js.map +0 -1
- package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
- package/dist/RLSEditor-CBX4uD66.js.map +0 -1
- package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
- package/dist/StorageView-BGDeC5GI.js.map +0 -1
- package/dist/components/StorageView/storage-failure.d.ts +0 -34
- package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
- package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
- package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
- package/src/components/ApiExplorer/parseSpec.ts +0 -104
- package/src/components/ApiExplorer/types.ts +0 -84
- package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
- package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
- package/src/components/ApiKeys/permissions.ts +0 -145
- package/src/components/AuthSimulationSelector.tsx +0 -72
- package/src/components/Backups/BackupsView.tsx +0 -185
- package/src/components/Branches/BranchesView.tsx +0 -386
- package/src/components/CronJobs/CronJobsView.tsx +0 -413
- package/src/components/JSEditor/JSEditor.tsx +0 -1100
- package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
- package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
- package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
- package/src/components/LogsExplorer/useLogTail.ts +0 -356
- package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
- package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
- package/src/components/RLSEditor/index.ts +0 -1
- package/src/components/RLSEditor/policy-presets.ts +0 -176
- package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
- package/src/components/RebaseStudio.tsx +0 -174
- package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
- package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
- package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
- package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
- package/src/components/SQLEditor/sql_editor_types.ts +0 -18
- package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
- package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
- package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
- package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
- package/src/components/StorageView/StorageView.tsx +0 -1588
- package/src/components/StorageView/storage-failure.ts +0 -63
- package/src/components/StudioHomePage.tsx +0 -430
- package/src/index.ts +0 -31
- package/src/utils/parseSpec.test.ts +0 -309
- package/src/utils/sql_utils.test.ts +0 -265
- package/src/utils/sql_utils.ts +0 -291
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as classifyLoadFailure } from "./load-failure-C6OMJSGC.js";
|
|
2
|
+
import { t as LoadFailureView } from "./load-failure-view-CBTB_h-h.js";
|
|
3
|
+
import { useRebaseClient, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
2
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
5
|
import { Alert, Button, Chip, CircularProgress, DatabaseIcon, DownloadIcon, IconButton, RefreshCwIcon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
4
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -19,11 +21,14 @@ function formatDate(iso) {
|
|
|
19
21
|
function BackupsView() {
|
|
20
22
|
const client = useRebaseClient();
|
|
21
23
|
const snackbar = useSnackbarController();
|
|
24
|
+
const { t } = useTranslation();
|
|
22
25
|
const [backups, setBackups] = useState([]);
|
|
23
26
|
const [destinationKind, setDestinationKind] = useState("local");
|
|
24
27
|
const [configured, setConfigured] = useState(true);
|
|
25
28
|
const [loading, setLoading] = useState(true);
|
|
26
29
|
const [downloading, setDownloading] = useState(null);
|
|
30
|
+
/** Why the listing failed, classified — see `load-failure.ts`. */
|
|
31
|
+
const [failure, setFailure] = useState(null);
|
|
27
32
|
const clientRef = useRef(client);
|
|
28
33
|
clientRef.current = client;
|
|
29
34
|
const snackbarRef = useRef(snackbar);
|
|
@@ -35,16 +40,14 @@ function BackupsView() {
|
|
|
35
40
|
setConfigured(false);
|
|
36
41
|
return;
|
|
37
42
|
}
|
|
43
|
+
setFailure(null);
|
|
38
44
|
try {
|
|
39
45
|
const res = await c.backups.list();
|
|
40
46
|
setBackups(res.backups);
|
|
41
47
|
setDestinationKind(res.destinationKind);
|
|
42
48
|
setConfigured(res.configured);
|
|
43
49
|
} catch (e) {
|
|
44
|
-
|
|
45
|
-
type: "error",
|
|
46
|
-
message: e instanceof Error ? e.message : String(e)
|
|
47
|
-
});
|
|
50
|
+
setFailure(classifyLoadFailure(e));
|
|
48
51
|
} finally {
|
|
49
52
|
setLoading(false);
|
|
50
53
|
}
|
|
@@ -113,7 +116,7 @@ function BackupsView() {
|
|
|
113
116
|
/* @__PURE__ */ jsx(Typography, {
|
|
114
117
|
variant: "subtitle2",
|
|
115
118
|
className: "font-semibold",
|
|
116
|
-
children: "
|
|
119
|
+
children: t("studio_tool_backups")
|
|
117
120
|
}),
|
|
118
121
|
/* @__PURE__ */ jsx(Chip, {
|
|
119
122
|
size: "smallest",
|
|
@@ -134,34 +137,29 @@ function BackupsView() {
|
|
|
134
137
|
})]
|
|
135
138
|
}), /* @__PURE__ */ jsx("div", {
|
|
136
139
|
className: "flex-1 overflow-y-auto p-5",
|
|
137
|
-
children:
|
|
140
|
+
children: failure ? /* @__PURE__ */ jsx(LoadFailureView, {
|
|
141
|
+
failure,
|
|
142
|
+
title: t("studio_backups_read_failed"),
|
|
143
|
+
deniedTitle: t("studio_backups_denied_title"),
|
|
144
|
+
deniedHint: t("studio_backups_denied_hint"),
|
|
145
|
+
onRetry: load
|
|
146
|
+
}) : !configured ? /* @__PURE__ */ jsx(Alert, {
|
|
138
147
|
color: "info",
|
|
139
148
|
children: /* @__PURE__ */ jsxs(Typography, {
|
|
140
149
|
variant: "body2",
|
|
141
150
|
className: "text-[13px]",
|
|
142
151
|
children: [
|
|
143
|
-
/* @__PURE__ */ jsx("strong", { children: "
|
|
144
|
-
"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
className: "
|
|
152
|
-
children: "
|
|
153
|
-
})
|
|
154
|
-
" / ",
|
|
155
|
-
/* @__PURE__ */ jsx("code", {
|
|
156
|
-
className: "font-mono text-[12px]",
|
|
157
|
-
children: "gs://"
|
|
158
|
-
}),
|
|
159
|
-
" URL) and create backups with",
|
|
160
|
-
/* @__PURE__ */ jsx("code", {
|
|
161
|
-
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]",
|
|
162
|
-
children: "rebase db backup"
|
|
163
|
-
}),
|
|
164
|
-
"or a scheduled cron. See docs/backups.md."
|
|
152
|
+
/* @__PURE__ */ jsx("strong", { children: t("studio_backups_not_configured_title") }),
|
|
153
|
+
" ",
|
|
154
|
+
t("studio_backups_not_configured_body"),
|
|
155
|
+
" ",
|
|
156
|
+
/* @__PURE__ */ jsx("a", {
|
|
157
|
+
href: "https://rebase.pro/docs/backend/jobs",
|
|
158
|
+
target: "_blank",
|
|
159
|
+
rel: "noreferrer",
|
|
160
|
+
className: "text-primary underline",
|
|
161
|
+
children: t("studio_read_the_docs")
|
|
162
|
+
})
|
|
165
163
|
]
|
|
166
164
|
})
|
|
167
165
|
}) : backups.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -213,7 +211,7 @@ function BackupsView() {
|
|
|
213
211
|
onClick: () => handleDownload(backup),
|
|
214
212
|
disabled: downloading === backup.key,
|
|
215
213
|
startIcon: downloading === backup.key ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest }),
|
|
216
|
-
children: downloading === backup.key ? "
|
|
214
|
+
children: downloading === backup.key ? t("studio_backups_downloading") : t("download")
|
|
217
215
|
})
|
|
218
216
|
]
|
|
219
217
|
}, backup.key))
|
|
@@ -224,4 +222,4 @@ function BackupsView() {
|
|
|
224
222
|
//#endregion
|
|
225
223
|
export { BackupsView };
|
|
226
224
|
|
|
227
|
-
//# sourceMappingURL=BackupsView-
|
|
225
|
+
//# sourceMappingURL=BackupsView-DVkKzaPR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackupsView-DVkKzaPR.js","names":[],"sources":["../src/components/Backups/BackupsView.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n DatabaseIcon,\n defaultBorderMixin,\n DownloadIcon,\n iconSize,\n IconButton,\n RefreshCwIcon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseClient, useSnackbarController, useTranslation } from \"@rebasepro/app\";\nimport type { BackupInfo, RebaseClient } from \"@rebasepro/types\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatDate(iso: string | undefined): string {\n if (!iso) return \"—\";\n const d = new Date(iso);\n if (isNaN(d.getTime())) return \"—\";\n return d.toLocaleString();\n}\n\nexport function BackupsView() {\n const client = useRebaseClient<RebaseClient>();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n\n const [backups, setBackups] = useState<BackupInfo[]>([]);\n const [destinationKind, setDestinationKind] = useState<string>(\"local\");\n const [configured, setConfigured] = useState(true);\n const [loading, setLoading] = useState(true);\n const [downloading, setDownloading] = useState<string | null>(null);\n /** Why the listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n const clientRef = useRef(client);\n clientRef.current = client;\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const load = useCallback(async () => {\n const c = clientRef.current;\n if (!c?.backups) {\n setLoading(false);\n setConfigured(false);\n return;\n }\n setFailure(null);\n try {\n const res = await c.backups.list();\n setBackups(res.backups);\n setDestinationKind(res.destinationKind);\n setConfigured(res.configured);\n } catch (e: unknown) {\n // The snackbar is gone in seconds, and the list underneath it is\n // empty — which is how a refused listing came to read \"No backups\n // found yet\" about a database that has backups. Keep the reason on\n // screen instead.\n setFailure(classifyLoadFailure(e));\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n load();\n }, [load]);\n\n const handleDownload = async (backup: BackupInfo) => {\n const c = clientRef.current;\n if (!c?.backups) return;\n setDownloading(backup.key);\n try {\n const blob = await c.backups.download(backup.key);\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = backup.name;\n document.body.appendChild(a);\n a.click();\n a.remove();\n URL.revokeObjectURL(url);\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDownloading(null);\n }\n };\n\n // Backups API not available on this client (e.g. non-Postgres backend).\n if (!client?.backups) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">Backups Not Available</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n The backups API is not exposed by this backend.\n </Typography>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <DatabaseIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_backups\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{backups.length}</Chip>\n <Chip size=\"smallest\" className=\"bg-surface-100 dark:bg-surface-800 text-surface-500 dark:text-surface-400 uppercase font-mono text-[10px]\">{destinationKind}</Chip>\n </div>\n <IconButton size=\"small\" onClick={load} title=\"Refresh\">\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n\n <div className=\"flex-1 overflow-y-auto p-5\">\n {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_backups_read_failed\")}\n deniedTitle={t(\"studio_backups_denied_title\")}\n deniedHint={t(\"studio_backups_denied_hint\")}\n onRetry={load}\n />\n ) : !configured ? (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>{t(\"studio_backups_not_configured_title\")}</strong>{\" \"}\n {t(\"studio_backups_not_configured_body\")}{\" \"}\n <a\n href=\"https://rebase.pro/docs/backend/jobs\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </Typography>\n </Alert>\n ) : backups.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center\">\n <DatabaseIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n No backups found yet. Run <code className=\"font-mono text-[12px]\">rebase db backup</code> or wait for the next scheduled run.\n </Typography>\n </div>\n ) : (\n <div className=\"space-y-2 max-w-3xl\">\n {backups.map(backup => (\n <div\n key={backup.key}\n className={cls(\"flex items-center gap-3 px-4 py-3 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}\n >\n <DatabaseIcon size={iconSize.small} className=\"text-surface-400 shrink-0\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium font-mono text-[12px]\">{backup.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[11px]\">\n {formatDate(backup.createdAt)} · {formatSize(backup.sizeBytes)}\n </Typography>\n </div>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => handleDownload(backup)}\n disabled={downloading === backup.key}\n startIcon={downloading === backup.key\n ? <CircularProgress size=\"smallest\"/>\n : <DownloadIcon size={iconSize.smallest}/>}\n >\n {downloading === backup.key ? t(\"studio_backups_downloading\") : t(\"download\")}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as classifyLoadFailure } from "./load-failure-C6OMJSGC.js";
|
|
2
|
+
import { t as LoadFailureView } from "./load-failure-view-CBTB_h-h.js";
|
|
3
|
+
import { ConfirmationDialog, useRebaseContext, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
2
4
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
5
|
import { Alert, Button, Chip, CircularProgress, CopyIcon, Dialog, DialogActions, DialogContent, DialogTitle, GitBranchIcon, IconButton, PlusIcon, RefreshCwIcon, Select, SelectItem, TextField, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
4
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
7
|
import { isBranchAdmin } from "@rebasepro/types";
|
|
6
8
|
import { formatRelativeTime } from "@rebasepro/utils";
|
|
7
9
|
//#region src/components/Branches/BranchesView.tsx
|
|
10
|
+
/**
|
|
11
|
+
* The prefix the driver puts on every branch database.
|
|
12
|
+
*
|
|
13
|
+
* Stated here rather than derived from the row, because `BranchInfo` carries
|
|
14
|
+
* the *name* a person typed and not the database it became — and the detail
|
|
15
|
+
* pane used to print the name under "Database:", which is a connection string
|
|
16
|
+
* to nothing. Kept in step with `BranchService.BRANCH_DB_PREFIX` and the CLI's
|
|
17
|
+
* `branch-pointer.ts`, which say the same thing for the same reason.
|
|
18
|
+
*/
|
|
19
|
+
var BRANCH_DB_PREFIX = "rb_";
|
|
8
20
|
function formatSize(bytes) {
|
|
9
21
|
if (bytes === void 0 || bytes === null) return "—";
|
|
10
22
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -21,6 +33,7 @@ function formatRelative(date) {
|
|
|
21
33
|
function BranchesView() {
|
|
22
34
|
const { databaseAdmin } = useRebaseContext();
|
|
23
35
|
const snackbar = useSnackbarController();
|
|
36
|
+
const { t } = useTranslation();
|
|
24
37
|
const [branches, setBranches] = useState([]);
|
|
25
38
|
const [loading, setLoading] = useState(true);
|
|
26
39
|
const [selectedBranch, setSelectedBranch] = useState(null);
|
|
@@ -28,8 +41,20 @@ function BranchesView() {
|
|
|
28
41
|
const [newBranchName, setNewBranchName] = useState("");
|
|
29
42
|
const [sourceBranch, setSourceBranch] = useState(void 0);
|
|
30
43
|
const [creating, setCreating] = useState(false);
|
|
44
|
+
/**
|
|
45
|
+
* Why the last create attempt was refused, kept on screen.
|
|
46
|
+
*
|
|
47
|
+
* The server refuses branching outright on the managed development
|
|
48
|
+
* database — PGlite serves one database, so the "branch" would be the
|
|
49
|
+
* parent. That refusal is the whole answer to what the reader just tried to
|
|
50
|
+
* do, and a snackbar is gone in four seconds; the same argument
|
|
51
|
+
* `load-failure.ts` makes for listings.
|
|
52
|
+
*/
|
|
53
|
+
const [createError, setCreateError] = useState(null);
|
|
31
54
|
const [deleteTarget, setDeleteTarget] = useState(null);
|
|
32
55
|
const [deleting, setDeleting] = useState(false);
|
|
56
|
+
/** Why the branch listing failed, classified — see `load-failure.ts`. */
|
|
57
|
+
const [failure, setFailure] = useState(null);
|
|
33
58
|
const snackbarRef = useRef(snackbar);
|
|
34
59
|
snackbarRef.current = snackbar;
|
|
35
60
|
const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : void 0;
|
|
@@ -41,11 +66,9 @@ function BranchesView() {
|
|
|
41
66
|
try {
|
|
42
67
|
const result = await branchAdmin.listBranches();
|
|
43
68
|
setBranches(result);
|
|
69
|
+
setFailure(null);
|
|
44
70
|
} catch (e) {
|
|
45
|
-
|
|
46
|
-
type: "error",
|
|
47
|
-
message: e instanceof Error ? e.message : String(e)
|
|
48
|
-
});
|
|
71
|
+
setFailure(classifyLoadFailure(e));
|
|
49
72
|
} finally {
|
|
50
73
|
setLoading(false);
|
|
51
74
|
}
|
|
@@ -56,6 +79,7 @@ function BranchesView() {
|
|
|
56
79
|
const handleCreate = async () => {
|
|
57
80
|
if (!branchAdmin || !newBranchName.trim()) return;
|
|
58
81
|
setCreating(true);
|
|
82
|
+
setCreateError(null);
|
|
59
83
|
try {
|
|
60
84
|
await branchAdmin.createBranch(newBranchName.trim(), sourceBranch ? { source: sourceBranch } : void 0);
|
|
61
85
|
snackbarRef.current.open({
|
|
@@ -67,9 +91,11 @@ function BranchesView() {
|
|
|
67
91
|
setSourceBranch(void 0);
|
|
68
92
|
await loadBranches();
|
|
69
93
|
} catch (e) {
|
|
94
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
95
|
+
setCreateError(message);
|
|
70
96
|
snackbarRef.current.open({
|
|
71
97
|
type: "error",
|
|
72
|
-
message
|
|
98
|
+
message
|
|
73
99
|
});
|
|
74
100
|
} finally {
|
|
75
101
|
setCreating(false);
|
|
@@ -106,20 +132,20 @@ function BranchesView() {
|
|
|
106
132
|
/* @__PURE__ */ jsx(Typography, {
|
|
107
133
|
variant: "h6",
|
|
108
134
|
color: "secondary",
|
|
109
|
-
children: "
|
|
135
|
+
children: t("studio_branches_unavailable_title")
|
|
110
136
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
112
138
|
variant: "body2",
|
|
113
139
|
color: "disabled",
|
|
114
140
|
className: "max-w-md",
|
|
115
|
-
children:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
children: t("studio_branches_unavailable_body")
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsx("a", {
|
|
144
|
+
href: "https://rebase.pro/docs/backend/branching",
|
|
145
|
+
target: "_blank",
|
|
146
|
+
rel: "noreferrer",
|
|
147
|
+
className: "text-primary text-sm underline",
|
|
148
|
+
children: t("studio_read_the_docs")
|
|
123
149
|
})
|
|
124
150
|
]
|
|
125
151
|
});
|
|
@@ -145,7 +171,7 @@ function BranchesView() {
|
|
|
145
171
|
/* @__PURE__ */ jsx(Typography, {
|
|
146
172
|
variant: "subtitle2",
|
|
147
173
|
className: "font-semibold",
|
|
148
|
-
children: "
|
|
174
|
+
children: t("studio_tool_branches")
|
|
149
175
|
}),
|
|
150
176
|
/* @__PURE__ */ jsx(Chip, {
|
|
151
177
|
size: "smallest",
|
|
@@ -170,7 +196,13 @@ function BranchesView() {
|
|
|
170
196
|
})]
|
|
171
197
|
}), /* @__PURE__ */ jsx("div", {
|
|
172
198
|
className: "flex-1 overflow-y-auto p-2 space-y-1",
|
|
173
|
-
children:
|
|
199
|
+
children: failure ? /* @__PURE__ */ jsx(LoadFailureView, {
|
|
200
|
+
failure,
|
|
201
|
+
title: t("studio_branches_read_failed"),
|
|
202
|
+
deniedTitle: t("studio_branches_denied_title"),
|
|
203
|
+
deniedHint: t("studio_branches_denied_hint"),
|
|
204
|
+
onRetry: loadBranches
|
|
205
|
+
}) : branches.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
174
206
|
className: "flex flex-col items-center justify-center h-full gap-3 text-center px-4",
|
|
175
207
|
children: [
|
|
176
208
|
/* @__PURE__ */ jsx(CopyIcon, {
|
|
@@ -304,12 +336,17 @@ function BranchesView() {
|
|
|
304
336
|
className: "text-[13px]",
|
|
305
337
|
children: [
|
|
306
338
|
/* @__PURE__ */ jsx("strong", { children: "How to use this branch:" }),
|
|
307
|
-
"
|
|
339
|
+
" point this checkout at it with",
|
|
340
|
+
/* @__PURE__ */ jsxs("code", {
|
|
341
|
+
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]",
|
|
342
|
+
children: ["rebase db branch switch ", selected.name]
|
|
343
|
+
}),
|
|
344
|
+
"— every later command uses it, and",
|
|
308
345
|
/* @__PURE__ */ jsx("code", {
|
|
309
346
|
className: "mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]",
|
|
310
|
-
children:
|
|
347
|
+
children: "rebase db branch switch --off"
|
|
311
348
|
}),
|
|
312
|
-
"
|
|
349
|
+
"goes back to the main database. Changes made here don't affect it."
|
|
313
350
|
]
|
|
314
351
|
})
|
|
315
352
|
}), /* @__PURE__ */ jsxs("div", {
|
|
@@ -331,7 +368,20 @@ function BranchesView() {
|
|
|
331
368
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
332
369
|
variant: "body2",
|
|
333
370
|
className: "font-mono text-[12px]",
|
|
334
|
-
children: selected.name
|
|
371
|
+
children: `${BRANCH_DB_PREFIX}${selected.name}`
|
|
372
|
+
})]
|
|
373
|
+
}),
|
|
374
|
+
/* @__PURE__ */ jsxs("div", {
|
|
375
|
+
className: "flex items-start gap-2",
|
|
376
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
377
|
+
variant: "caption",
|
|
378
|
+
color: "secondary",
|
|
379
|
+
className: "w-24 shrink-0 text-[11px]",
|
|
380
|
+
children: "Connect with:"
|
|
381
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
382
|
+
variant: "body2",
|
|
383
|
+
className: "font-mono text-[12px] break-all",
|
|
384
|
+
children: `DATABASE_URL=…/${BRANCH_DB_PREFIX}${selected.name}`
|
|
335
385
|
})]
|
|
336
386
|
}),
|
|
337
387
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -368,7 +418,10 @@ function BranchesView() {
|
|
|
368
418
|
}),
|
|
369
419
|
/* @__PURE__ */ jsxs(Dialog, {
|
|
370
420
|
open: createOpen,
|
|
371
|
-
onOpenChange:
|
|
421
|
+
onOpenChange: (open) => {
|
|
422
|
+
setCreateOpen(open);
|
|
423
|
+
if (!open) setCreateError(null);
|
|
424
|
+
},
|
|
372
425
|
children: [
|
|
373
426
|
/* @__PURE__ */ jsx(DialogTitle, { children: "Create New Branch" }),
|
|
374
427
|
/* @__PURE__ */ jsxs(DialogContent, {
|
|
@@ -380,6 +433,14 @@ function BranchesView() {
|
|
|
380
433
|
className: "text-[13px]",
|
|
381
434
|
children: "Create an isolated database copy. The branch will be a full clone of the source database at this point in time."
|
|
382
435
|
}),
|
|
436
|
+
createError && /* @__PURE__ */ jsx(Alert, {
|
|
437
|
+
color: "error",
|
|
438
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
439
|
+
variant: "body2",
|
|
440
|
+
className: "text-[13px]",
|
|
441
|
+
children: createError
|
|
442
|
+
})
|
|
443
|
+
}),
|
|
383
444
|
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(TextField, {
|
|
384
445
|
label: "Branch Name",
|
|
385
446
|
value: newBranchName,
|
|
@@ -454,4 +515,4 @@ function StatCard({ label, value, mono }) {
|
|
|
454
515
|
//#endregion
|
|
455
516
|
export { BranchesView };
|
|
456
517
|
|
|
457
|
-
//# sourceMappingURL=BranchesView-
|
|
518
|
+
//# sourceMappingURL=BranchesView-Df8hQ8wE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BranchesView-Df8hQ8wE.js","names":[],"sources":["../src/components/Branches/BranchesView.tsx"],"sourcesContent":["\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n CopyIcon,\n defaultBorderMixin,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n GitBranchIcon,\n IconButton,\n iconSize,\n Paper,\n PlusIcon,\n RefreshCwIcon,\n Select,\n SelectItem,\n TextField,\n Trash2Icon,\n Typography\n} from \"@rebasepro/ui\";\nimport { useRebaseContext, useSnackbarController, ConfirmationDialog, useTranslation } from \"@rebasepro/app\";\nimport { isBranchAdmin } from \"@rebasepro/types\";\nimport type { BranchInfo } from \"@rebasepro/types\";\nimport { formatRelativeTime } from \"@rebasepro/utils\";\n\nimport { classifyLoadFailure, type LoadFailure } from \"../load-failure\";\n\n/**\n * The prefix the driver puts on every branch database.\n *\n * Stated here rather than derived from the row, because `BranchInfo` carries\n * the *name* a person typed and not the database it became — and the detail\n * pane used to print the name under \"Database:\", which is a connection string\n * to nothing. Kept in step with `BranchService.BRANCH_DB_PREFIX` and the CLI's\n * `branch-pointer.ts`, which say the same thing for the same reason.\n */\nconst BRANCH_DB_PREFIX = \"rb_\";\nimport { LoadFailureView } from \"../load-failure-view\";\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined || bytes === null) return \"—\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nfunction formatRelative(date: Date | string | undefined): string {\n if (!date) return \"—\";\n const d = date instanceof Date ? date : new Date(date);\n if (isNaN(d.getTime())) return \"—\";\n return formatRelativeTime(d) ?? d.toLocaleDateString();\n}\n\nexport function BranchesView() {\n const { databaseAdmin } = useRebaseContext();\n const snackbar = useSnackbarController();\n const { t } = useTranslation();\n\n const [branches, setBranches] = useState<BranchInfo[]>([]);\n const [loading, setLoading] = useState(true);\n const [selectedBranch, setSelectedBranch] = useState<string | null>(null);\n\n // Create dialog\n const [createOpen, setCreateOpen] = useState(false);\n const [newBranchName, setNewBranchName] = useState(\"\");\n const [sourceBranch, setSourceBranch] = useState<string | undefined>(undefined);\n const [creating, setCreating] = useState(false);\n /**\n * Why the last create attempt was refused, kept on screen.\n *\n * The server refuses branching outright on the managed development\n * database — PGlite serves one database, so the \"branch\" would be the\n * parent. That refusal is the whole answer to what the reader just tried to\n * do, and a snackbar is gone in four seconds; the same argument\n * `load-failure.ts` makes for listings.\n */\n const [createError, setCreateError] = useState<string | null>(null);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n /** Why the branch listing failed, classified — see `load-failure.ts`. */\n const [failure, setFailure] = useState<LoadFailure | null>(null);\n\n // Refs\n const snackbarRef = useRef(snackbar);\n snackbarRef.current = snackbar;\n\n const branchAdmin = isBranchAdmin(databaseAdmin) ? databaseAdmin : undefined;\n\n const loadBranches = useCallback(async () => {\n if (!branchAdmin) {\n setLoading(false);\n return;\n }\n try {\n const result = await branchAdmin.listBranches();\n setBranches(result);\n setFailure(null);\n } catch (e: unknown) {\n // \"No branches yet. Create one…\" over a refused listing invites the\n // reader to create a branch they are not allowed to see, and then\n // hides the one they already have.\n setFailure(classifyLoadFailure(e));\n } finally {\n setLoading(false);\n }\n }, [branchAdmin]);\n\n useEffect(() => {\n loadBranches();\n }, [loadBranches]);\n\n const handleCreate = async () => {\n if (!branchAdmin || !newBranchName.trim()) return;\n setCreating(true);\n setCreateError(null);\n try {\n await branchAdmin.createBranch(newBranchName.trim(), sourceBranch ? { source: sourceBranch } : undefined);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${newBranchName.trim()}\" created successfully`\n });\n setCreateOpen(false);\n setNewBranchName(\"\");\n setSourceBranch(undefined);\n await loadBranches();\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : String(e);\n setCreateError(message);\n snackbarRef.current.open({ type: \"error\",\n message });\n } finally {\n setCreating(false);\n }\n };\n\n const handleDelete = async () => {\n if (!branchAdmin || !deleteTarget) return;\n setDeleting(true);\n try {\n await branchAdmin.deleteBranch(deleteTarget);\n snackbarRef.current.open({\n type: \"success\",\n message: `Branch \"${deleteTarget}\" deleted`\n });\n if (selectedBranch === deleteTarget) setSelectedBranch(null);\n setDeleteTarget(null);\n await loadBranches();\n } catch (e: unknown) {\n snackbarRef.current.open({\n type: \"error\",\n message: e instanceof Error ? e.message : String(e)\n });\n } finally {\n setDeleting(false);\n }\n };\n\n // Not supported\n if (!branchAdmin) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full gap-4 text-center p-8\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"h6\" color=\"secondary\">{t(\"studio_branches_unavailable_title\")}</Typography>\n <Typography variant=\"body2\" color=\"disabled\" className=\"max-w-md\">\n {t(\"studio_branches_unavailable_body\")}\n </Typography>\n <a\n href=\"https://rebase.pro/docs/backend/branching\"\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-primary text-sm underline\"\n >\n {t(\"studio_read_the_docs\")}\n </a>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <CircularProgress/>\n </div>\n );\n }\n\n const selected = branches.find(b => b.name === selectedBranch);\n\n return (\n <div className=\"flex h-full w-full overflow-hidden bg-white dark:bg-surface-950\">\n {/* ── Branch List ── */}\n <div className={cls(\"flex flex-col w-[340px] min-w-[280px] border-r h-full\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-4 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-2\">\n <GitBranchIcon size={iconSize.small} className=\"text-primary\"/>\n <Typography variant=\"subtitle2\" className=\"font-semibold\">{t(\"studio_tool_branches\")}</Typography>\n <Chip size=\"smallest\" className=\"bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300\">{branches.length}</Chip>\n </div>\n <div className=\"flex items-center gap-1\">\n <IconButton size=\"small\" onClick={loadBranches} title=\"Refresh\">\n <RefreshCwIcon size={iconSize.smallest}/>\n </IconButton>\n <IconButton size=\"small\" onClick={() => setCreateOpen(true)} title=\"Create branch\" className=\"text-primary\">\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto p-2 space-y-1\">\n {failure ? (\n <LoadFailureView\n failure={failure}\n title={t(\"studio_branches_read_failed\")}\n deniedTitle={t(\"studio_branches_denied_title\")}\n deniedHint={t(\"studio_branches_denied_hint\")}\n onRetry={loadBranches}\n />\n ) : branches.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3 text-center px-4\">\n <CopyIcon size={iconSize.small} className=\"text-surface-300 dark:text-surface-600\"/>\n <Typography variant=\"body2\" color=\"disabled\" className=\"text-[13px]\">\n No branches yet. Create one to start working with an isolated database copy.\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n onClick={() => setCreateOpen(true)}\n startIcon={<PlusIcon size={iconSize.smallest}/>}\n >\n Create Branch\n </Button>\n </div>\n ) : (\n branches.map(branch => (\n <div\n key={branch.name}\n onClick={() => setSelectedBranch(branch.name)}\n className={cls(\n \"flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all\",\n selectedBranch === branch.name\n ? \"bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30\"\n : \"hover:bg-surface-100 dark:hover:bg-surface-950\"\n )}\n >\n <div className=\"w-2 h-2 rounded-full shrink-0 bg-emerald-500\"/>\n <div className=\"flex-1 min-w-0\">\n <Typography variant=\"body2\" className=\"truncate font-medium text-[13px]\">{branch.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate text-[11px]\">\n from {branch.parentDatabase} · {formatRelative(branch.createdAt)}\n </Typography>\n </div>\n {branch.sizeBytes !== undefined && (\n <Typography variant=\"caption\" color=\"disabled\" className=\"font-mono text-[10px] shrink-0\">\n {formatSize(branch.sizeBytes)}\n </Typography>\n )}\n </div>\n ))\n )}\n </div>\n </div>\n\n {/* ── Detail Panel ── */}\n <div className=\"flex-1 flex flex-col min-w-0 h-full overflow-hidden\">\n {!selected ? (\n <div className=\"flex flex-col items-center justify-center h-full gap-3\">\n <GitBranchIcon size={iconSize.large} className=\"text-surface-200 dark:text-surface-700\"/>\n <Typography variant=\"body2\" color=\"disabled\">\n {branches.length === 0 ? \"Create a branch to get started\" : \"Select a branch to view details\"}\n </Typography>\n </div>\n ) : (\n <>\n {/* Header */}\n <div className={cls(\"flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]\", defaultBorderMixin)}>\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-emerald-500\"/>\n <div className=\"min-w-0\">\n <Typography variant=\"subtitle1\" className=\"font-semibold truncate\">{selected.name}</Typography>\n <Typography variant=\"caption\" color=\"secondary\" className=\"truncate\">\n Created from <span className=\"font-mono text-[11px]\">{selected.parentDatabase}</span>\n </Typography>\n </div>\n </div>\n <div className=\"flex items-center gap-2 shrink-0\">\n <Button\n size=\"small\"\n color=\"error\"\n variant=\"outlined\"\n onClick={() => setDeleteTarget(selected.name)}\n startIcon={<Trash2Icon size={iconSize.smallest}/>}\n >\n Delete\n </Button>\n </div>\n </div>\n\n {/* Info Cards */}\n <div className=\"px-5 py-4 bg-surface-50 dark:bg-surface-900/50\">\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-3\">\n <StatCard label=\"Branch Name\" value={selected.name} mono/>\n <StatCard label=\"Source Database\" value={selected.parentDatabase} mono/>\n <StatCard label=\"Created\" value={formatRelative(selected.createdAt)}/>\n <StatCard label=\"Size\" value={formatSize(selected.sizeBytes)}/>\n </div>\n </div>\n\n {/* Usage Info */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"text-[13px]\">\n <strong>How to use this branch:</strong> point this checkout at it with\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]\">rebase db branch switch {selected.name}</code>\n — every later command uses it, and\n <code className=\"mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]\">rebase db branch switch --off</code>\n goes back to the main database. Changes made here don't affect it.\n </Typography>\n </Alert>\n <div className=\"mt-4 p-4 rounded-lg border bg-surface-50 dark:bg-surface-900 border-surface-200 dark:border-surface-700\">\n <Typography variant=\"caption\" className=\"text-[10px] uppercase tracking-wider text-surface-400 mb-2 block font-medium\">Connection Details</Typography>\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2\">\n {/* The *database*, not the branch name. They are not the\n same string — the driver prefixes every branch database\n with `rb_` — and a connection string built from the name\n on this pane pointed at a database that does not\n exist. */}\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Database:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{`${BRANCH_DB_PREFIX}${selected.name}`}</Typography>\n </div>\n <div className=\"flex items-start gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Connect with:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px] break-all\">\n {`DATABASE_URL=…/${BRANCH_DB_PREFIX}${selected.name}`}\n </Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Branched from:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">{selected.parentDatabase}</Typography>\n </div>\n <div className=\"flex items-center gap-2\">\n <Typography variant=\"caption\" color=\"secondary\" className=\"w-24 shrink-0 text-[11px]\">Created at:</Typography>\n <Typography variant=\"body2\" className=\"font-mono text-[12px]\">\n {selected.createdAt instanceof Date\n ? selected.createdAt.toLocaleString()\n : new Date(selected.createdAt).toLocaleString()}\n </Typography>\n </div>\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n {/* ── Create Dialog ── */}\n <Dialog open={createOpen} onOpenChange={(open) => {\n setCreateOpen(open);\n if (!open) setCreateError(null);\n }}>\n <DialogTitle>Create New Branch</DialogTitle>\n <DialogContent className=\"space-y-4 min-w-[400px]\">\n <Typography variant=\"body2\" color=\"secondary\" className=\"text-[13px]\">\n Create an isolated database copy. The branch will be a full clone of the source database at this point in time.\n </Typography>\n {createError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"text-[13px]\">{createError}</Typography>\n </Alert>\n )}\n <div>\n <TextField\n label=\"Branch Name\"\n value={newBranchName}\n onChange={(e) => setNewBranchName(e.target.value)}\n placeholder=\"e.g. feature-auth, staging, preview-pr-42\"\n size=\"small\"\n autoFocus\n />\n </div>\n {branches.length > 0 && (\n <div>\n <Select\n label=\"Source Database\"\n value={sourceBranch ?? \"__main__\"}\n onValueChange={(v) => setSourceBranch(v === \"__main__\" ? undefined : v)}\n placeholder=\"Default (main database)\"\n size=\"small\"\n >\n <SelectItem value=\"__main__\">Default (main database)</SelectItem>\n {branches.map(b => (\n <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>\n ))}\n </Select>\n </div>\n )}\n </DialogContent>\n <DialogActions>\n <Button variant=\"text\" onClick={() => setCreateOpen(false)} disabled={creating}>\n Cancel\n </Button>\n <Button\n color=\"primary\"\n onClick={handleCreate}\n disabled={!newBranchName.trim() || creating}\n startIcon={creating ? <CircularProgress size=\"smallest\"/> : <PlusIcon size={iconSize.smallest}/>}\n >\n {creating ? \"Creating...\" : \"Create Branch\"}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* ── Delete Confirm ── */}\n <ConfirmationDialog\n open={!!deleteTarget}\n onAccept={handleDelete}\n onCancel={() => setDeleteTarget(null)}\n title=\"Delete Branch\"\n body={\n <Typography variant=\"body2\">\n Are you sure you want to permanently delete the branch <strong>{deleteTarget}</strong>?\n This action cannot be undone and all data in this branch will be lost.\n </Typography>\n }\n loading={deleting}\n />\n </div>\n );\n}\n\nfunction StatCard({ label, value, mono }: {\n label: string; value: string; mono?: boolean;\n}) {\n return (\n <div className={cls(\"px-3 py-2 rounded-lg border bg-white dark:bg-surface-900\", defaultBorderMixin)}>\n <Typography variant=\"caption\" color=\"secondary\" className=\"text-[10px] uppercase tracking-wider font-medium\">{label}</Typography>\n <Typography variant=\"body2\" className={cls(\n \"mt-0.5 font-semibold text-[13px] truncate\",\n mono && \"font-mono\"\n )}>{value}</Typography>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as classifyLoadFailure } from "./load-failure-C6OMJSGC.js";
|
|
2
|
+
import { t as LoadFailureView } from "./load-failure-view-CBTB_h-h.js";
|
|
3
|
+
import { useRebaseClient, useSnackbarController, useTranslation } from "@rebasepro/app";
|
|
2
4
|
import { useEffect, useRef, useState } from "react";
|
|
3
5
|
import { AlertCircleIcon, Button, CalendarIcon, CheckCircleIcon, Chip, CircularProgress, HistoryIcon, IconButton, PauseIcon, PlayIcon, RefreshCwIcon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
4
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -35,12 +37,15 @@ var stateColors = {
|
|
|
35
37
|
function CronJobsView() {
|
|
36
38
|
const client = useRebaseClient();
|
|
37
39
|
const snackbar = useSnackbarController();
|
|
40
|
+
const { t } = useTranslation();
|
|
38
41
|
const [jobs, setJobs] = useState([]);
|
|
39
42
|
const [loading, setLoading] = useState(true);
|
|
40
43
|
const [selectedId, setSelectedId] = useState(null);
|
|
41
44
|
const [logs, setLogs] = useState([]);
|
|
42
45
|
const [logsLoading, setLogsLoading] = useState(false);
|
|
43
46
|
const [triggering, setTriggering] = useState(null);
|
|
47
|
+
/** Why the job listing failed, classified — see `load-failure.ts`. */
|
|
48
|
+
const [failure, setFailure] = useState(null);
|
|
44
49
|
const clientRef = useRef(client);
|
|
45
50
|
clientRef.current = client;
|
|
46
51
|
const snackbarRef = useRef(snackbar);
|
|
@@ -55,12 +60,12 @@ function CronJobsView() {
|
|
|
55
60
|
}
|
|
56
61
|
try {
|
|
57
62
|
const res = await c.cron.listJobs();
|
|
58
|
-
if (!cancelled)
|
|
63
|
+
if (!cancelled) {
|
|
64
|
+
setJobs(res.jobs);
|
|
65
|
+
setFailure(null);
|
|
66
|
+
}
|
|
59
67
|
} catch (e) {
|
|
60
|
-
if (!cancelled)
|
|
61
|
-
type: "error",
|
|
62
|
-
message: e instanceof Error ? e.message : String(e)
|
|
63
|
-
});
|
|
68
|
+
if (!cancelled) setFailure(classifyLoadFailure(e));
|
|
64
69
|
} finally {
|
|
65
70
|
if (!cancelled) setLoading(false);
|
|
66
71
|
}
|
|
@@ -116,11 +121,9 @@ function CronJobsView() {
|
|
|
116
121
|
try {
|
|
117
122
|
const res = await c.cron.listJobs();
|
|
118
123
|
setJobs(res.jobs);
|
|
124
|
+
setFailure(null);
|
|
119
125
|
} catch (e) {
|
|
120
|
-
|
|
121
|
-
type: "error",
|
|
122
|
-
message: e instanceof Error ? e.message : String(e)
|
|
123
|
-
});
|
|
126
|
+
setFailure(classifyLoadFailure(e));
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
async function refreshLogs(id) {
|
|
@@ -183,6 +186,13 @@ function CronJobsView() {
|
|
|
183
186
|
className: "flex items-center justify-center h-full",
|
|
184
187
|
children: /* @__PURE__ */ jsx(CircularProgress, {})
|
|
185
188
|
});
|
|
189
|
+
if (failure) return /* @__PURE__ */ jsx(LoadFailureView, {
|
|
190
|
+
failure,
|
|
191
|
+
title: t("studio_cron_read_failed"),
|
|
192
|
+
deniedTitle: t("studio_cron_denied_title"),
|
|
193
|
+
deniedHint: t("studio_cron_denied_hint"),
|
|
194
|
+
onRetry: refreshJobs
|
|
195
|
+
});
|
|
186
196
|
if (jobs.length === 0) return /* @__PURE__ */ jsxs("div", {
|
|
187
197
|
className: "flex flex-col items-center justify-center h-full gap-4 text-center p-8",
|
|
188
198
|
children: [
|
|
@@ -193,25 +203,20 @@ function CronJobsView() {
|
|
|
193
203
|
/* @__PURE__ */ jsx(Typography, {
|
|
194
204
|
variant: "h6",
|
|
195
205
|
color: "secondary",
|
|
196
|
-
children: "
|
|
206
|
+
children: t("studio_cron_empty_title")
|
|
197
207
|
}),
|
|
198
|
-
/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
199
209
|
variant: "body2",
|
|
200
210
|
color: "disabled",
|
|
201
211
|
className: "max-w-md",
|
|
202
|
-
children:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
className: "text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono",
|
|
211
|
-
children: "CronJobDefinition"
|
|
212
|
-
}),
|
|
213
|
-
"."
|
|
214
|
-
]
|
|
212
|
+
children: t("studio_cron_empty_body")
|
|
213
|
+
}),
|
|
214
|
+
/* @__PURE__ */ jsx("a", {
|
|
215
|
+
href: "https://rebase.pro/docs/backend/cron-jobs",
|
|
216
|
+
target: "_blank",
|
|
217
|
+
rel: "noreferrer",
|
|
218
|
+
className: "text-primary text-sm underline",
|
|
219
|
+
children: t("studio_read_the_docs")
|
|
215
220
|
})
|
|
216
221
|
]
|
|
217
222
|
});
|
|
@@ -231,7 +236,7 @@ function CronJobsView() {
|
|
|
231
236
|
/* @__PURE__ */ jsx(Typography, {
|
|
232
237
|
variant: "subtitle2",
|
|
233
238
|
className: "font-semibold",
|
|
234
|
-
children: "
|
|
239
|
+
children: t("studio_tool_cron")
|
|
235
240
|
}),
|
|
236
241
|
/* @__PURE__ */ jsx(Chip, {
|
|
237
242
|
size: "smallest",
|
|
@@ -513,4 +518,4 @@ function LogRow({ log }) {
|
|
|
513
518
|
//#endregion
|
|
514
519
|
export { CronJobsView };
|
|
515
520
|
|
|
516
|
-
//# sourceMappingURL=CronJobsView-
|
|
521
|
+
//# sourceMappingURL=CronJobsView-CLOGjb-L.js.map
|