@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.
Files changed (81) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +12 -4
  3. package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
  4. package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
  5. package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
  6. package/dist/BackupsView-DVkKzaPR.js.map +1 -0
  7. package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
  8. package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
  9. package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
  10. package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
  11. package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
  12. package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
  13. package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
  14. package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
  15. package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
  16. package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
  17. package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
  18. package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
  19. package/dist/components/RLSEditor/saveRules.d.ts +38 -0
  20. package/dist/components/RebaseStudio.d.ts +1 -1
  21. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
  22. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
  23. package/dist/components/load-failure-view.d.ts +17 -0
  24. package/dist/components/load-failure.d.ts +47 -0
  25. package/dist/index.es.js +56 -36
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/load-failure-C6OMJSGC.js +31 -0
  28. package/dist/load-failure-C6OMJSGC.js.map +1 -0
  29. package/dist/load-failure-view-CBTB_h-h.js +48 -0
  30. package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
  31. package/package.json +36 -21
  32. package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
  33. package/dist/BackupsView-DNS6LdVg.js.map +0 -1
  34. package/dist/BranchesView-CM3B-ER3.js.map +0 -1
  35. package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
  36. package/dist/RLSEditor-CBX4uD66.js.map +0 -1
  37. package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
  38. package/dist/StorageView-BGDeC5GI.js.map +0 -1
  39. package/dist/components/StorageView/storage-failure.d.ts +0 -34
  40. package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
  41. package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
  42. package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
  43. package/src/components/ApiExplorer/parseSpec.ts +0 -104
  44. package/src/components/ApiExplorer/types.ts +0 -84
  45. package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
  46. package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
  47. package/src/components/ApiKeys/permissions.ts +0 -145
  48. package/src/components/AuthSimulationSelector.tsx +0 -72
  49. package/src/components/Backups/BackupsView.tsx +0 -185
  50. package/src/components/Branches/BranchesView.tsx +0 -386
  51. package/src/components/CronJobs/CronJobsView.tsx +0 -413
  52. package/src/components/JSEditor/JSEditor.tsx +0 -1100
  53. package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
  54. package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
  55. package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
  56. package/src/components/LogsExplorer/useLogTail.ts +0 -356
  57. package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
  58. package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
  59. package/src/components/RLSEditor/index.ts +0 -1
  60. package/src/components/RLSEditor/policy-presets.ts +0 -176
  61. package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
  62. package/src/components/RebaseStudio.tsx +0 -174
  63. package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
  64. package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
  65. package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
  66. package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
  67. package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
  68. package/src/components/SQLEditor/sql_editor_types.ts +0 -18
  69. package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
  70. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
  71. package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
  72. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
  73. package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
  74. package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
  75. package/src/components/StorageView/StorageView.tsx +0 -1588
  76. package/src/components/StorageView/storage-failure.ts +0 -63
  77. package/src/components/StudioHomePage.tsx +0 -430
  78. package/src/index.ts +0 -31
  79. package/src/utils/parseSpec.test.ts +0 -309
  80. package/src/utils/sql_utils.test.ts +0 -265
  81. package/src/utils/sql_utils.ts +0 -291
@@ -1,4 +1,6 @@
1
- import { useRebaseClient, useSnackbarController } from "@rebasepro/app";
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
- snackbarRef.current.open({
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: "Backups"
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: !configured ? /* @__PURE__ */ jsx(Alert, {
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: "Backups are not configured." }),
144
- " Set",
145
- /* @__PURE__ */ jsx("code", {
146
- className: "mx-1 px-1.5 py-0.5 rounded bg-surface-100 dark:bg-surface-950 font-mono text-[12px]",
147
- children: "BACKUP_DESTINATION"
148
- }),
149
- "(a local path or an ",
150
- /* @__PURE__ */ jsx("code", {
151
- className: "font-mono text-[12px]",
152
- children: "s3://"
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 ? "Downloading…" : "Download"
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-DNS6LdVg.js.map
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 { ConfirmationDialog, useRebaseContext, useSnackbarController } from "@rebasepro/app";
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
- snackbarRef.current.open({
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: e instanceof Error ? e.message : String(e)
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: "Database Branching Not Available"
135
+ children: t("studio_branches_unavailable_title")
110
136
  }),
111
- /* @__PURE__ */ jsxs(Typography, {
137
+ /* @__PURE__ */ jsx(Typography, {
112
138
  variant: "body2",
113
139
  color: "disabled",
114
140
  className: "max-w-md",
115
- children: [
116
- "Branching requires a PostgreSQL backend with an admin connection configured. Set ",
117
- /* @__PURE__ */ jsx("code", {
118
- className: "text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono",
119
- children: "adminConnectionString"
120
- }),
121
- " in your server configuration."
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: "Branches"
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: branches.length === 0 ? /* @__PURE__ */ jsxs("div", {
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
- " Switch your application's database connection to",
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: selected.name
347
+ children: "rebase db branch switch --off"
311
348
  }),
312
- "to work with an isolated copy of your data. Changes made to this branch won't affect your main database."
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: setCreateOpen,
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-CM3B-ER3.js.map
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&apos;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 { useRebaseClient, useSnackbarController } from "@rebasepro/app";
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) setJobs(res.jobs);
63
+ if (!cancelled) {
64
+ setJobs(res.jobs);
65
+ setFailure(null);
66
+ }
59
67
  } catch (e) {
60
- if (!cancelled) snackbarRef.current.open({
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
- snackbarRef.current.open({
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: "No Cron Jobs Registered"
206
+ children: t("studio_cron_empty_title")
197
207
  }),
198
- /* @__PURE__ */ jsxs(Typography, {
208
+ /* @__PURE__ */ jsx(Typography, {
199
209
  variant: "body2",
200
210
  color: "disabled",
201
211
  className: "max-w-md",
202
- children: [
203
- "Create a file in your ",
204
- /* @__PURE__ */ jsx("code", {
205
- className: "text-xs bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded font-mono",
206
- children: "crons/"
207
- }),
208
- " directory that default-exports a ",
209
- /* @__PURE__ */ jsx("code", {
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: "Cron Jobs"
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-BHtJZeJZ.js.map
521
+ //# sourceMappingURL=CronJobsView-CLOGjb-L.js.map