@rebasepro/studio 0.17.3 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/dist/index.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IconForView, SchemaDriftBanner, StudioBridgeContext, StudioBridgeProvider, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useStudioBreadcrumbs, useStudioBreadcrumbs as useStudioBreadcrumbs$1, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController } from "@rebasepro/app";
|
|
1
|
+
import { IconForView, SchemaDriftBanner, StudioBridgeContext, StudioBridgeProvider, useNavigationGroupLabel, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useStudioBreadcrumbs, useStudioBreadcrumbs as useStudioBreadcrumbs$1, useStudioCollectionRegistry, useStudioNavigationState, useStudioSidePanelController, useStudioUrlController, useTranslation } from "@rebasepro/app";
|
|
2
2
|
import React, { Suspense, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Card, CircularProgressCenter, Container, ExpandablePanel, Typography, cls, lazyChunk } from "@rebasepro/ui";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -93,6 +93,7 @@ function StudioHomePage({ additionalActions, additionalChildrenStart, additional
|
|
|
93
93
|
hiddenGroups
|
|
94
94
|
]);
|
|
95
95
|
const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(useMemo(() => filteredSections.map((s) => s.label), [filteredSections]));
|
|
96
|
+
const groupLabel = useNavigationGroupLabel();
|
|
96
97
|
return /* @__PURE__ */ jsx("div", {
|
|
97
98
|
ref: containerRef,
|
|
98
99
|
className: "py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800",
|
|
@@ -126,7 +127,7 @@ function StudioHomePage({ additionalActions, additionalChildrenStart, additional
|
|
|
126
127
|
component: "h2",
|
|
127
128
|
color: "secondary",
|
|
128
129
|
className: cls("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
129
|
-
children: section.label
|
|
130
|
+
children: groupLabel(section.label)
|
|
130
131
|
}),
|
|
131
132
|
children: /* @__PURE__ */ jsx("div", {
|
|
132
133
|
className: "mt-4 pt-0",
|
|
@@ -549,17 +550,17 @@ function SyntaxHighlightedSnippet() {
|
|
|
549
550
|
}
|
|
550
551
|
//#endregion
|
|
551
552
|
//#region src/components/RebaseStudio.tsx
|
|
552
|
-
var SQLEditor = lazyChunk(() => import("./SQLEditor-
|
|
553
|
+
var SQLEditor = lazyChunk(() => import("./SQLEditor-BfNA8xM3.js").then((m) => ({ default: m.SQLEditor })));
|
|
553
554
|
var JSEditor = lazyChunk(() => import("./JSEditor-75FTeu6P.js").then((m) => ({ default: m.JSEditor })));
|
|
554
|
-
var RLSEditor = lazyChunk(() => import("./RLSEditor-
|
|
555
|
-
var StorageView = lazyChunk(() => import("./StorageView-
|
|
556
|
-
var CronJobsView = lazyChunk(() => import("./CronJobsView-
|
|
557
|
-
var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-
|
|
558
|
-
var BranchesView = lazyChunk(() => import("./BranchesView-
|
|
559
|
-
var BackupsView = lazyChunk(() => import("./BackupsView-
|
|
555
|
+
var RLSEditor = lazyChunk(() => import("./RLSEditor-Sa1D34ny.js").then((m) => ({ default: m.RLSEditor })));
|
|
556
|
+
var StorageView = lazyChunk(() => import("./StorageView-Ed9Q_VlB.js").then((m) => ({ default: m.StorageView })));
|
|
557
|
+
var CronJobsView = lazyChunk(() => import("./CronJobsView-CLOGjb-L.js").then((m) => ({ default: m.CronJobsView })));
|
|
558
|
+
var SchemaVisualizer = lazyChunk(() => import("./SchemaVisualizer-DTDMYL6o.js").then((m) => ({ default: m.SchemaVisualizer })));
|
|
559
|
+
var BranchesView = lazyChunk(() => import("./BranchesView-Df8hQ8wE.js").then((m) => ({ default: m.BranchesView })));
|
|
560
|
+
var BackupsView = lazyChunk(() => import("./BackupsView-DVkKzaPR.js").then((m) => ({ default: m.BackupsView })));
|
|
560
561
|
var ApiExplorer = lazyChunk(() => import("./ApiExplorer-9iwGvNnt.js").then((m) => ({ default: m.ApiExplorer })));
|
|
561
562
|
var LogsExplorer = lazyChunk(() => import("./LogsExplorer-DI8SVpya.js").then((m) => ({ default: m.LogsExplorer })));
|
|
562
|
-
var ApiKeysView = lazyChunk(() => import("./ApiKeysView-
|
|
563
|
+
var ApiKeysView = lazyChunk(() => import("./ApiKeysView-EIvegrUk.js").then((m) => ({ default: m.ApiKeysView })));
|
|
563
564
|
/**
|
|
564
565
|
* Declarative component to configure the Studio in Rebase.
|
|
565
566
|
* Renders nothing — purely registers config into the RebaseRegistry.
|
|
@@ -572,8 +573,9 @@ var ApiKeysView = lazyChunk(() => import("./ApiKeysView-DiCurTEU.js").then((m) =
|
|
|
572
573
|
* schema-editing tool and belongs beside SQL and RLS.
|
|
573
574
|
*/
|
|
574
575
|
var DEFAULT_HOME_PAGE = /* @__PURE__ */ jsx(StudioHomePage, {});
|
|
575
|
-
function RebaseStudio({ tools, homePage }) {
|
|
576
|
+
function RebaseStudio({ tools, homePage, devViews: extraViews }) {
|
|
576
577
|
const dispatch = useRebaseRegistryDispatch();
|
|
578
|
+
const { t, i18n } = useTranslation();
|
|
577
579
|
const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;
|
|
578
580
|
const devViews = useMemo(() => {
|
|
579
581
|
const views = [];
|
|
@@ -596,94 +598,112 @@ function RebaseStudio({ tools, homePage }) {
|
|
|
596
598
|
});
|
|
597
599
|
if (activeTools.includes("sql")) views.push({
|
|
598
600
|
slug: "sql",
|
|
599
|
-
name: "
|
|
601
|
+
name: t("studio_tool_sql"),
|
|
600
602
|
group: "Database",
|
|
601
603
|
icon: "terminal",
|
|
602
|
-
description: "
|
|
604
|
+
description: t("studio_tool_sql_description"),
|
|
603
605
|
view: suspense(/* @__PURE__ */ jsx(SQLEditor, {}))
|
|
604
606
|
});
|
|
605
607
|
if (activeTools.includes("js")) views.push({
|
|
606
608
|
slug: "js",
|
|
607
|
-
name: "
|
|
609
|
+
name: t("studio_tool_js"),
|
|
608
610
|
group: "Compute",
|
|
609
611
|
icon: "code",
|
|
610
|
-
description: "
|
|
612
|
+
description: t("studio_tool_js_description"),
|
|
611
613
|
view: suspense(/* @__PURE__ */ jsx(JSEditor, {}))
|
|
612
614
|
});
|
|
613
615
|
if (activeTools.includes("rls")) views.push({
|
|
614
616
|
slug: "rls",
|
|
615
|
-
name: "
|
|
617
|
+
name: t("studio_tool_rls"),
|
|
616
618
|
group: "Database",
|
|
617
619
|
icon: "ShieldCheck",
|
|
618
|
-
description: "
|
|
620
|
+
description: t("studio_tool_rls_description"),
|
|
619
621
|
view: suspense(/* @__PURE__ */ jsx(RLSEditor, {}))
|
|
620
622
|
});
|
|
621
623
|
if (activeTools.includes("storage")) views.push({
|
|
622
624
|
slug: "storage",
|
|
623
|
-
name: "
|
|
625
|
+
name: t("studio_tool_storage"),
|
|
624
626
|
group: "Storage",
|
|
625
627
|
icon: "HardDrive",
|
|
626
|
-
description: "
|
|
628
|
+
description: t("studio_tool_storage_description"),
|
|
627
629
|
view: suspense(/* @__PURE__ */ jsx(StorageView, {}))
|
|
628
630
|
});
|
|
629
631
|
if (activeTools.includes("cron")) views.push({
|
|
630
632
|
slug: "cron",
|
|
631
|
-
name: "
|
|
633
|
+
name: t("studio_tool_cron"),
|
|
632
634
|
group: "Compute",
|
|
633
635
|
icon: "Clock",
|
|
634
|
-
description: "
|
|
636
|
+
description: t("studio_tool_cron_description"),
|
|
635
637
|
view: suspense(/* @__PURE__ */ jsx(CronJobsView, {}))
|
|
636
638
|
});
|
|
637
639
|
if (activeTools.includes("schema-visualizer")) views.push({
|
|
638
640
|
slug: "schema-visualizer",
|
|
639
|
-
name: "
|
|
641
|
+
name: t("studio_tool_schema_visualizer"),
|
|
640
642
|
group: "Database",
|
|
641
643
|
icon: "Network",
|
|
642
|
-
description: "
|
|
644
|
+
description: t("studio_tool_schema_visualizer_description"),
|
|
643
645
|
view: suspense(/* @__PURE__ */ jsx(SchemaVisualizer, {}))
|
|
644
646
|
});
|
|
645
647
|
if (activeTools.includes("branches")) views.push({
|
|
646
648
|
slug: "branches",
|
|
647
|
-
name: "
|
|
649
|
+
name: t("studio_tool_branches"),
|
|
648
650
|
group: "Database",
|
|
649
651
|
icon: "GitBranch",
|
|
650
|
-
description: "
|
|
652
|
+
description: t("studio_tool_branches_description"),
|
|
651
653
|
view: suspense(/* @__PURE__ */ jsx(BranchesView, {}))
|
|
652
654
|
});
|
|
653
655
|
if (activeTools.includes("backups")) views.push({
|
|
654
656
|
slug: "backups",
|
|
655
|
-
name: "
|
|
657
|
+
name: t("studio_tool_backups"),
|
|
656
658
|
group: "Database",
|
|
657
659
|
icon: "Database",
|
|
658
|
-
description: "
|
|
660
|
+
description: t("studio_tool_backups_description"),
|
|
659
661
|
view: suspense(/* @__PURE__ */ jsx(BackupsView, {}))
|
|
660
662
|
});
|
|
661
663
|
if (activeTools.includes("api")) views.push({
|
|
662
664
|
slug: "api",
|
|
663
|
-
name: "
|
|
665
|
+
name: t("studio_tool_api"),
|
|
664
666
|
group: "API",
|
|
665
667
|
icon: "BookOpen",
|
|
666
|
-
description: "
|
|
668
|
+
description: t("studio_tool_api_description"),
|
|
667
669
|
view: suspense(/* @__PURE__ */ jsx(ApiExplorer, {}))
|
|
668
670
|
});
|
|
669
671
|
if (activeTools.includes("logs")) views.push({
|
|
670
672
|
slug: "logs",
|
|
671
|
-
name: "
|
|
673
|
+
name: t("studio_tool_logs"),
|
|
672
674
|
group: "Database",
|
|
673
675
|
icon: "Activity",
|
|
674
|
-
description: "
|
|
676
|
+
description: t("studio_tool_logs_description"),
|
|
675
677
|
view: suspense(/* @__PURE__ */ jsx(LogsExplorer, {}))
|
|
676
678
|
});
|
|
677
679
|
if (activeTools.includes("api-keys")) views.push({
|
|
678
680
|
slug: "api-keys",
|
|
679
|
-
name: "
|
|
681
|
+
name: t("studio_tool_api_keys"),
|
|
680
682
|
group: "Access Control",
|
|
681
683
|
icon: "KeyRound",
|
|
682
|
-
description: "
|
|
684
|
+
description: t("studio_tool_api_keys_description"),
|
|
683
685
|
view: suspense(/* @__PURE__ */ jsx(ApiKeysView, {}))
|
|
684
686
|
});
|
|
685
687
|
return views;
|
|
686
|
-
}, [
|
|
688
|
+
}, [
|
|
689
|
+
tools ? tools.join(",") : "",
|
|
690
|
+
t,
|
|
691
|
+
i18n.language
|
|
692
|
+
]);
|
|
693
|
+
/**
|
|
694
|
+
* Your own tools, keyed the same way `tools` is and for the same reason:
|
|
695
|
+
* callers write `devViews={[…]}` inline, so the array is new on every
|
|
696
|
+
* render. Re-registering would tear down and remount whichever Studio view
|
|
697
|
+
* was on screen — a half-typed SQL query, an open policy editor.
|
|
698
|
+
*
|
|
699
|
+
* The key is the identity of each view, not its element: a view's `view` is
|
|
700
|
+
* resolved by the router at render time, so a fresh element for the same
|
|
701
|
+
* slug is not a configuration change.
|
|
702
|
+
*/
|
|
703
|
+
const extraViewsKey = (extraViews ?? []).map((v) => `${v.slug}|${v.name}|${v.group ?? ""}|${v.hideFromNavigation ?? false}`).join(",");
|
|
704
|
+
const extraViewsRef = React.useRef(extraViews);
|
|
705
|
+
extraViewsRef.current = extraViews;
|
|
706
|
+
const allDevViews = useMemo(() => [...devViews, ...extraViewsRef.current ?? []], [devViews, extraViewsKey]);
|
|
687
707
|
const homePageRef = React.useRef(resolvedHomePage);
|
|
688
708
|
homePageRef.current = resolvedHomePage;
|
|
689
709
|
const toolsRef = React.useRef(tools);
|
|
@@ -692,10 +712,10 @@ function RebaseStudio({ tools, homePage }) {
|
|
|
692
712
|
dispatch.registerStudio({
|
|
693
713
|
tools: toolsRef.current,
|
|
694
714
|
homePage: homePageRef.current,
|
|
695
|
-
devViews
|
|
715
|
+
devViews: allDevViews
|
|
696
716
|
});
|
|
697
717
|
return () => dispatch.unregisterStudio();
|
|
698
|
-
}, [dispatch,
|
|
718
|
+
}, [dispatch, allDevViews]);
|
|
699
719
|
return null;
|
|
700
720
|
}
|
|
701
721
|
//#endregion
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":[],"sources":["../src/components/StudioHomePage.tsx","../src/components/RebaseStudio.tsx"],"sourcesContent":["import type { AppView, HomePageSection, PluginGenericProps } from \"@rebasepro/cms-types\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { Card, cls, Container, ExpandablePanel, Typography } from \"@rebasepro/ui\";\nimport { IconForView, useRebaseContext, useRebaseRegistry, useRestoreScroll, useSlot } from \"@rebasepro/app\";\nimport { useNavigate } from \"react-router\";\nimport { useStudioBreadcrumbs, SchemaDriftBanner } from \"@rebasepro/app\";\n\n/* ═══════════════════════════════════════════════════════════════\n Studio tool sections, derived from the registered Studio views\n ═══════════════════════════════════════════════════════════════ */\n\ninterface StudioTool {\n path: string;\n name: string;\n description: string;\n icon?: string | React.ReactNode;\n}\n\ninterface StudioSection {\n label: string;\n tools: StudioTool[];\n}\n\n/** The view metadata the cards need — not the rendered `view` itself. */\ntype StudioViewMeta = Pick<AppView, \"slug\" | \"name\" | \"group\" | \"description\" | \"icon\" | \"hideFromNavigation\">;\n\n/** Group order for the home page; unknown groups are appended in view order. */\nconst GROUP_ORDER = [\"Database\", \"Compute\", \"Storage\", \"API\", \"Access Control\"];\n\nconst UNGROUPED_LABEL = \"Tools\";\n\n/**\n * Build the home page sections from the Studio views that are actually\n * registered, so a card exists if and only if its route does. A hand-written\n * list drifts: it used to advertise Users and Roles pages that 404, while\n * omitting the Backups view that does exist.\n */\nfunction buildSections(views: StudioViewMeta[]): StudioSection[] {\n const byGroup = new Map<string, StudioTool[]>();\n\n for (const view of views) {\n if (view.hideFromNavigation) continue;\n const label = view.group ?? UNGROUPED_LABEL;\n const tools = byGroup.get(label) ?? [];\n tools.push({\n path: `/${view.slug}`,\n name: view.name,\n description: view.description ?? \"\",\n icon: view.icon\n });\n byGroup.set(label, tools);\n }\n\n const ordered = [\n ...GROUP_ORDER.filter(g => byGroup.has(g)),\n ...[...byGroup.keys()].filter(g => !GROUP_ORDER.includes(g))\n ];\n\n return ordered.map(label => ({ label,\ntools: byGroup.get(label)! }));\n}\n\n/* ═══════════════════════════════════════════════════════════════ */\n\nconst COLLAPSED_STORAGE_KEY = \"rebase-studio-home-collapsed\";\n\nfunction useStudioCollapsedGroups(groupNames: string[]) {\n const [collapsed, setCollapsed] = useState<Set<string>>(() => {\n try {\n const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);\n return stored ? new Set(JSON.parse(stored)) : new Set<string>();\n } catch {\n return new Set<string>();\n }\n });\n\n const isGroupCollapsed = (name: string) => collapsed.has(name);\n\n const toggleGroupCollapsed = (name: string) => {\n setCollapsed(prev => {\n const next = new Set(prev);\n if (next.has(name)) {\n next.delete(name);\n } else {\n next.add(name);\n }\n try {\n localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));\n } catch { /* noop */ }\n return next;\n });\n };\n\n return { isGroupCollapsed, toggleGroupCollapsed };\n}\n\nexport function StudioHomePage({\n additionalActions,\n additionalChildrenStart,\n additionalChildrenEnd,\n sections,\n hiddenGroups\n}: {\n additionalActions?: React.ReactNode;\n additionalChildrenStart?: React.ReactNode;\n additionalChildrenEnd?: React.ReactNode;\n sections?: HomePageSection[];\n hiddenGroups?: string[];\n}) {\n const context = useRebaseContext();\n const breadcrumbs = useStudioBreadcrumbs();\n const navigate = useNavigate();\n const registry = useRebaseRegistry();\n\n useEffect(() => {\n breadcrumbs.set({ breadcrumbs: [] });\n }, [breadcrumbs.set]);\n\n const { containerRef } = useRestoreScroll();\n\n const sectionProps: PluginGenericProps = { context };\n\n const pluginActions = useSlot(\"home.actions\", sectionProps);\n\n // The collection editor (\"schema\") is not part of `devViews` — RebaseNavigation\n // injects it when the admin enables a collection editor. Mirror that condition so\n // the card tracks the route.\n const schemaEnabled = Boolean(registry.studioConfig && registry.cmsConfig?.collectionEditor);\n\n const filteredSections = useMemo(() => {\n const views: StudioViewMeta[] = [];\n if (schemaEnabled) {\n views.push({\n slug: \"schema\",\n name: \"Collections\",\n group: \"Database\",\n icon: \"LayoutList\",\n description: \"Define and manage your data model and collection schemas\"\n });\n }\n views.push(...(registry.studioConfig?.devViews ?? []));\n return buildSections(views).filter(s => !hiddenGroups?.includes(s.label));\n }, [registry.studioConfig?.devViews, schemaEnabled, hiddenGroups]);\n\n const groupNames = useMemo(\n () => filteredSections.map(s => s.label),\n [filteredSections]\n );\n\n const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(groupNames);\n\n return (\n <div ref={containerRef} className=\"py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800\">\n <Container maxWidth=\"6xl\">\n <div className=\"mb-4 flex flex-col gap-2\">\n <SchemaDriftBanner />\n </div>\n\n {(additionalActions || pluginActions) && (\n <div className=\"w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4 justify-end\">\n {additionalActions}\n {pluginActions}\n </div>\n )}\n\n {additionalChildrenStart}\n\n {/* ── Tool sections ── */}\n {filteredSections.map((section) => {\n const sectionCollapsed = isGroupCollapsed(section.label);\n\n return (\n <div key={section.label} className=\"my-10\">\n <ExpandablePanel\n invisible\n expanded={!sectionCollapsed}\n onExpandedChange={(open) => {\n if (open !== !sectionCollapsed) {\n toggleGroupCollapsed(section.label);\n }\n }}\n className=\"mt-6\"\n titleClassName={cls(\n \"min-h-0 p-0 border-none\",\n \"rounded flex items-center justify-between w-full\",\n \"hover:bg-transparent\",\n \"cursor-pointer select-none\",\n sectionCollapsed && \"bg-surface-100 dark:bg-surface-900/50\"\n )}\n innerClassName=\"mt-4 pt-0\"\n title={\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {section.label}\n </Typography>\n }\n >\n <div className=\"mt-4 pt-0\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {section.tools.map((tool) => (\n <Card\n key={tool.path}\n onClick={() => {\n navigate(tool.path);\n context.analyticsController?.onAnalyticsEvent?.(\n \"home_navigate_to_view\",\n { path: tool.path }\n );\n }}\n className={cls(\n \"group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out\",\n \"hover:bg-primary/5 dark:hover:bg-primary/5\"\n )}\n >\n <div className=\"flex flex-col h-full\">\n {/* Header: icon + title */}\n <div className=\"flex items-center w-full justify-between mb-1\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary\">\n <IconForView\n collectionOrView={{ slug: tool.path, name: tool.name, icon: tool.icon }}\n size=\"small\"\n />\n </div>\n <Typography variant=\"subtitle1\" component=\"h2\">\n {tool.name}\n </Typography>\n </div>\n </div>\n\n {/* Description indented to align with title */}\n <div className=\"pl-8\">\n {tool.description && (\n <Typography variant=\"caption\" color=\"secondary\" component=\"div\">\n {tool.description}\n </Typography>\n )}\n </div>\n\n {/* Spacer */}\n <div className=\"grow\"/>\n </div>\n </Card>\n ))}\n </div>\n </div>\n </ExpandablePanel>\n </div>\n );\n })}\n\n {/* ── SDK Quick Start ── */}\n <div className=\"mt-10 mb-6\">\n <div className=\"flex items-center mb-1\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n Quick Start\n </Typography>\n </div>\n\n <Typography variant=\"body2\" color=\"secondary\" className=\"mb-4 max-w-2xl\">\n Generate a fully-typed SDK from your collections with{\" \"}\n <code className=\"text-emerald-400 font-mono text-xs bg-emerald-400/10 px-1.5 py-0.5 rounded\">\n npx rebase generate-sdk\n </code>\n {\" \"}and start querying your data with full TypeScript autocompletion.\n </Typography>\n\n <div className=\"rounded-lg border border-surface-200/40 dark:border-surface-700/40 bg-white dark:bg-surface-950 overflow-hidden\">\n {/* Title bar */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-surface-200/40 dark:border-surface-700/40 bg-surface-50 dark:bg-surface-900/80\">\n <div className=\"flex items-center gap-2.5\">\n <div className=\"flex gap-1.5\">\n <span className=\"w-2.5 h-2.5 rounded-full bg-red-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-amber-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-emerald-400/60\"/>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500 ml-1\">\n app.ts\n </span>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500\">\n TypeScript\n </span>\n </div>\n\n {/* Syntax-highlighted code */}\n <div className=\"px-5 py-4 overflow-x-auto text-[13px] leading-6 font-mono\">\n <SyntaxHighlightedSnippet/>\n </div>\n </div>\n </div>\n\n {/* ── Extra sections from props ── */}\n {sections?.map((s) => (\n <div key={s.key} className=\"my-10\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {s.title}\n </Typography>\n <div className=\"mt-4\">{s.children}</div>\n </div>\n ))}\n\n {additionalChildrenEnd}\n </Container>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Hand-crafted syntax-highlighted code snippet.\n Uses inline spans with Tailwind color classes to avoid\n pulling in a syntax highlighting library.\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SyntaxHighlightedSnippet() {\n const kw = \"text-violet-600 dark:text-violet-400\"; // keywords\n const str = \"text-emerald-600 dark:text-emerald-400\"; // strings\n const typ = \"text-amber-600 dark:text-amber-300\"; // types\n const fn = \"text-blue-600 dark:text-blue-400\"; // functions\n const cm = \"text-surface-500 dark:text-surface-400 italic\"; // comments\n const op = \"text-surface-500 dark:text-surface-400\"; // operators / punctuation\n const tx = \"text-surface-950 dark:text-surface-200\"; // plain text\n\n return (\n <pre className=\"m-0 whitespace-pre\">\n <span className={kw}>import</span>\n <span className={tx}>{\" { \"}</span>\n <span className={fn}>createRebaseClient</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>'@rebasepro/client'</span>\n <span className={op}>;</span>\n {\"\\n\"}\n\n <span className={kw}>import</span>\n <span className={tx}> </span>\n <span className={kw}>type</span>\n <span className={tx}>{\" { \"}</span>\n <span className={typ}>Database</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>'./database.types'</span>\n <span className={op}>;</span>\n {\"\\n\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}> rebase </span>\n <span className={op}>= </span>\n <span className={fn}>createRebaseClient</span>\n <span className={op}>{\"<\"}</span>\n <span className={typ}>Database</span>\n <span className={op}>{\">(\"}</span>\n <span className={tx}>{\"{\"}</span>\n {\"\\n\"}\n <span className={tx}>{\" baseUrl\"}</span>\n <span className={op}>: </span>\n <span className={str}>'http://localhost:3001'</span>\n <span className={op}>,</span>\n {\"\\n\"}\n <span className={tx}>{\"}\"}</span>\n <span className={op}>);</span>\n {\"\\n\\n\"}\n\n <span className={cm}>{\"// Fully typed — autocompletion for tables and columns\"}</span>\n {\"\\n\"}\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>users</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={tx}>users</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n {\"\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>posts</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={fn}>collection</span>\n <span className={op}>(</span>\n <span className={str}>'posts'</span>\n <span className={op}>)</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n </pre>\n );\n}\n","import React, { Suspense, useLayoutEffect, useMemo } from \"react\";\nimport { useRebaseRegistryDispatch } from \"@rebasepro/app\";\nimport type { RebaseStudioConfig, AppView } from \"@rebasepro/cms-types\";\nimport { CircularProgressCenter, lazyChunk } from \"@rebasepro/ui\";\n\n// Lazy-loaded studio tools — each fetched only when its route is visited.\n// This keeps Monaco, @xyflow/react, dagre, pgsql-ast-parser etc. out of the initial bundle.\nconst SQLEditor = lazyChunk(() => import(\"./SQLEditor/SQLEditor\").then(m => ({ default: m.SQLEditor })));\nconst JSEditor = lazyChunk(() => import(\"./JSEditor/JSEditor\").then(m => ({ default: m.JSEditor })));\nconst RLSEditor = lazyChunk(() => import(\"./RLSEditor/RLSEditor\").then(m => ({ default: m.RLSEditor })));\nconst StorageView = lazyChunk(() => import(\"./StorageView/StorageView\").then(m => ({ default: m.StorageView })));\nconst CronJobsView = lazyChunk(() => import(\"./CronJobs/CronJobsView\").then(m => ({ default: m.CronJobsView })));\nconst SchemaVisualizer = lazyChunk(() => import(\"./SchemaVisualizer/SchemaVisualizer\").then(m => ({ default: m.SchemaVisualizer })));\nconst BranchesView = lazyChunk(() => import(\"./Branches/BranchesView\").then(m => ({ default: m.BranchesView })));\nconst BackupsView = lazyChunk(() => import(\"./Backups/BackupsView\").then(m => ({ default: m.BackupsView })));\nconst ApiExplorer = lazyChunk(() => import(\"./ApiExplorer/ApiExplorer\").then(m => ({ default: m.ApiExplorer })));\nconst LogsExplorer = lazyChunk(() => import(\"./LogsExplorer/LogsExplorer\").then(m => ({ default: m.LogsExplorer })));\nconst ApiKeysView = lazyChunk(() => import(\"./ApiKeys/ApiKeysView\").then(m => ({ default: m.ApiKeysView })));\n\nimport { StudioHomePage } from \"./StudioHomePage\";\n\n/**\n * Declarative component to configure the Studio in Rebase.\n * Renders nothing — purely registers config into the RebaseRegistry.\n *\n * The \"schema\" tool (collection editor view) is a Studio tool, but it is not\n * registered here: it ships from the panel package because it needs the project's\n * collection source to write back to. When `<RebaseCMS collectionEditor={...}>`\n * is used, the schema view is automatically injected into Studio — no manual\n * wiring needed. Where it is mounted from is an implementation detail; it is a\n * schema-editing tool and belongs beside SQL and RLS.\n */\nconst DEFAULT_HOME_PAGE = <StudioHomePage/>;\n\nexport function RebaseStudio({ tools, homePage }: RebaseStudioConfig) {\n const dispatch = useRebaseRegistryDispatch();\n\n const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;\n\n /**\n * The tool list reduced to a value, so that re-rendering the host does not\n * republish the registry.\n *\n * `tools` is an array, and callers write it inline — `tools={[...TOOLS]}`\n * in the hosted console. A fresh array every render meant a fresh\n * `devViews` every render, which meant the effect below unregistered and\n * re-registered on every render. Because each registration builds *new*\n * `<Suspense><SQLEditor/></Suspense>` elements, whatever view was on screen\n * was torn down and remounted each time: a half-typed SQL query, a storage\n * folder you had navigated into, an open policy editor — all discarded, and\n * every mount reissued the view's initial requests.\n */\n const toolsKey = tools ? tools.join(\",\") : \"\";\n\n const devViews: AppView[] = useMemo(() => {\n const views: AppView[] = [];\n const activeTools = tools ?? [\"sql\", \"js\", \"rls\", \"storage\", \"cron\", \"schema-visualizer\", \"branches\", \"backups\", \"api\", \"logs\", \"api-keys\"];\n const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;\n\n if (activeTools.includes(\"sql\")) {\n views.push({ slug: \"sql\",\nname: \"SQL Console\",\ngroup: \"Database\",\nicon: \"terminal\",\ndescription: \"Execute SQL queries\",\nview: suspense(<SQLEditor/>) });\n }\n if (activeTools.includes(\"js\")) {\n views.push({ slug: \"js\",\nname: \"JS Console\",\ngroup: \"Compute\",\nicon: \"code\",\ndescription: \"Execute JavaScript\",\nview: suspense(<JSEditor/>) });\n }\n if (activeTools.includes(\"rls\")) {\n views.push({ slug: \"rls\",\nname: \"RLS Policies\",\ngroup: \"Database\",\nicon: \"ShieldCheck\",\ndescription: \"Row Level Security\",\nview: suspense(<RLSEditor/>) });\n }\n if (activeTools.includes(\"storage\")) {\n views.push({ slug: \"storage\",\nname: \"Storage\",\ngroup: \"Storage\",\nicon: \"HardDrive\",\ndescription: \"Manage storage files\",\nview: suspense(<StorageView/>) });\n }\n if (activeTools.includes(\"cron\")) {\n views.push({ slug: \"cron\",\nname: \"Cron Jobs\",\ngroup: \"Compute\",\nicon: \"Clock\",\ndescription: \"Manage scheduled tasks\",\nview: suspense(<CronJobsView/>) });\n }\n if (activeTools.includes(\"schema-visualizer\")) {\n views.push({ slug: \"schema-visualizer\",\nname: \"Schema Visualizer\",\ngroup: \"Database\",\nicon: \"Network\",\ndescription: \"Interactive database ERD\",\nview: suspense(<SchemaVisualizer/>) });\n }\n if (activeTools.includes(\"branches\")) {\n views.push({ slug: \"branches\",\nname: \"Branches\",\ngroup: \"Database\",\nicon: \"GitBranch\",\ndescription: \"Create and manage database branches\",\nview: suspense(<BranchesView/>) });\n }\n if (activeTools.includes(\"backups\")) {\n views.push({ slug: \"backups\",\nname: \"Backups\",\ngroup: \"Database\",\nicon: \"Database\",\ndescription: \"Download database backups\",\nview: suspense(<BackupsView/>) });\n }\n if (activeTools.includes(\"api\")) {\n views.push({ slug: \"api\",\nname: \"API Explorer\",\ngroup: \"API\",\nicon: \"BookOpen\",\ndescription: \"Interactive API documentation and testing\",\nview: suspense(<ApiExplorer/>) });\n }\n if (activeTools.includes(\"logs\")) {\n views.push({ slug: \"logs\",\nname: \"Logs Explorer\",\ngroup: \"Database\",\nicon: \"Activity\",\ndescription: \"Real-time system and query logs\",\nview: suspense(<LogsExplorer/>) });\n }\n if (activeTools.includes(\"api-keys\")) {\n views.push({ slug: \"api-keys\",\nname: \"API Keys\",\ngroup: \"Access Control\",\nicon: \"KeyRound\",\ndescription: \"Create and manage scoped API keys\",\nview: suspense(<ApiKeysView/>) });\n }\n // Note: \"schema\" tool is auto-injected by RebaseShell when collectionEditor is enabled.\n // It is NOT registered here anymore.\n return views;\n // Keyed by the tool list's *contents*; see `toolsKey`. `tools` itself is\n // read inside, and is exactly determined by the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [toolsKey]);\n\n // Use a ref for homePage so it never destabilizes the effect.\n // homePage is a React element — its identity doesn't matter for registration.\n const homePageRef = React.useRef(resolvedHomePage);\n homePageRef.current = resolvedHomePage;\n\n // Same reasoning for `tools`: it is registered as config, but a new array\n // with the same contents must not count as a change.\n const toolsRef = React.useRef(tools);\n toolsRef.current = tools;\n\n useLayoutEffect(() => {\n dispatch.registerStudio({ tools: toolsRef.current,\nhomePage: homePageRef.current,\ndevViews });\n return () => dispatch.unregisterStudio();\n }, [dispatch, devViews]);\n\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":[],"sources":["../src/components/StudioHomePage.tsx","../src/components/RebaseStudio.tsx"],"sourcesContent":["import type { AppView, HomePageSection, PluginGenericProps } from \"@rebasepro/cms-types\";\nimport React, { useEffect, useMemo, useState } from \"react\";\nimport { Card, cls, Container, ExpandablePanel, Typography } from \"@rebasepro/ui\";\nimport { IconForView, useNavigationGroupLabel, useRebaseContext, useRebaseRegistry, useRestoreScroll, useSlot } from \"@rebasepro/app\";\nimport { useNavigate } from \"react-router\";\nimport { useStudioBreadcrumbs, SchemaDriftBanner } from \"@rebasepro/app\";\n\n/* ═══════════════════════════════════════════════════════════════\n Studio tool sections, derived from the registered Studio views\n ═══════════════════════════════════════════════════════════════ */\n\ninterface StudioTool {\n path: string;\n name: string;\n description: string;\n icon?: string | React.ReactNode;\n}\n\ninterface StudioSection {\n label: string;\n tools: StudioTool[];\n}\n\n/** The view metadata the cards need — not the rendered `view` itself. */\ntype StudioViewMeta = Pick<AppView, \"slug\" | \"name\" | \"group\" | \"description\" | \"icon\" | \"hideFromNavigation\">;\n\n/** Group order for the home page; unknown groups are appended in view order. */\nconst GROUP_ORDER = [\"Database\", \"Compute\", \"Storage\", \"API\", \"Access Control\"];\n\nconst UNGROUPED_LABEL = \"Tools\";\n\n/**\n * Build the home page sections from the Studio views that are actually\n * registered, so a card exists if and only if its route does. A hand-written\n * list drifts: it used to advertise Users and Roles pages that 404, while\n * omitting the Backups view that does exist.\n */\nfunction buildSections(views: StudioViewMeta[]): StudioSection[] {\n const byGroup = new Map<string, StudioTool[]>();\n\n for (const view of views) {\n if (view.hideFromNavigation) continue;\n const label = view.group ?? UNGROUPED_LABEL;\n const tools = byGroup.get(label) ?? [];\n tools.push({\n path: `/${view.slug}`,\n name: view.name,\n description: view.description ?? \"\",\n icon: view.icon\n });\n byGroup.set(label, tools);\n }\n\n const ordered = [\n ...GROUP_ORDER.filter(g => byGroup.has(g)),\n ...[...byGroup.keys()].filter(g => !GROUP_ORDER.includes(g))\n ];\n\n return ordered.map(label => ({ label,\ntools: byGroup.get(label)! }));\n}\n\n/* ═══════════════════════════════════════════════════════════════ */\n\nconst COLLAPSED_STORAGE_KEY = \"rebase-studio-home-collapsed\";\n\nfunction useStudioCollapsedGroups(groupNames: string[]) {\n const [collapsed, setCollapsed] = useState<Set<string>>(() => {\n try {\n const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);\n return stored ? new Set(JSON.parse(stored)) : new Set<string>();\n } catch {\n return new Set<string>();\n }\n });\n\n const isGroupCollapsed = (name: string) => collapsed.has(name);\n\n const toggleGroupCollapsed = (name: string) => {\n setCollapsed(prev => {\n const next = new Set(prev);\n if (next.has(name)) {\n next.delete(name);\n } else {\n next.add(name);\n }\n try {\n localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));\n } catch { /* noop */ }\n return next;\n });\n };\n\n return { isGroupCollapsed, toggleGroupCollapsed };\n}\n\nexport function StudioHomePage({\n additionalActions,\n additionalChildrenStart,\n additionalChildrenEnd,\n sections,\n hiddenGroups\n}: {\n additionalActions?: React.ReactNode;\n additionalChildrenStart?: React.ReactNode;\n additionalChildrenEnd?: React.ReactNode;\n sections?: HomePageSection[];\n hiddenGroups?: string[];\n}) {\n const context = useRebaseContext();\n const breadcrumbs = useStudioBreadcrumbs();\n const navigate = useNavigate();\n const registry = useRebaseRegistry();\n\n useEffect(() => {\n breadcrumbs.set({ breadcrumbs: [] });\n }, [breadcrumbs.set]);\n\n const { containerRef } = useRestoreScroll();\n\n const sectionProps: PluginGenericProps = { context };\n\n const pluginActions = useSlot(\"home.actions\", sectionProps);\n\n // The collection editor (\"schema\") is not part of `devViews` — RebaseNavigation\n // injects it when the admin enables a collection editor. Mirror that condition so\n // the card tracks the route.\n const schemaEnabled = Boolean(registry.studioConfig && registry.cmsConfig?.collectionEditor);\n\n const filteredSections = useMemo(() => {\n const views: StudioViewMeta[] = [];\n if (schemaEnabled) {\n views.push({\n slug: \"schema\",\n name: \"Collections\",\n group: \"Database\",\n icon: \"LayoutList\",\n description: \"Define and manage your data model and collection schemas\"\n });\n }\n views.push(...(registry.studioConfig?.devViews ?? []));\n return buildSections(views).filter(s => !hiddenGroups?.includes(s.label));\n }, [registry.studioConfig?.devViews, schemaEnabled, hiddenGroups]);\n\n const groupNames = useMemo(\n () => filteredSections.map(s => s.label),\n [filteredSections]\n );\n\n const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(groupNames);\n // The group name is the identifier this page orders and collapses by;\n // only what the reader sees is translated.\n const groupLabel = useNavigationGroupLabel();\n\n return (\n <div ref={containerRef} className=\"py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800\">\n <Container maxWidth=\"6xl\">\n <div className=\"mb-4 flex flex-col gap-2\">\n <SchemaDriftBanner />\n </div>\n\n {(additionalActions || pluginActions) && (\n <div className=\"w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4 justify-end\">\n {additionalActions}\n {pluginActions}\n </div>\n )}\n\n {additionalChildrenStart}\n\n {/* ── Tool sections ── */}\n {filteredSections.map((section) => {\n const sectionCollapsed = isGroupCollapsed(section.label);\n\n return (\n <div key={section.label} className=\"my-10\">\n <ExpandablePanel\n invisible\n expanded={!sectionCollapsed}\n onExpandedChange={(open) => {\n if (open !== !sectionCollapsed) {\n toggleGroupCollapsed(section.label);\n }\n }}\n className=\"mt-6\"\n titleClassName={cls(\n \"min-h-0 p-0 border-none\",\n \"rounded flex items-center justify-between w-full\",\n \"hover:bg-transparent\",\n \"cursor-pointer select-none\",\n sectionCollapsed && \"bg-surface-100 dark:bg-surface-900/50\"\n )}\n innerClassName=\"mt-4 pt-0\"\n title={\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {groupLabel(section.label)}\n </Typography>\n }\n >\n <div className=\"mt-4 pt-0\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {section.tools.map((tool) => (\n <Card\n key={tool.path}\n onClick={() => {\n navigate(tool.path);\n context.analyticsController?.onAnalyticsEvent?.(\n \"home_navigate_to_view\",\n { path: tool.path }\n );\n }}\n className={cls(\n \"group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out\",\n \"hover:bg-primary/5 dark:hover:bg-primary/5\"\n )}\n >\n <div className=\"flex flex-col h-full\">\n {/* Header: icon + title */}\n <div className=\"flex items-center w-full justify-between mb-1\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary\">\n <IconForView\n collectionOrView={{ slug: tool.path, name: tool.name, icon: tool.icon }}\n size=\"small\"\n />\n </div>\n <Typography variant=\"subtitle1\" component=\"h2\">\n {tool.name}\n </Typography>\n </div>\n </div>\n\n {/* Description indented to align with title */}\n <div className=\"pl-8\">\n {tool.description && (\n <Typography variant=\"caption\" color=\"secondary\" component=\"div\">\n {tool.description}\n </Typography>\n )}\n </div>\n\n {/* Spacer */}\n <div className=\"grow\"/>\n </div>\n </Card>\n ))}\n </div>\n </div>\n </ExpandablePanel>\n </div>\n );\n })}\n\n {/* ── SDK Quick Start ── */}\n <div className=\"mt-10 mb-6\">\n <div className=\"flex items-center mb-1\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n Quick Start\n </Typography>\n </div>\n\n <Typography variant=\"body2\" color=\"secondary\" className=\"mb-4 max-w-2xl\">\n Generate a fully-typed SDK from your collections with{\" \"}\n <code className=\"text-emerald-400 font-mono text-xs bg-emerald-400/10 px-1.5 py-0.5 rounded\">\n npx rebase generate-sdk\n </code>\n {\" \"}and start querying your data with full TypeScript autocompletion.\n </Typography>\n\n <div className=\"rounded-lg border border-surface-200/40 dark:border-surface-700/40 bg-white dark:bg-surface-950 overflow-hidden\">\n {/* Title bar */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-surface-200/40 dark:border-surface-700/40 bg-surface-50 dark:bg-surface-900/80\">\n <div className=\"flex items-center gap-2.5\">\n <div className=\"flex gap-1.5\">\n <span className=\"w-2.5 h-2.5 rounded-full bg-red-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-amber-400/60\"/>\n <span className=\"w-2.5 h-2.5 rounded-full bg-emerald-400/60\"/>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500 ml-1\">\n app.ts\n </span>\n </div>\n <span className=\"text-xs font-mono text-surface-400 dark:text-surface-500\">\n TypeScript\n </span>\n </div>\n\n {/* Syntax-highlighted code */}\n <div className=\"px-5 py-4 overflow-x-auto text-[13px] leading-6 font-mono\">\n <SyntaxHighlightedSnippet/>\n </div>\n </div>\n </div>\n\n {/* ── Extra sections from props ── */}\n {sections?.map((s) => (\n <div key={s.key} className=\"my-10\">\n <Typography\n variant=\"caption\"\n component=\"h2\"\n color=\"secondary\"\n className={cls(\n \"px-4 py-1 rounded\",\n \"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70\"\n )}\n >\n {s.title}\n </Typography>\n <div className=\"mt-4\">{s.children}</div>\n </div>\n ))}\n\n {additionalChildrenEnd}\n </Container>\n </div>\n );\n}\n\n/* ═══════════════════════════════════════════════════════════════\n Hand-crafted syntax-highlighted code snippet.\n Uses inline spans with Tailwind color classes to avoid\n pulling in a syntax highlighting library.\n ═══════════════════════════════════════════════════════════════ */\n\nfunction SyntaxHighlightedSnippet() {\n const kw = \"text-violet-600 dark:text-violet-400\"; // keywords\n const str = \"text-emerald-600 dark:text-emerald-400\"; // strings\n const typ = \"text-amber-600 dark:text-amber-300\"; // types\n const fn = \"text-blue-600 dark:text-blue-400\"; // functions\n const cm = \"text-surface-500 dark:text-surface-400 italic\"; // comments\n const op = \"text-surface-500 dark:text-surface-400\"; // operators / punctuation\n const tx = \"text-surface-950 dark:text-surface-200\"; // plain text\n\n return (\n <pre className=\"m-0 whitespace-pre\">\n <span className={kw}>import</span>\n <span className={tx}>{\" { \"}</span>\n <span className={fn}>createRebaseClient</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>'@rebasepro/client'</span>\n <span className={op}>;</span>\n {\"\\n\"}\n\n <span className={kw}>import</span>\n <span className={tx}> </span>\n <span className={kw}>type</span>\n <span className={tx}>{\" { \"}</span>\n <span className={typ}>Database</span>\n <span className={tx}>{\" } \"}</span>\n <span className={kw}>from</span>\n <span className={tx}> </span>\n <span className={str}>'./database.types'</span>\n <span className={op}>;</span>\n {\"\\n\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}> rebase </span>\n <span className={op}>= </span>\n <span className={fn}>createRebaseClient</span>\n <span className={op}>{\"<\"}</span>\n <span className={typ}>Database</span>\n <span className={op}>{\">(\"}</span>\n <span className={tx}>{\"{\"}</span>\n {\"\\n\"}\n <span className={tx}>{\" baseUrl\"}</span>\n <span className={op}>: </span>\n <span className={str}>'http://localhost:3001'</span>\n <span className={op}>,</span>\n {\"\\n\"}\n <span className={tx}>{\"}\"}</span>\n <span className={op}>);</span>\n {\"\\n\\n\"}\n\n <span className={cm}>{\"// Fully typed — autocompletion for tables and columns\"}</span>\n {\"\\n\"}\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>users</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={tx}>users</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n {\"\\n\"}\n\n <span className={kw}>const</span>\n <span className={tx}>{\" { \"}</span>\n <span className={tx}>data</span>\n <span className={op}>: </span>\n <span className={tx}>posts</span>\n <span className={tx}>{\" } \"}</span>\n <span className={op}>= </span>\n <span className={kw}>await</span>\n <span className={tx}> rebase</span>\n <span className={op}>.</span>\n <span className={tx}>data</span>\n <span className={op}>.</span>\n <span className={fn}>collection</span>\n <span className={op}>(</span>\n <span className={str}>'posts'</span>\n <span className={op}>)</span>\n <span className={op}>.</span>\n <span className={fn}>find</span>\n <span className={op}>();</span>\n </pre>\n );\n}\n","import React, { Suspense, useLayoutEffect, useMemo } from \"react\";\nimport { useRebaseRegistryDispatch, useTranslation } from \"@rebasepro/app\";\nimport type { RebaseStudioConfig, AppView } from \"@rebasepro/cms-types\";\nimport { CircularProgressCenter, lazyChunk } from \"@rebasepro/ui\";\n\n// Lazy-loaded studio tools — each fetched only when its route is visited.\n// This keeps Monaco, @xyflow/react, dagre, pgsql-ast-parser etc. out of the initial bundle.\nconst SQLEditor = lazyChunk(() => import(\"./SQLEditor/SQLEditor\").then(m => ({ default: m.SQLEditor })));\nconst JSEditor = lazyChunk(() => import(\"./JSEditor/JSEditor\").then(m => ({ default: m.JSEditor })));\nconst RLSEditor = lazyChunk(() => import(\"./RLSEditor/RLSEditor\").then(m => ({ default: m.RLSEditor })));\nconst StorageView = lazyChunk(() => import(\"./StorageView/StorageView\").then(m => ({ default: m.StorageView })));\nconst CronJobsView = lazyChunk(() => import(\"./CronJobs/CronJobsView\").then(m => ({ default: m.CronJobsView })));\nconst SchemaVisualizer = lazyChunk(() => import(\"./SchemaVisualizer/SchemaVisualizer\").then(m => ({ default: m.SchemaVisualizer })));\nconst BranchesView = lazyChunk(() => import(\"./Branches/BranchesView\").then(m => ({ default: m.BranchesView })));\nconst BackupsView = lazyChunk(() => import(\"./Backups/BackupsView\").then(m => ({ default: m.BackupsView })));\nconst ApiExplorer = lazyChunk(() => import(\"./ApiExplorer/ApiExplorer\").then(m => ({ default: m.ApiExplorer })));\nconst LogsExplorer = lazyChunk(() => import(\"./LogsExplorer/LogsExplorer\").then(m => ({ default: m.LogsExplorer })));\nconst ApiKeysView = lazyChunk(() => import(\"./ApiKeys/ApiKeysView\").then(m => ({ default: m.ApiKeysView })));\n\nimport { StudioHomePage } from \"./StudioHomePage\";\n\n/**\n * Declarative component to configure the Studio in Rebase.\n * Renders nothing — purely registers config into the RebaseRegistry.\n *\n * The \"schema\" tool (collection editor view) is a Studio tool, but it is not\n * registered here: it ships from the panel package because it needs the project's\n * collection source to write back to. When `<RebaseCMS collectionEditor={...}>`\n * is used, the schema view is automatically injected into Studio — no manual\n * wiring needed. Where it is mounted from is an implementation detail; it is a\n * schema-editing tool and belongs beside SQL and RLS.\n */\nconst DEFAULT_HOME_PAGE = <StudioHomePage/>;\n\nexport function RebaseStudio({ tools, homePage, devViews: extraViews }: RebaseStudioConfig) {\n const dispatch = useRebaseRegistryDispatch();\n const { t, i18n } = useTranslation();\n\n const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;\n\n /**\n * The tool list reduced to a value, so that re-rendering the host does not\n * republish the registry.\n *\n * `tools` is an array, and callers write it inline — `tools={[...TOOLS]}`\n * in the hosted console. A fresh array every render meant a fresh\n * `devViews` every render, which meant the effect below unregistered and\n * re-registered on every render. Because each registration builds *new*\n * `<Suspense><SQLEditor/></Suspense>` elements, whatever view was on screen\n * was torn down and remounted each time: a half-typed SQL query, a storage\n * folder you had navigated into, an open policy editor — all discarded, and\n * every mount reissued the view's initial requests.\n */\n const toolsKey = tools ? tools.join(\",\") : \"\";\n\n const devViews: AppView[] = useMemo(() => {\n const views: AppView[] = [];\n const activeTools = tools ?? [\"sql\", \"js\", \"rls\", \"storage\", \"cron\", \"schema-visualizer\", \"branches\", \"backups\", \"api\", \"logs\", \"api-keys\"];\n const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;\n\n if (activeTools.includes(\"sql\")) {\n views.push({ slug: \"sql\",\nname: t(\"studio_tool_sql\"),\ngroup: \"Database\",\nicon: \"terminal\",\ndescription: t(\"studio_tool_sql_description\"),\nview: suspense(<SQLEditor/>) });\n }\n if (activeTools.includes(\"js\")) {\n views.push({ slug: \"js\",\nname: t(\"studio_tool_js\"),\ngroup: \"Compute\",\nicon: \"code\",\ndescription: t(\"studio_tool_js_description\"),\nview: suspense(<JSEditor/>) });\n }\n if (activeTools.includes(\"rls\")) {\n views.push({ slug: \"rls\",\nname: t(\"studio_tool_rls\"),\ngroup: \"Database\",\nicon: \"ShieldCheck\",\ndescription: t(\"studio_tool_rls_description\"),\nview: suspense(<RLSEditor/>) });\n }\n if (activeTools.includes(\"storage\")) {\n views.push({ slug: \"storage\",\nname: t(\"studio_tool_storage\"),\ngroup: \"Storage\",\nicon: \"HardDrive\",\ndescription: t(\"studio_tool_storage_description\"),\nview: suspense(<StorageView/>) });\n }\n if (activeTools.includes(\"cron\")) {\n views.push({ slug: \"cron\",\nname: t(\"studio_tool_cron\"),\ngroup: \"Compute\",\nicon: \"Clock\",\ndescription: t(\"studio_tool_cron_description\"),\nview: suspense(<CronJobsView/>) });\n }\n if (activeTools.includes(\"schema-visualizer\")) {\n views.push({ slug: \"schema-visualizer\",\nname: t(\"studio_tool_schema_visualizer\"),\ngroup: \"Database\",\nicon: \"Network\",\ndescription: t(\"studio_tool_schema_visualizer_description\"),\nview: suspense(<SchemaVisualizer/>) });\n }\n if (activeTools.includes(\"branches\")) {\n views.push({ slug: \"branches\",\nname: t(\"studio_tool_branches\"),\ngroup: \"Database\",\nicon: \"GitBranch\",\ndescription: t(\"studio_tool_branches_description\"),\nview: suspense(<BranchesView/>) });\n }\n if (activeTools.includes(\"backups\")) {\n views.push({ slug: \"backups\",\nname: t(\"studio_tool_backups\"),\ngroup: \"Database\",\nicon: \"Database\",\ndescription: t(\"studio_tool_backups_description\"),\nview: suspense(<BackupsView/>) });\n }\n if (activeTools.includes(\"api\")) {\n views.push({ slug: \"api\",\nname: t(\"studio_tool_api\"),\ngroup: \"API\",\nicon: \"BookOpen\",\ndescription: t(\"studio_tool_api_description\"),\nview: suspense(<ApiExplorer/>) });\n }\n if (activeTools.includes(\"logs\")) {\n views.push({ slug: \"logs\",\nname: t(\"studio_tool_logs\"),\ngroup: \"Database\",\nicon: \"Activity\",\ndescription: t(\"studio_tool_logs_description\"),\nview: suspense(<LogsExplorer/>) });\n }\n if (activeTools.includes(\"api-keys\")) {\n views.push({ slug: \"api-keys\",\nname: t(\"studio_tool_api_keys\"),\ngroup: \"Access Control\",\nicon: \"KeyRound\",\ndescription: t(\"studio_tool_api_keys_description\"),\nview: suspense(<ApiKeysView/>) });\n }\n // Note: \"schema\" tool is auto-injected by RebaseShell when collectionEditor is enabled.\n // It is NOT registered here anymore.\n return views;\n // Keyed by the tool list's *contents*; see `toolsKey`. `tools` itself is\n // read inside, and is exactly determined by the key.\n // The strings are now translated, so the active language is part of\n // what this list is.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [toolsKey, t, i18n.language]);\n\n /**\n * Your own tools, keyed the same way `tools` is and for the same reason:\n * callers write `devViews={[…]}` inline, so the array is new on every\n * render. Re-registering would tear down and remount whichever Studio view\n * was on screen — a half-typed SQL query, an open policy editor.\n *\n * The key is the identity of each view, not its element: a view's `view` is\n * resolved by the router at render time, so a fresh element for the same\n * slug is not a configuration change.\n */\n const extraViewsKey = (extraViews ?? [])\n .map((v) => `${v.slug}|${v.name}|${v.group ?? \"\"}|${v.hideFromNavigation ?? false}`)\n .join(\",\");\n const extraViewsRef = React.useRef(extraViews);\n extraViewsRef.current = extraViews;\n\n const allDevViews: AppView[] = useMemo(\n () => [...devViews, ...(extraViewsRef.current ?? [])],\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [devViews, extraViewsKey]\n );\n\n // Use a ref for homePage so it never destabilizes the effect.\n // homePage is a React element — its identity doesn't matter for registration.\n const homePageRef = React.useRef(resolvedHomePage);\n homePageRef.current = resolvedHomePage;\n\n // Same reasoning for `tools`: it is registered as config, but a new array\n // with the same contents must not count as a change.\n const toolsRef = React.useRef(tools);\n toolsRef.current = tools;\n\n useLayoutEffect(() => {\n dispatch.registerStudio({ tools: toolsRef.current,\nhomePage: homePageRef.current,\ndevViews: allDevViews });\n return () => dispatch.unregisterStudio();\n }, [dispatch, allDevViews]);\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/components/load-failure.ts
|
|
2
|
+
/** HTTP status carried by whatever the SDK threw, if it carries one. */
|
|
3
|
+
function statusOf(err) {
|
|
4
|
+
if (typeof err !== "object" || err === null) return null;
|
|
5
|
+
for (const key of [
|
|
6
|
+
"status",
|
|
7
|
+
"statusCode",
|
|
8
|
+
"code"
|
|
9
|
+
]) {
|
|
10
|
+
const v = err[key];
|
|
11
|
+
if (typeof v === "number" && v >= 100 && v < 600) return v;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function classifyLoadFailure(err) {
|
|
16
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const status = statusOf(err);
|
|
18
|
+
return status === 403 || status === 401 || /\bnot authori[sz]ed\b|\bforbidden\b|\bpermission denied\b/i.test(detail) ? {
|
|
19
|
+
kind: "denied",
|
|
20
|
+
detail,
|
|
21
|
+
retryable: false
|
|
22
|
+
} : {
|
|
23
|
+
kind: "unavailable",
|
|
24
|
+
detail,
|
|
25
|
+
retryable: true
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { classifyLoadFailure as t };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=load-failure-C6OMJSGC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-failure-C6OMJSGC.js","names":[],"sources":["../src/components/load-failure.ts"],"sourcesContent":["/**\n * Why a Studio listing failed — and whether it is the platform's fault.\n *\n * ## Why this is a function\n *\n * The storage pane rendered one view for every failure: a red card titled\n * **\"Error loading storage\"** with whatever string came back underneath.\n *\n * On 2026-08-27 a customer opened the Files tab of a healthy project and read\n * \"Error loading storage — Not authorized for this object\". Nothing was broken.\n * That project's own `storageAuthorize` hook refuses an unscoped listing on\n * purpose — its comment says a `list` that is not scoped to a team \"is precisely\n * the enumeration this exists to stop\" — and the Files tab always opens at the\n * bucket root. The console took a policy working exactly as written and\n * presented it as a fault in the platform, which sends the reader hunting for a\n * problem that does not exist.\n *\n * A refusal and a failure need different words, a different colour, and a\n * different next step: one is \"your rule said no\", the other is \"we could not\n * ask\". Retry is an affordance for the second and noise on the first.\n *\n * ## Why it is shared\n *\n * Storage was the only pane that had learned this. Backups, Cron Jobs, API Keys\n * and Branches each caught the error, opened a snackbar that is gone in four\n * seconds, and left their list empty — so a reader who looked away, or arrived\n * after the toast, was told \"No backups found yet\" about a database that has\n * backups and an account that may not list them. One classifier and one view, so\n * the next pane inherits the distinction instead of rediscovering it.\n *\n * The view that renders one of these is `load-failure-view.tsx`. It is a\n * separate file so that this classifier — the part with the rules in it — can\n * be tested without dragging in `@rebasepro/app` and, behind it, react-router.\n */\n\nexport type LoadFailureKind =\n /** The project's own rules, or the caller's own role, said no. Not an error. */\n | \"denied\"\n /** We could not reach or read the source. */\n | \"unavailable\";\n\nexport interface LoadFailure {\n kind: LoadFailureKind;\n /** The message as the server gave it, never re-worded. */\n detail: string;\n /** Offering retry against a standing policy teaches people the button does nothing. */\n retryable: boolean;\n}\n\n/** HTTP status carried by whatever the SDK threw, if it carries one. */\nfunction statusOf(err: unknown): number | null {\n if (typeof err !== \"object\" || err === null) return null;\n for (const key of [\"status\", \"statusCode\", \"code\"]) {\n const v = (err as Record<string, unknown>)[key];\n if (typeof v === \"number\" && v >= 100 && v < 600) return v;\n }\n return null;\n}\n\nexport function classifyLoadFailure(err: unknown): LoadFailure {\n const detail = err instanceof Error ? err.message : String(err);\n const status = statusOf(err);\n\n // 403 is the rule's answer; 401 means the caller is not signed in as anyone\n // a rule can evaluate, which is the same conversation from the other end.\n // The message match is the fallback, because an SDK that flattens an error\n // to a string is exactly how this reached the screen unclassified.\n const denied =\n status === 403 ||\n status === 401 ||\n /\\bnot authori[sz]ed\\b|\\bforbidden\\b|\\bpermission denied\\b/i.test(detail);\n\n return denied\n ? { kind: \"denied\", detail, retryable: false }\n : { kind: \"unavailable\", detail, retryable: true };\n}\n"],"mappings":";;AAkDA,SAAS,SAAS,KAA6B;CAC3C,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM,OAAO;CACpD,KAAK,MAAM,OAAO;EAAC;EAAU;EAAc;CAAM,GAAG;EAChD,MAAM,IAAK,IAAgC;EAC3C,IAAI,OAAO,MAAM,YAAY,KAAK,OAAO,IAAI,KAAK,OAAO;CAC7D;CACA,OAAO;AACX;AAEA,SAAgB,oBAAoB,KAA2B;CAC3D,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC9D,MAAM,SAAS,SAAS,GAAG;CAW3B,OAJI,WAAW,OACX,WAAW,OACX,6DAA6D,KAAK,MAAM,IAGtE;EAAE,MAAM;EAAU;EAAQ,WAAW;CAAM,IAC3C;EAAE,MAAM;EAAe;EAAQ,WAAW;CAAK;AACzD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ErrorView } from "@rebasepro/app";
|
|
2
|
+
import "react";
|
|
3
|
+
import { Typography } from "@rebasepro/ui";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/load-failure-view.tsx
|
|
6
|
+
/**
|
|
7
|
+
* The two failures, told apart. Sized to drop into the place a pane would
|
|
8
|
+
* otherwise render its empty state.
|
|
9
|
+
*/
|
|
10
|
+
function LoadFailureView({ failure, title, deniedTitle, deniedHint, onRetry }) {
|
|
11
|
+
if (failure.kind === "denied") return /* @__PURE__ */ jsx("div", {
|
|
12
|
+
className: "flex items-center justify-center h-full p-6 overflow-auto",
|
|
13
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
14
|
+
className: "max-w-md text-center",
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
17
|
+
variant: "subtitle2",
|
|
18
|
+
className: "block",
|
|
19
|
+
children: deniedTitle
|
|
20
|
+
}),
|
|
21
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
22
|
+
variant: "body2",
|
|
23
|
+
color: "secondary",
|
|
24
|
+
className: "block mt-2",
|
|
25
|
+
children: deniedHint
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
28
|
+
variant: "caption",
|
|
29
|
+
color: "disabled",
|
|
30
|
+
className: "block mt-3 font-mono break-all",
|
|
31
|
+
children: failure.detail
|
|
32
|
+
})
|
|
33
|
+
]
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
return /* @__PURE__ */ jsx("div", {
|
|
37
|
+
className: "flex items-center justify-center h-full p-6 overflow-auto",
|
|
38
|
+
children: /* @__PURE__ */ jsx(ErrorView, {
|
|
39
|
+
title,
|
|
40
|
+
error: failure.detail,
|
|
41
|
+
onRetry: failure.retryable ? onRetry : void 0
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { LoadFailureView as t };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=load-failure-view-CBTB_h-h.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-failure-view-CBTB_h-h.js","names":[],"sources":["../src/components/load-failure-view.tsx"],"sourcesContent":["import React from \"react\";\nimport { ErrorView } from \"@rebasepro/app\";\nimport { Typography } from \"@rebasepro/ui\";\n\nimport type { LoadFailure } from \"./load-failure\";\n\nexport interface LoadFailureViewProps {\n failure: LoadFailure;\n /** What could not be read, for the failure case: \"Could not read backups\". */\n title: string;\n /** What was refused, for the denied case: \"You cannot list backups\". */\n deniedTitle: string;\n /** One sentence on what decides it, and what to do. */\n deniedHint: React.ReactNode;\n onRetry?: () => void;\n}\n\n/**\n * The two failures, told apart. Sized to drop into the place a pane would\n * otherwise render its empty state.\n */\nexport function LoadFailureView({ failure, title, deniedTitle, deniedHint, onRetry }: LoadFailureViewProps) {\n if (failure.kind === \"denied\") {\n return (\n <div className=\"flex items-center justify-center h-full p-6 overflow-auto\">\n <div className=\"max-w-md text-center\">\n <Typography variant=\"subtitle2\" className=\"block\">\n {deniedTitle}\n </Typography>\n <Typography variant=\"body2\" color=\"secondary\" className=\"block mt-2\">\n {deniedHint}\n </Typography>\n <Typography variant=\"caption\" color=\"disabled\" className=\"block mt-3 font-mono break-all\">\n {failure.detail}\n </Typography>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center justify-center h-full p-6 overflow-auto\">\n <ErrorView\n title={title}\n error={failure.detail}\n onRetry={failure.retryable ? onRetry : undefined}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAqBA,SAAgB,gBAAgB,EAAE,SAAS,OAAO,aAAa,YAAY,WAAiC;CACxG,IAAI,QAAQ,SAAS,UACjB,OACI,oBAAC,OAAD;EAAK,WAAU;YACX,qBAAC,OAAD;GAAK,WAAU;aAAf;IACI,oBAAC,YAAD;KAAY,SAAQ;KAAY,WAAU;eACrC;IACO,CAAA;IACZ,oBAAC,YAAD;KAAY,SAAQ;KAAQ,OAAM;KAAY,WAAU;eACnD;IACO,CAAA;IACZ,oBAAC,YAAD;KAAY,SAAQ;KAAU,OAAM;KAAW,WAAU;eACpD,QAAQ;IACD,CAAA;GACX;;CACJ,CAAA;CAIb,OACI,oBAAC,OAAD;EAAK,WAAU;YACX,oBAAC,WAAD;GACW;GACP,OAAO,QAAQ;GACf,SAAS,QAAQ,YAAY,UAAU,KAAA;EAC1C,CAAA;CACA,CAAA;AAEb"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/studio",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"version": "0.18.0",
|
|
4
|
+
"description": "Rebase Studio — the SQL editor, schema visualiser, RLS policy editor and API explorer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"rebase",
|
|
7
|
+
"studio",
|
|
8
|
+
"sql",
|
|
9
|
+
"rls",
|
|
10
|
+
"postgres"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://rebase.pro",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/rebasepro/rebase/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
19
|
+
"directory": "packages/studio"
|
|
20
|
+
},
|
|
5
21
|
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.22.0"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
6
26
|
"main": "./dist/index.es.js",
|
|
7
27
|
"module": "./dist/index.es.js",
|
|
8
28
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"source": "src/index.ts",
|
|
10
29
|
"dependencies": {
|
|
11
30
|
"@monaco-editor/react": "^4.7.0",
|
|
12
31
|
"@types/dagre": "0.7.54",
|
|
@@ -16,19 +35,19 @@
|
|
|
16
35
|
"pgsql-ast-parser": "12.0.2",
|
|
17
36
|
"prism-react-renderer": "^2.4.1",
|
|
18
37
|
"react-dropzone": "^19.1.1",
|
|
19
|
-
"@rebasepro/cms-types": "0.
|
|
20
|
-
"@rebasepro/
|
|
21
|
-
"@rebasepro/
|
|
22
|
-
"@rebasepro/
|
|
23
|
-
"@rebasepro/
|
|
24
|
-
"@rebasepro/
|
|
25
|
-
"@rebasepro/utils": "0.
|
|
38
|
+
"@rebasepro/cms-types": "0.18.0",
|
|
39
|
+
"@rebasepro/client": "0.18.0",
|
|
40
|
+
"@rebasepro/common": "0.18.0",
|
|
41
|
+
"@rebasepro/app": "0.18.0",
|
|
42
|
+
"@rebasepro/ui": "0.18.0",
|
|
43
|
+
"@rebasepro/types": "0.18.0",
|
|
44
|
+
"@rebasepro/utils": "0.18.0"
|
|
26
45
|
},
|
|
27
46
|
"peerDependencies": {
|
|
28
|
-
"react": "
|
|
29
|
-
"react-dom": "
|
|
47
|
+
"react": "^19.2.7",
|
|
48
|
+
"react-dom": "^19.2.7",
|
|
30
49
|
"react-router": "^8.3.0",
|
|
31
|
-
"@rebasepro/cms": "0.
|
|
50
|
+
"@rebasepro/cms": "0.18.0"
|
|
32
51
|
},
|
|
33
52
|
"peerDependenciesMeta": {
|
|
34
53
|
"@rebasepro/cms": {
|
|
@@ -39,7 +58,8 @@
|
|
|
39
58
|
".": {
|
|
40
59
|
"types": "./dist/index.d.ts",
|
|
41
60
|
"development": "./dist/index.es.js",
|
|
42
|
-
"import": "./dist/index.es.js"
|
|
61
|
+
"import": "./dist/index.es.js",
|
|
62
|
+
"default": "./dist/index.es.js"
|
|
43
63
|
},
|
|
44
64
|
"./package.json": "./package.json"
|
|
45
65
|
},
|
|
@@ -72,21 +92,16 @@
|
|
|
72
92
|
"vite": "^8.1.5"
|
|
73
93
|
},
|
|
74
94
|
"files": [
|
|
75
|
-
"dist"
|
|
76
|
-
"src"
|
|
95
|
+
"dist"
|
|
77
96
|
],
|
|
78
97
|
"publishConfig": {
|
|
79
98
|
"access": "public"
|
|
80
99
|
},
|
|
81
100
|
"gitHead": "9ecf37abf793bd2f2daaaed6f517ee5ee19b01ae",
|
|
82
|
-
"repository": {
|
|
83
|
-
"type": "git",
|
|
84
|
-
"url": "https://github.com/rebasepro/rebase.git",
|
|
85
|
-
"directory": "packages/studio"
|
|
86
|
-
},
|
|
87
101
|
"scripts": {
|
|
88
102
|
"dev": "vite",
|
|
89
103
|
"test": "jest --passWithNoTests",
|
|
104
|
+
"test:watch": "jest --watch",
|
|
90
105
|
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
|
|
91
106
|
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
92
107
|
}
|