@rebasepro/studio 0.9.0 → 0.9.1-canary.09aaf62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/{ApiExplorer-CdIwR9Ga.js → ApiExplorer-B9M9-uy_.js} +3 -3
- package/dist/ApiExplorer-B9M9-uy_.js.map +1 -0
- package/dist/{ApiKeysView-D-_FSlNL.js → ApiKeysView-vQKqt2wl.js} +45 -3
- package/dist/ApiKeysView-vQKqt2wl.js.map +1 -0
- package/dist/{AuthSimulationSelector-iEZ-Or_1.js → AuthSimulationSelector-Bougd521.js} +2 -2
- package/dist/AuthSimulationSelector-Bougd521.js.map +1 -0
- package/dist/BackupsView-DNS6LdVg.js +227 -0
- package/dist/BackupsView-DNS6LdVg.js.map +1 -0
- package/dist/{BranchesView-Dlg78EQ8.js → BranchesView-DH0qnU9J.js} +2 -2
- package/dist/BranchesView-DH0qnU9J.js.map +1 -0
- package/dist/{CronJobsView-4gdtJvoe.js → CronJobsView-B59i3Mis.js} +2 -2
- package/dist/CronJobsView-B59i3Mis.js.map +1 -0
- package/dist/{JSEditor-Sgmw1SHC.js → JSEditor-BeHHuxhA.js} +5 -5
- package/dist/JSEditor-BeHHuxhA.js.map +1 -0
- package/dist/LogsExplorer-BCXoGREr.js +298 -0
- package/dist/LogsExplorer-BCXoGREr.js.map +1 -0
- package/dist/{MonacoEditor-COZqrIJ1.js → MonacoEditor-D0_QljcX.js} +2 -2
- package/dist/MonacoEditor-D0_QljcX.js.map +1 -0
- package/dist/{RLSEditor-DPhky8XW.js → RLSEditor-D6adwB6e.js} +34 -20
- package/dist/RLSEditor-D6adwB6e.js.map +1 -0
- package/dist/{SQLEditor-CgR2hlUI.js → SQLEditor-_dCqVAN_.js} +5 -5
- package/dist/SQLEditor-_dCqVAN_.js.map +1 -0
- package/dist/{SchemaVisualizer-WK4ZZR5X.js → SchemaVisualizer-C_ErehfH.js} +3 -3
- package/dist/SchemaVisualizer-C_ErehfH.js.map +1 -0
- package/dist/{StorageView-BMhD29YO.js → StorageView-UvyM2xdW.js} +2 -2
- package/dist/StorageView-UvyM2xdW.js.map +1 -0
- package/dist/components/AuthSimulationSelector.d.ts +1 -1
- package/dist/components/Backups/BackupsView.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.es.js +73 -110
- package/dist/index.es.js.map +1 -1
- package/package.json +11 -12
- package/src/components/ApiExplorer/ApiExplorer.tsx +1 -1
- package/src/components/ApiExplorer/TryItPanel.tsx +1 -1
- package/src/components/ApiKeys/ApiKeysView.tsx +43 -3
- package/src/components/AuthSimulationSelector.tsx +1 -1
- package/src/components/Backups/BackupsView.tsx +185 -0
- package/src/components/Branches/BranchesView.tsx +1 -1
- package/src/components/CronJobs/CronJobsView.tsx +1 -1
- package/src/components/JSEditor/JSEditor.tsx +6 -6
- package/src/components/JSEditor/JSMonacoEditor.tsx +1 -1
- package/src/components/LogsExplorer/LogsExplorer.tsx +168 -46
- package/src/components/RLSEditor/PolicyEditor.tsx +5 -3
- package/src/components/RLSEditor/RLSEditor.tsx +50 -20
- package/src/components/RebaseStudio.tsx +12 -3
- package/src/components/SQLEditor/MonacoEditor.tsx +1 -1
- package/src/components/SQLEditor/SQLEditor.tsx +3 -3
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +1 -1
- package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +3 -2
- package/src/components/SchemaVisualizer/TableNode.tsx +1 -1
- package/src/components/StorageView/StorageView.tsx +1 -1
- package/src/components/StudioHomePage.tsx +62 -87
- package/src/index.ts +3 -3
- package/dist/ApiExplorer-CdIwR9Ga.js.map +0 -1
- package/dist/ApiKeysView-D-_FSlNL.js.map +0 -1
- package/dist/AuthSimulationSelector-iEZ-Or_1.js.map +0 -1
- package/dist/BranchesView-Dlg78EQ8.js.map +0 -1
- package/dist/CronJobsView-4gdtJvoe.js.map +0 -1
- package/dist/JSEditor-Sgmw1SHC.js.map +0 -1
- package/dist/LogsExplorer-J4xfsuv3.js +0 -206
- package/dist/LogsExplorer-J4xfsuv3.js.map +0 -1
- package/dist/MonacoEditor-COZqrIJ1.js.map +0 -1
- package/dist/RLSEditor-DPhky8XW.js.map +0 -1
- package/dist/SQLEditor-CgR2hlUI.js.map +0 -1
- package/dist/SchemaVisualizer-WK4ZZR5X.js.map +0 -1
- package/dist/StorageView-BMhD29YO.js.map +0 -1
- package/dist/index.umd.js +0 -11477
- package/dist/index.umd.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { useStudioCollectionRegistry } from "@rebasepro/
|
|
2
|
+
import { useStudioCollectionRegistry } from "@rebasepro/app";
|
|
3
3
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
4
4
|
import {
|
|
5
5
|
Alert,
|
|
@@ -25,9 +25,11 @@ import {
|
|
|
25
25
|
Trash2Icon,
|
|
26
26
|
Typography
|
|
27
27
|
} from "@rebasepro/ui";
|
|
28
|
-
import { useRebaseContext, useSnackbarController, ErrorView, useTranslation } from "@rebasepro/
|
|
28
|
+
import { useRebaseContext, useSnackbarController, ErrorView, useTranslation } from "@rebasepro/app";
|
|
29
29
|
import { isPostgresCollectionConfig } from "@rebasepro/types";
|
|
30
30
|
import { REBASE_INTERNAL_SCHEMAS, REBASE_INTERNAL_PREFIXES, JUNCTION_TABLES_SQL } from "@rebasepro/common";
|
|
31
|
+
import { getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations } from "@rebasepro/utils";
|
|
32
|
+
import { resolveJunctionSpecs, getJunctionSecurityRules } from "@rebasepro/common";
|
|
31
33
|
import { PolicyEditor } from "./PolicyEditor";
|
|
32
34
|
|
|
33
35
|
type TableCategory = "collection" | "junction" | "internal" | "other";
|
|
@@ -387,28 +389,54 @@ export const RLSEditor = ({ apiUrl = "" }: { apiUrl?: string }) => {
|
|
|
387
389
|
status: "live" };
|
|
388
390
|
});
|
|
389
391
|
|
|
390
|
-
// Merge code-based policies
|
|
392
|
+
// Merge code-based policies.
|
|
393
|
+
//
|
|
394
|
+
// A rule without an explicit `name` still produces policies — Postgres gets
|
|
395
|
+
// `<table>_<op>_<hash>`, one per operation. Skipping those rules left their
|
|
396
|
+
// live policies looking like hand-written SQL ("DB Only"), so derive the
|
|
397
|
+
// names the generator would emit and match on those.
|
|
391
398
|
if (activeCollection && isPostgresCollectionConfig(activeCollection) && activeCollection.securityRules) {
|
|
392
399
|
activeCollection.securityRules.forEach((rule) => {
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
400
|
+
const ops = getPolicyOperations(rule);
|
|
401
|
+
const policyNames = getPolicyNamesForRule(rule, activeTableData.tableName);
|
|
402
|
+
|
|
403
|
+
policyNames.forEach((policyName, opIdx) => {
|
|
404
|
+
policiesMap[policyName] = {
|
|
405
|
+
policyname: policyName,
|
|
406
|
+
tablename: activeTableData.tableName,
|
|
407
|
+
permissive: (rule.mode || "permissive").toUpperCase() as PostgresPolicy["permissive"],
|
|
408
|
+
cmd: (ops[opIdx] ?? rule.operation ?? "ALL").toUpperCase() as PostgresPolicy["cmd"],
|
|
409
|
+
roles: [...(rule.roles ?? ["public"])],
|
|
410
|
+
qual: rule.using || null,
|
|
411
|
+
with_check: rule.withCheck || null,
|
|
412
|
+
// "both" = defined in code and live in Postgres (potentially edited)
|
|
413
|
+
status: policiesMap[policyName] ? "both" : "code_only"
|
|
414
|
+
};
|
|
415
|
+
});
|
|
407
416
|
});
|
|
408
417
|
}
|
|
409
418
|
|
|
419
|
+
// Junction tables have no collection, but their policies are generated
|
|
420
|
+
// too — derived from the endpoints' relations. Recognise them so they
|
|
421
|
+
// don't show as hand-written SQL ("DB Only"). If the registry's
|
|
422
|
+
// collections don't carry resolvable relations, they simply stay "live".
|
|
423
|
+
if (!activeCollection) {
|
|
424
|
+
try {
|
|
425
|
+
const registryCollections = (collectionRegistry.collections ?? []) as unknown as Parameters<typeof resolveJunctionSpecs>[0];
|
|
426
|
+
const spec = resolveJunctionSpecs(registryCollections).get(activeTableData.tableName);
|
|
427
|
+
if (spec) {
|
|
428
|
+
const generatedNames = getPolicyNamesForRules(getJunctionSecurityRules(spec), spec.table);
|
|
429
|
+
for (const p of Object.values(policiesMap)) {
|
|
430
|
+
if (generatedNames.has(p.policyname)) p.status = "both";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch {
|
|
434
|
+
/* serialized configs without relation closures — leave as live */
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
410
438
|
return Object.values(policiesMap).sort((a, b) => a.policyname.localeCompare(b.policyname));
|
|
411
|
-
}, [activeTableData, activeCollection]);
|
|
439
|
+
}, [activeTableData, activeCollection, collectionRegistry.collections]);
|
|
412
440
|
|
|
413
441
|
// Stats for the info tab
|
|
414
442
|
const rlsStats = useMemo(() => {
|
|
@@ -816,8 +844,10 @@ message: e instanceof Error ? e.message : String(e) });
|
|
|
816
844
|
</Typography>
|
|
817
845
|
<Typography variant="caption" className="opacity-80">
|
|
818
846
|
This is an auto-generated junction table for a many-to-many relation.
|
|
819
|
-
|
|
820
|
-
|
|
847
|
+
Rebase derives its policies: rows are readable when both related rows
|
|
848
|
+
are, and writable following the declaring collection's update rules
|
|
849
|
+
(plus the server/admin baseline). You can still add RLS policies
|
|
850
|
+
directly to broaden access.
|
|
821
851
|
</Typography>
|
|
822
852
|
</div>
|
|
823
853
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { lazy, Suspense, useLayoutEffect, useMemo } from "react";
|
|
2
|
-
import { useRebaseRegistryDispatch } from "@rebasepro/
|
|
2
|
+
import { useRebaseRegistryDispatch } from "@rebasepro/app";
|
|
3
3
|
import type { RebaseStudioConfig, AppView } from "@rebasepro/types";
|
|
4
4
|
import { CircularProgressCenter } from "@rebasepro/ui";
|
|
5
5
|
|
|
@@ -12,6 +12,7 @@ const StorageView = lazy(() => import("./StorageView/StorageView").then(m => ({
|
|
|
12
12
|
const CronJobsView = lazy(() => import("./CronJobs/CronJobsView").then(m => ({ default: m.CronJobsView })));
|
|
13
13
|
const SchemaVisualizer = lazy(() => import("./SchemaVisualizer/SchemaVisualizer").then(m => ({ default: m.SchemaVisualizer })));
|
|
14
14
|
const BranchesView = lazy(() => import("./Branches/BranchesView").then(m => ({ default: m.BranchesView })));
|
|
15
|
+
const BackupsView = lazy(() => import("./Backups/BackupsView").then(m => ({ default: m.BackupsView })));
|
|
15
16
|
const ApiExplorer = lazy(() => import("./ApiExplorer/ApiExplorer").then(m => ({ default: m.ApiExplorer })));
|
|
16
17
|
const LogsExplorer = lazy(() => import("./LogsExplorer/LogsExplorer").then(m => ({ default: m.LogsExplorer })));
|
|
17
18
|
const ApiKeysView = lazy(() => import("./ApiKeys/ApiKeysView").then(m => ({ default: m.ApiKeysView })));
|
|
@@ -23,7 +24,7 @@ import { StudioHomePage } from "./StudioHomePage";
|
|
|
23
24
|
* Renders nothing — purely registers config into the RebaseRegistry.
|
|
24
25
|
*
|
|
25
26
|
* The "schema" tool (collection editor view) is now a built-in CMS feature.
|
|
26
|
-
* When `<
|
|
27
|
+
* When `<RebaseAdmin collectionEditor={...}>` is used, the schema view is
|
|
27
28
|
* automatically injected into Studio — no manual wiring needed.
|
|
28
29
|
*/
|
|
29
30
|
const DEFAULT_HOME_PAGE = <StudioHomePage/>;
|
|
@@ -35,7 +36,7 @@ export function RebaseStudio({ tools, homePage }: RebaseStudioConfig) {
|
|
|
35
36
|
|
|
36
37
|
const devViews: AppView[] = useMemo(() => {
|
|
37
38
|
const views: AppView[] = [];
|
|
38
|
-
const activeTools = tools ?? ["sql", "js", "rls", "storage", "cron", "schema-visualizer", "branches", "api", "logs", "api-keys"];
|
|
39
|
+
const activeTools = tools ?? ["sql", "js", "rls", "storage", "cron", "schema-visualizer", "branches", "backups", "api", "logs", "api-keys"];
|
|
39
40
|
const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;
|
|
40
41
|
|
|
41
42
|
if (activeTools.includes("sql")) {
|
|
@@ -93,6 +94,14 @@ group: "Database",
|
|
|
93
94
|
icon: "GitBranch",
|
|
94
95
|
description: "Create and manage database branches",
|
|
95
96
|
view: suspense(<BranchesView/>) });
|
|
97
|
+
}
|
|
98
|
+
if (activeTools.includes("backups")) {
|
|
99
|
+
views.push({ slug: "backups",
|
|
100
|
+
name: "Backups",
|
|
101
|
+
group: "Database",
|
|
102
|
+
icon: "Database",
|
|
103
|
+
description: "Download database backups",
|
|
104
|
+
view: suspense(<BackupsView/>) });
|
|
96
105
|
}
|
|
97
106
|
if (activeTools.includes("api")) {
|
|
98
107
|
views.push({ slug: "api",
|
|
@@ -2,7 +2,7 @@ import React, { useRef } from "react";
|
|
|
2
2
|
import Editor, { Monaco, OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type { editor, Position, IRange } from "monaco-editor";
|
|
4
4
|
import { cls, defaultBorderMixin } from "@rebasepro/ui";
|
|
5
|
-
import { useModeController } from "@rebasepro/
|
|
5
|
+
import { useModeController } from "@rebasepro/app";
|
|
6
6
|
|
|
7
7
|
export type MonacoEditorProps = {
|
|
8
8
|
value: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { IconForView } from "@rebasepro/
|
|
3
|
-
import { useStudioCollectionRegistry, useStudioSidePanelController } from "@rebasepro/
|
|
2
|
+
import { IconForView } from "@rebasepro/app";
|
|
3
|
+
import { useStudioCollectionRegistry, useStudioSidePanelController } from "@rebasepro/app";
|
|
4
4
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import {
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
XIcon
|
|
43
43
|
} from "@rebasepro/ui";
|
|
44
44
|
|
|
45
|
-
import { useRebaseContext, useSnackbarController, ConfirmationDialog, ErrorView, useTranslation } from "@rebasepro/
|
|
45
|
+
import { useRebaseContext, useSnackbarController, ConfirmationDialog, ErrorView, useTranslation } from "@rebasepro/app";
|
|
46
46
|
import { MonacoEditor } from "./MonacoEditor";
|
|
47
47
|
import { SQLEditorSidebar, Snippet } from "./SQLEditorSidebar";
|
|
48
48
|
import { parseFirst } from "pgsql-ast-parser";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { useState } from "react";
|
|
3
3
|
import { cls, defaultBorderMixin, IconButton, iconSize, Tab, Tabs, Trash2Icon, Typography } from "@rebasepro/ui";
|
|
4
|
-
import { useTranslation } from "@rebasepro/
|
|
4
|
+
import { useTranslation } from "@rebasepro/app";
|
|
5
5
|
import { SchemaBrowser } from "./SchemaBrowser";
|
|
6
6
|
import { TableInfo } from "./SQLEditor";
|
|
7
7
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
Typography
|
|
16
16
|
} from "@rebasepro/ui";
|
|
17
17
|
import { TableInfo } from "./SQLEditor";
|
|
18
|
-
import { ErrorView, useTranslation } from "@rebasepro/
|
|
18
|
+
import { ErrorView, useTranslation } from "@rebasepro/app";
|
|
19
19
|
|
|
20
20
|
export const SchemaBrowser = ({
|
|
21
21
|
onTableClick,
|
|
@@ -86,7 +86,7 @@ export const SchemaBrowser = ({
|
|
|
86
86
|
e.stopPropagation();
|
|
87
87
|
navigator.clipboard.writeText(table.tableName);
|
|
88
88
|
}}
|
|
89
|
-
title="
|
|
89
|
+
title="Copy table name"
|
|
90
90
|
>
|
|
91
91
|
<CopyIcon size={iconSize.small}/>
|
|
92
92
|
</IconButton>
|
|
@@ -148,7 +148,7 @@ export const SchemaBrowser = ({
|
|
|
148
148
|
e.stopPropagation();
|
|
149
149
|
navigator.clipboard.writeText(col.name);
|
|
150
150
|
}}
|
|
151
|
-
title="
|
|
151
|
+
title="Copy column name"
|
|
152
152
|
>
|
|
153
153
|
<CopyIcon size={iconSize.smallest}/>
|
|
154
154
|
</IconButton>
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
useStudioCollectionRegistry,
|
|
30
30
|
IconForView
|
|
31
|
-
} from "@rebasepro/
|
|
31
|
+
} from "@rebasepro/app";
|
|
32
32
|
import type { CollectionConfig } from "@rebasepro/types";
|
|
33
33
|
import { isPostgresCollectionConfig } from "@rebasepro/types";
|
|
34
34
|
import { useSchemaGraph } from "./useSchemaGraph";
|
|
@@ -543,7 +543,8 @@ duration: 400 }
|
|
|
543
543
|
/>
|
|
544
544
|
<Controls
|
|
545
545
|
showInteractive={false}
|
|
546
|
-
|
|
546
|
+
// mb clears the legend overlay pinned at bottom-left
|
|
547
|
+
className="!mb-16 !bg-white dark:!bg-surface-900 !border !border-surface-200/40 dark:!border-surface-700/40 !shadow-sm !rounded-lg dark:[--xy-controls-button-background-color:var(--color-surface-900)] dark:[--xy-controls-button-background-color-hover:var(--color-surface-800)] dark:[--xy-controls-button-color:var(--color-surface-300)] dark:[--xy-controls-button-color-hover:var(--color-surface-50)] dark:[--xy-controls-button-border-color:var(--color-surface-700)]"
|
|
547
548
|
/>
|
|
548
549
|
<MiniMap
|
|
549
550
|
nodeStrokeColor={(n) => {
|
|
@@ -2,7 +2,7 @@ import React, { memo, useMemo } from "react";
|
|
|
2
2
|
import { Handle, Position } from "@xyflow/react";
|
|
3
3
|
import type { NodeProps } from "@xyflow/react";
|
|
4
4
|
import { Typography, Chip, Tooltip, cls } from "@rebasepro/ui";
|
|
5
|
-
import { IconForView } from "@rebasepro/
|
|
5
|
+
import { IconForView } from "@rebasepro/app";
|
|
6
6
|
import type { TableNodeData, ColumnInfo } from "./useSchemaGraph";
|
|
7
7
|
import { getColumnRowY, getHeaderHeight } from "./schema-visualizer.utils";
|
|
8
8
|
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
VideoIcon,
|
|
42
42
|
XIcon
|
|
43
43
|
} from "@rebasepro/ui";
|
|
44
|
-
import { useStorageSource, useStorageSources, useSnackbarController, ErrorView, useApiConfig } from "@rebasepro/
|
|
44
|
+
import { useStorageSource, useStorageSources, useSnackbarController, ErrorView, useApiConfig } from "@rebasepro/app";
|
|
45
45
|
import { DEFAULT_STORAGE_SOURCE_KEY, type StorageListResult } from "@rebasepro/types";
|
|
46
46
|
import { useSearchParams } from "react-router-dom";
|
|
47
47
|
import { useDropzone } from "react-dropzone";
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type { HomePageSection, PluginGenericProps } from "@rebasepro/types";
|
|
1
|
+
import type { AppView, HomePageSection, PluginGenericProps } from "@rebasepro/types";
|
|
2
2
|
import React, { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Card, cls, Container, ExpandablePanel, Typography } from "@rebasepro/ui";
|
|
4
|
-
import { IconForView, useRebaseContext, useRestoreScroll, useSlot } from "@rebasepro/
|
|
4
|
+
import { IconForView, useRebaseContext, useRebaseRegistry, useRestoreScroll, useSlot } from "@rebasepro/app";
|
|
5
5
|
import { useNavigate } from "react-router-dom";
|
|
6
|
-
import { useStudioBreadcrumbs, SchemaDriftBanner } from "@rebasepro/
|
|
6
|
+
import { useStudioBreadcrumbs, SchemaDriftBanner } from "@rebasepro/app";
|
|
7
7
|
|
|
8
8
|
/* ═══════════════════════════════════════════════════════════════
|
|
9
|
-
|
|
9
|
+
Studio tool sections, derived from the registered Studio views
|
|
10
10
|
═══════════════════════════════════════════════════════════════ */
|
|
11
11
|
|
|
12
12
|
interface StudioTool {
|
|
13
13
|
path: string;
|
|
14
14
|
name: string;
|
|
15
15
|
description: string;
|
|
16
|
-
icon
|
|
16
|
+
icon?: string | React.ReactNode;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface StudioSection {
|
|
@@ -21,85 +21,44 @@ interface StudioSection {
|
|
|
21
21
|
tools: StudioTool[];
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
label: "Compute",
|
|
56
|
-
tools: [
|
|
57
|
-
{ path: "/js",
|
|
58
|
-
name: "JS Console",
|
|
59
|
-
description: "Run JavaScript with the Rebase SDK in a live sandbox",
|
|
60
|
-
icon: "code" },
|
|
61
|
-
{ path: "/cron",
|
|
62
|
-
name: "Cron Jobs",
|
|
63
|
-
description: "Monitor and manage scheduled background tasks",
|
|
64
|
-
icon: "Clock" }
|
|
65
|
-
]
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
label: "API",
|
|
69
|
-
tools: [
|
|
70
|
-
{ path: "/api",
|
|
71
|
-
name: "API Explorer",
|
|
72
|
-
description: "Interactive API documentation with live request testing",
|
|
73
|
-
icon: "BookOpen" }
|
|
74
|
-
]
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
label: "Storage",
|
|
78
|
-
tools: [
|
|
79
|
-
{ path: "/storage",
|
|
80
|
-
name: "Storage",
|
|
81
|
-
description: "Browse, upload, and manage files in your storage bucket",
|
|
82
|
-
icon: "HardDrive" }
|
|
83
|
-
]
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
label: "Access Control",
|
|
87
|
-
tools: [
|
|
88
|
-
{ path: "/users",
|
|
89
|
-
name: "Users",
|
|
90
|
-
description: "Manage developers and assign roles in your workspace",
|
|
91
|
-
icon: "group" },
|
|
92
|
-
{ path: "/roles",
|
|
93
|
-
name: "Roles",
|
|
94
|
-
description: "Create and configure fine-grained access permissions",
|
|
95
|
-
icon: "ShieldEllipsis" },
|
|
96
|
-
{ path: "/api-keys",
|
|
97
|
-
name: "API Keys",
|
|
98
|
-
description: "Create and manage scoped keys for machine-to-machine access",
|
|
99
|
-
icon: "KeyRound" }
|
|
100
|
-
]
|
|
24
|
+
/** The view metadata the cards need — not the rendered `view` itself. */
|
|
25
|
+
type StudioViewMeta = Pick<AppView, "slug" | "name" | "group" | "description" | "icon" | "hideFromNavigation">;
|
|
26
|
+
|
|
27
|
+
/** Group order for the home page; unknown groups are appended in view order. */
|
|
28
|
+
const GROUP_ORDER = ["Database", "Compute", "Storage", "API", "Access Control"];
|
|
29
|
+
|
|
30
|
+
const UNGROUPED_LABEL = "Tools";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build the home page sections from the Studio views that are actually
|
|
34
|
+
* registered, so a card exists if and only if its route does. A hand-written
|
|
35
|
+
* list drifts: it used to advertise Users and Roles pages that 404, while
|
|
36
|
+
* omitting the Backups view that does exist.
|
|
37
|
+
*/
|
|
38
|
+
function buildSections(views: StudioViewMeta[]): StudioSection[] {
|
|
39
|
+
const byGroup = new Map<string, StudioTool[]>();
|
|
40
|
+
|
|
41
|
+
for (const view of views) {
|
|
42
|
+
if (view.hideFromNavigation) continue;
|
|
43
|
+
const label = view.group ?? UNGROUPED_LABEL;
|
|
44
|
+
const tools = byGroup.get(label) ?? [];
|
|
45
|
+
tools.push({
|
|
46
|
+
path: `/${view.slug}`,
|
|
47
|
+
name: view.name,
|
|
48
|
+
description: view.description ?? "",
|
|
49
|
+
icon: view.icon
|
|
50
|
+
});
|
|
51
|
+
byGroup.set(label, tools);
|
|
101
52
|
}
|
|
102
|
-
|
|
53
|
+
|
|
54
|
+
const ordered = [
|
|
55
|
+
...GROUP_ORDER.filter(g => byGroup.has(g)),
|
|
56
|
+
...[...byGroup.keys()].filter(g => !GROUP_ORDER.includes(g))
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
return ordered.map(label => ({ label,
|
|
60
|
+
tools: byGroup.get(label)! }));
|
|
61
|
+
}
|
|
103
62
|
|
|
104
63
|
/* ═══════════════════════════════════════════════════════════════ */
|
|
105
64
|
|
|
@@ -151,6 +110,7 @@ export function StudioHomePage({
|
|
|
151
110
|
const context = useRebaseContext();
|
|
152
111
|
const breadcrumbs = useStudioBreadcrumbs();
|
|
153
112
|
const navigate = useNavigate();
|
|
113
|
+
const registry = useRebaseRegistry();
|
|
154
114
|
|
|
155
115
|
useEffect(() => {
|
|
156
116
|
breadcrumbs.set({ breadcrumbs: [] });
|
|
@@ -162,10 +122,25 @@ export function StudioHomePage({
|
|
|
162
122
|
|
|
163
123
|
const pluginActions = useSlot("home.actions", sectionProps);
|
|
164
124
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
);
|
|
125
|
+
// The collection editor ("schema") is not part of `devViews` — RebaseNavigation
|
|
126
|
+
// injects it when the CMS enables a collection editor. Mirror that condition so
|
|
127
|
+
// the card tracks the route.
|
|
128
|
+
const schemaEnabled = Boolean(registry.studioConfig && registry.cmsConfig?.collectionEditor);
|
|
129
|
+
|
|
130
|
+
const filteredSections = useMemo(() => {
|
|
131
|
+
const views: StudioViewMeta[] = [];
|
|
132
|
+
if (schemaEnabled) {
|
|
133
|
+
views.push({
|
|
134
|
+
slug: "schema",
|
|
135
|
+
name: "Collections",
|
|
136
|
+
group: "Database",
|
|
137
|
+
icon: "LayoutList",
|
|
138
|
+
description: "Define and manage your data model and collection schemas"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
views.push(...(registry.studioConfig?.devViews ?? []));
|
|
142
|
+
return buildSections(views).filter(s => !hiddenGroups?.includes(s.label));
|
|
143
|
+
}, [registry.studioConfig?.devViews, schemaEnabled, hiddenGroups]);
|
|
169
144
|
|
|
170
145
|
const groupNames = useMemo(
|
|
171
146
|
() => filteredSections.map(s => s.label),
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ─── Bridge ─────────────────────────────────────────────────────────
|
|
2
|
-
// Re-export the Studio Bridge from @rebasepro/
|
|
2
|
+
// Re-export the Studio Bridge from @rebasepro/app.
|
|
3
3
|
// The bridge lives in core so both studio and CMS can access it
|
|
4
4
|
// without circular dependencies.
|
|
5
5
|
export {
|
|
@@ -10,12 +10,12 @@ export {
|
|
|
10
10
|
useStudioUrlController,
|
|
11
11
|
useStudioNavigationState,
|
|
12
12
|
useStudioBreadcrumbs
|
|
13
|
-
} from "@rebasepro/
|
|
13
|
+
} from "@rebasepro/app";
|
|
14
14
|
export type {
|
|
15
15
|
StudioBridge,
|
|
16
16
|
BreadcrumbEntry,
|
|
17
17
|
BreadcrumbsController
|
|
18
|
-
} from "@rebasepro/
|
|
18
|
+
} from "@rebasepro/app";
|
|
19
19
|
|
|
20
20
|
// ─── Studio Entry Point ─────────────────────────────────────────────
|
|
21
21
|
// Only export the lightweight orchestrator & home page.
|