@rebasepro/studio 0.6.1 → 0.8.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/dist/ApiKeysView-D-_FSlNL.js +684 -0
- package/dist/ApiKeysView-D-_FSlNL.js.map +1 -0
- package/dist/{BranchesView-DncIRcZt.js → BranchesView-Dlg78EQ8.js} +4 -5
- package/dist/{BranchesView-DncIRcZt.js.map → BranchesView-Dlg78EQ8.js.map} +1 -1
- package/dist/{JSEditor-BhAbEjCP.js → JSEditor-Ca4XYGRp.js} +7 -7
- package/dist/JSEditor-Ca4XYGRp.js.map +1 -0
- package/dist/{LogsExplorer-CqtKILj8.js → LogsExplorer-J4xfsuv3.js} +49 -83
- package/dist/LogsExplorer-J4xfsuv3.js.map +1 -0
- package/dist/{RLSEditor-DpF1u9EC.js → RLSEditor-BM64laoW.js} +294 -107
- package/dist/RLSEditor-BM64laoW.js.map +1 -0
- package/dist/{SQLEditor-BLuq_zDM.js → SQLEditor-CuAhR-zr.js} +4 -4
- package/dist/{SQLEditor-BLuq_zDM.js.map → SQLEditor-CuAhR-zr.js.map} +1 -1
- package/dist/{SchemaVisualizer-BJK2u3C0.js → SchemaVisualizer-OibKoD3g.js} +2 -2
- package/dist/{SchemaVisualizer-BJK2u3C0.js.map → SchemaVisualizer-OibKoD3g.js.map} +1 -1
- package/dist/{StorageView-CvrnHmDG.js → StorageView-BMhD29YO.js} +33 -5
- package/dist/StorageView-BMhD29YO.js.map +1 -0
- package/dist/components/ApiKeys/ApiKeysView.d.ts +2 -0
- package/dist/index.es.js +141 -94
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1221 -317
- package/dist/index.umd.js.map +1 -1
- package/package.json +8 -8
- package/src/components/ApiKeys/ApiKeysView.tsx +580 -0
- package/src/components/Branches/BranchesView.tsx +3 -4
- package/src/components/JSEditor/JSEditorSidebar.tsx +3 -3
- package/src/components/JSEditor/JSMonacoEditor.tsx +3 -3
- package/src/components/LogsExplorer/LogsExplorer.tsx +46 -84
- package/src/components/RLSEditor/RLSEditor.tsx +354 -117
- package/src/components/RebaseStudio.tsx +10 -1
- package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
- package/src/components/StorageView/StorageView.tsx +43 -4
- package/src/components/StudioHomePage.tsx +144 -85
- package/src/utils/pgColumnToProperty.ts +2 -2
- package/dist/JSEditor-BhAbEjCP.js.map +0 -1
- package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
- package/dist/RLSEditor-DpF1u9EC.js.map +0 -1
- package/dist/StorageView-CvrnHmDG.js.map +0 -1
|
@@ -14,6 +14,7 @@ const SchemaVisualizer = lazy(() => import("./SchemaVisualizer/SchemaVisualizer"
|
|
|
14
14
|
const BranchesView = lazy(() => import("./Branches/BranchesView").then(m => ({ default: m.BranchesView })));
|
|
15
15
|
const ApiExplorer = lazy(() => import("./ApiExplorer/ApiExplorer").then(m => ({ default: m.ApiExplorer })));
|
|
16
16
|
const LogsExplorer = lazy(() => import("./LogsExplorer/LogsExplorer").then(m => ({ default: m.LogsExplorer })));
|
|
17
|
+
const ApiKeysView = lazy(() => import("./ApiKeys/ApiKeysView").then(m => ({ default: m.ApiKeysView })));
|
|
17
18
|
|
|
18
19
|
import { StudioHomePage } from "./StudioHomePage";
|
|
19
20
|
|
|
@@ -34,7 +35,7 @@ export function RebaseStudio({ tools, homePage }: RebaseStudioConfig) {
|
|
|
34
35
|
|
|
35
36
|
const devViews: AppView[] = useMemo(() => {
|
|
36
37
|
const views: AppView[] = [];
|
|
37
|
-
const activeTools = tools ?? ["sql", "js", "rls", "storage", "cron", "schema-visualizer", "branches", "api", "logs"];
|
|
38
|
+
const activeTools = tools ?? ["sql", "js", "rls", "storage", "cron", "schema-visualizer", "branches", "api", "logs", "api-keys"];
|
|
38
39
|
const suspense = (el: React.ReactNode) => <Suspense fallback={<CircularProgressCenter/>}>{el}</Suspense>;
|
|
39
40
|
|
|
40
41
|
if (activeTools.includes("sql")) {
|
|
@@ -108,6 +109,14 @@ group: "Database",
|
|
|
108
109
|
icon: "Activity",
|
|
109
110
|
description: "Real-time system and query logs",
|
|
110
111
|
view: suspense(<LogsExplorer/>) });
|
|
112
|
+
}
|
|
113
|
+
if (activeTools.includes("api-keys")) {
|
|
114
|
+
views.push({ slug: "api-keys",
|
|
115
|
+
name: "API Keys",
|
|
116
|
+
group: "Access Control",
|
|
117
|
+
icon: "KeyRound",
|
|
118
|
+
description: "Create and manage scoped API keys",
|
|
119
|
+
view: suspense(<ApiKeysView/>) });
|
|
111
120
|
}
|
|
112
121
|
// Note: "schema" tool is auto-injected by RebaseShell when collectionEditor is enabled.
|
|
113
122
|
// It is NOT registered here anymore.
|
|
@@ -58,7 +58,7 @@ export const SchemaBrowser = ({
|
|
|
58
58
|
) : Object.entries(schemas).map(([schemaName, tables]) => (
|
|
59
59
|
<div key={schemaName} className="mb-2">
|
|
60
60
|
<div
|
|
61
|
-
className="flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-
|
|
61
|
+
className="flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors"
|
|
62
62
|
onClick={() => setExpandedSchemas(prev => ({ ...prev,
|
|
63
63
|
[schemaName]: !prev[schemaName] }))}
|
|
64
64
|
>
|
|
@@ -71,7 +71,7 @@ export const SchemaBrowser = ({
|
|
|
71
71
|
{tables.map(table => (
|
|
72
72
|
<div key={table.tableName}>
|
|
73
73
|
<div
|
|
74
|
-
className="flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-
|
|
74
|
+
className="flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors group relative"
|
|
75
75
|
onClick={() => setExpandedTables(prev => ({ ...prev,
|
|
76
76
|
[`${schemaName}.${table.tableName}`]: !prev[`${schemaName}.${table.tableName}`] }))}
|
|
77
77
|
>
|
|
@@ -137,7 +137,7 @@ export const SchemaBrowser = ({
|
|
|
137
137
|
{expandedTables[`${schemaName}.${table.tableName}`] && (
|
|
138
138
|
<div className={cls("ml-5 mt-1 space-y-0.5 border-l", defaultBorderMixin)}>
|
|
139
139
|
{table.columns.map(col => (
|
|
140
|
-
<div key={col.name} className="flex items-center p-1 group pl-2 hover:bg-surface-50 dark:hover:bg-surface-
|
|
140
|
+
<div key={col.name} className="flex items-center p-1 group pl-2 hover:bg-surface-50 dark:hover:bg-surface-900 rounded-r relative min-h-[28px]">
|
|
141
141
|
<svg className="w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 4.5v15m6-15v15m-10.5-1.5h15c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125h-15c-.621 0-1.125.504-1.125 1.125v12.75c0 .621.504 1.125 1.125 1.125Z"/></svg>
|
|
142
142
|
<Typography variant="caption" className="text-text-primary dark:text-text-primary-dark text-[11px] truncate flex-grow mr-2">{col.name}</Typography>
|
|
143
143
|
<Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark text-[9px] truncate mr-1 uppercase shrink-0" title={col.dataType}>{col.dataType}</Typography>
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
PlusIcon,
|
|
32
32
|
RefreshCwIcon,
|
|
33
33
|
ResizablePanels,
|
|
34
|
+
Select,
|
|
35
|
+
SelectItem,
|
|
34
36
|
TextField,
|
|
35
37
|
Tooltip,
|
|
36
38
|
Trash2Icon,
|
|
@@ -39,8 +41,8 @@ import {
|
|
|
39
41
|
VideoIcon,
|
|
40
42
|
XIcon
|
|
41
43
|
} from "@rebasepro/ui";
|
|
42
|
-
import { useStorageSource, useSnackbarController, ErrorView, useApiConfig } from "@rebasepro/core";
|
|
43
|
-
import type
|
|
44
|
+
import { useStorageSource, useStorageSources, useSnackbarController, ErrorView, useApiConfig } from "@rebasepro/core";
|
|
45
|
+
import { DEFAULT_STORAGE_SOURCE_KEY, type StorageListResult } from "@rebasepro/types";
|
|
44
46
|
import { useSearchParams } from "react-router-dom";
|
|
45
47
|
import { useDropzone } from "react-dropzone";
|
|
46
48
|
|
|
@@ -469,9 +471,22 @@ function FilePreviewPanel({
|
|
|
469
471
|
// ──────────────────────────────────────────────
|
|
470
472
|
|
|
471
473
|
export const StorageView = () => {
|
|
472
|
-
const
|
|
474
|
+
const defaultStorageSource = useStorageSource();
|
|
475
|
+
const storageSources = useStorageSources();
|
|
473
476
|
const snackbarController = useSnackbarController();
|
|
474
477
|
|
|
478
|
+
// Available backends to browse. Always includes the default; named
|
|
479
|
+
// sources come from `<Rebase storageSources={...}>`.
|
|
480
|
+
const sourceKeys = useMemo(() => {
|
|
481
|
+
const keys = Object.keys(storageSources.sources);
|
|
482
|
+
if (!keys.includes(DEFAULT_STORAGE_SOURCE_KEY)) keys.unshift(DEFAULT_STORAGE_SOURCE_KEY);
|
|
483
|
+
return keys;
|
|
484
|
+
}, [storageSources.sources]);
|
|
485
|
+
|
|
486
|
+
const [selectedSourceKey, setSelectedSourceKey] = useState<string>(DEFAULT_STORAGE_SOURCE_KEY);
|
|
487
|
+
|
|
488
|
+
const storageSource = storageSources.sources[selectedSourceKey] ?? defaultStorageSource;
|
|
489
|
+
|
|
475
490
|
// Navigation
|
|
476
491
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
477
492
|
const currentPath = searchParams.get("path") || "";
|
|
@@ -559,8 +574,10 @@ export const StorageView = () => {
|
|
|
559
574
|
}, []);
|
|
560
575
|
|
|
561
576
|
useEffect(() => {
|
|
577
|
+
// `selectedSourceKey` is a dep so switching backend re-lists; the
|
|
578
|
+
// ref-sync effect above runs first, so `storageSourceRef` is current.
|
|
562
579
|
fetchContents(currentPath);
|
|
563
|
-
}, [currentPath, fetchContents]);
|
|
580
|
+
}, [currentPath, fetchContents, selectedSourceKey]);
|
|
564
581
|
|
|
565
582
|
// Navigate to path
|
|
566
583
|
const handleNavigate = useCallback((path: string) => {
|
|
@@ -1266,6 +1283,28 @@ message: e instanceof Error ? e.message : String(e) });
|
|
|
1266
1283
|
|
|
1267
1284
|
<div className="flex shrink-0 items-center justify-end gap-1.5 pr-1">
|
|
1268
1285
|
|
|
1286
|
+
{/* Backend picker — only shown when more than one storage source is available */}
|
|
1287
|
+
{sourceKeys.length > 1 && (
|
|
1288
|
+
<Select
|
|
1289
|
+
size="small"
|
|
1290
|
+
position="item-aligned"
|
|
1291
|
+
value={selectedSourceKey}
|
|
1292
|
+
onValueChange={(value) => {
|
|
1293
|
+
if (value) setSelectedSourceKey(value);
|
|
1294
|
+
}}
|
|
1295
|
+
renderValue={(key) => {
|
|
1296
|
+
const label = storageSources.registry[key]?.label;
|
|
1297
|
+
return label ?? (key === DEFAULT_STORAGE_SOURCE_KEY ? "Default" : key);
|
|
1298
|
+
}}>
|
|
1299
|
+
{sourceKeys.map((key) => (
|
|
1300
|
+
<SelectItem key={key} value={key}>
|
|
1301
|
+
{storageSources.registry[key]?.label
|
|
1302
|
+
?? (key === DEFAULT_STORAGE_SOURCE_KEY ? "Default" : key)}
|
|
1303
|
+
</SelectItem>
|
|
1304
|
+
))}
|
|
1305
|
+
</Select>
|
|
1306
|
+
)}
|
|
1307
|
+
|
|
1269
1308
|
<Tooltip title="Grid view">
|
|
1270
1309
|
<IconButton
|
|
1271
1310
|
size="small"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { HomePageSection, PluginGenericProps } from "@rebasepro/types";
|
|
2
|
-
import React, { useEffect } from "react";
|
|
3
|
-
import { Card, cls, Container, Typography } from "@rebasepro/ui";
|
|
4
|
-
import { ArrowRightIcon, iconSize } from "@rebasepro/ui";
|
|
2
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Card, cls, Container, ExpandablePanel, Typography } from "@rebasepro/ui";
|
|
5
4
|
import { IconForView, useRebaseContext, useRestoreScroll, useSlot } from "@rebasepro/core";
|
|
6
5
|
import { useNavigate } from "react-router-dom";
|
|
7
6
|
import { useStudioBreadcrumbs, SchemaDriftBanner } from "@rebasepro/core";
|
|
@@ -19,16 +18,12 @@ interface StudioTool {
|
|
|
19
18
|
|
|
20
19
|
interface StudioSection {
|
|
21
20
|
label: string;
|
|
22
|
-
dotColor: string;
|
|
23
|
-
iconColor: string;
|
|
24
21
|
tools: StudioTool[];
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
const SECTIONS: StudioSection[] = [
|
|
28
25
|
{
|
|
29
26
|
label: "Database",
|
|
30
|
-
dotColor: "bg-emerald-400",
|
|
31
|
-
iconColor: "text-emerald-400",
|
|
32
27
|
tools: [
|
|
33
28
|
{ path: "/schema",
|
|
34
29
|
name: "Collections",
|
|
@@ -58,8 +53,6 @@ icon: "Activity" }
|
|
|
58
53
|
},
|
|
59
54
|
{
|
|
60
55
|
label: "Compute",
|
|
61
|
-
dotColor: "bg-blue-400",
|
|
62
|
-
iconColor: "text-blue-400",
|
|
63
56
|
tools: [
|
|
64
57
|
{ path: "/js",
|
|
65
58
|
name: "JS Console",
|
|
@@ -73,8 +66,6 @@ icon: "Clock" }
|
|
|
73
66
|
},
|
|
74
67
|
{
|
|
75
68
|
label: "API",
|
|
76
|
-
dotColor: "bg-violet-400",
|
|
77
|
-
iconColor: "text-violet-400",
|
|
78
69
|
tools: [
|
|
79
70
|
{ path: "/api",
|
|
80
71
|
name: "API Explorer",
|
|
@@ -84,8 +75,6 @@ icon: "BookOpen" }
|
|
|
84
75
|
},
|
|
85
76
|
{
|
|
86
77
|
label: "Storage",
|
|
87
|
-
dotColor: "bg-amber-400",
|
|
88
|
-
iconColor: "text-amber-400",
|
|
89
78
|
tools: [
|
|
90
79
|
{ path: "/storage",
|
|
91
80
|
name: "Storage",
|
|
@@ -95,8 +84,6 @@ icon: "HardDrive" }
|
|
|
95
84
|
},
|
|
96
85
|
{
|
|
97
86
|
label: "Access Control",
|
|
98
|
-
dotColor: "bg-rose-400",
|
|
99
|
-
iconColor: "text-rose-400",
|
|
100
87
|
tools: [
|
|
101
88
|
{ path: "/users",
|
|
102
89
|
name: "Users",
|
|
@@ -105,13 +92,49 @@ icon: "group" },
|
|
|
105
92
|
{ path: "/roles",
|
|
106
93
|
name: "Roles",
|
|
107
94
|
description: "Create and configure fine-grained access permissions",
|
|
108
|
-
icon: "ShieldEllipsis" }
|
|
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" }
|
|
109
100
|
]
|
|
110
101
|
}
|
|
111
102
|
];
|
|
112
103
|
|
|
113
104
|
/* ═══════════════════════════════════════════════════════════════ */
|
|
114
105
|
|
|
106
|
+
const COLLAPSED_STORAGE_KEY = "rebase-studio-home-collapsed";
|
|
107
|
+
|
|
108
|
+
function useStudioCollapsedGroups(groupNames: string[]) {
|
|
109
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(() => {
|
|
110
|
+
try {
|
|
111
|
+
const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);
|
|
112
|
+
return stored ? new Set(JSON.parse(stored)) : new Set<string>();
|
|
113
|
+
} catch {
|
|
114
|
+
return new Set<string>();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const isGroupCollapsed = (name: string) => collapsed.has(name);
|
|
119
|
+
|
|
120
|
+
const toggleGroupCollapsed = (name: string) => {
|
|
121
|
+
setCollapsed(prev => {
|
|
122
|
+
const next = new Set(prev);
|
|
123
|
+
if (next.has(name)) {
|
|
124
|
+
next.delete(name);
|
|
125
|
+
} else {
|
|
126
|
+
next.add(name);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));
|
|
130
|
+
} catch { /* noop */ }
|
|
131
|
+
return next;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return { isGroupCollapsed, toggleGroupCollapsed };
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
export function StudioHomePage({
|
|
116
139
|
additionalActions,
|
|
117
140
|
additionalChildrenStart,
|
|
@@ -139,8 +162,20 @@ export function StudioHomePage({
|
|
|
139
162
|
|
|
140
163
|
const pluginActions = useSlot("home.actions", sectionProps);
|
|
141
164
|
|
|
165
|
+
const filteredSections = useMemo(
|
|
166
|
+
() => SECTIONS.filter(s => !hiddenGroups?.includes(s.label)),
|
|
167
|
+
[hiddenGroups]
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const groupNames = useMemo(
|
|
171
|
+
() => filteredSections.map(s => s.label),
|
|
172
|
+
[filteredSections]
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups(groupNames);
|
|
176
|
+
|
|
142
177
|
return (
|
|
143
|
-
<div ref={containerRef} className="py-2 overflow-auto h-full w-full">
|
|
178
|
+
<div ref={containerRef} className="py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800">
|
|
144
179
|
<Container maxWidth="6xl">
|
|
145
180
|
<div className="mb-4 flex flex-col gap-2">
|
|
146
181
|
<SchemaDriftBanner />
|
|
@@ -156,77 +191,95 @@ export function StudioHomePage({
|
|
|
156
191
|
{additionalChildrenStart}
|
|
157
192
|
|
|
158
193
|
{/* ── Tool sections ── */}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
{filteredSections.map((section) => {
|
|
195
|
+
const sectionCollapsed = isGroupCollapsed(section.label);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div key={section.label} className="my-10">
|
|
199
|
+
<ExpandablePanel
|
|
200
|
+
invisible
|
|
201
|
+
expanded={!sectionCollapsed}
|
|
202
|
+
onExpandedChange={(open) => {
|
|
203
|
+
if (open !== !sectionCollapsed) {
|
|
204
|
+
toggleGroupCollapsed(section.label);
|
|
205
|
+
}
|
|
206
|
+
}}
|
|
207
|
+
className="mt-6"
|
|
208
|
+
titleClassName={cls(
|
|
209
|
+
"min-h-0 p-0 border-none",
|
|
210
|
+
"rounded flex items-center justify-between w-full",
|
|
211
|
+
"hover:bg-transparent",
|
|
212
|
+
"cursor-pointer select-none",
|
|
213
|
+
sectionCollapsed && "bg-surface-100 dark:bg-surface-900/50"
|
|
214
|
+
)}
|
|
215
|
+
innerClassName="mt-4 pt-0"
|
|
216
|
+
title={
|
|
217
|
+
<Typography
|
|
218
|
+
variant="caption"
|
|
219
|
+
component="h2"
|
|
220
|
+
color="secondary"
|
|
184
221
|
className={cls(
|
|
185
|
-
"
|
|
186
|
-
"
|
|
222
|
+
"px-4 py-1 rounded",
|
|
223
|
+
"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"
|
|
187
224
|
)}
|
|
188
225
|
>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
226
|
+
{section.label}
|
|
227
|
+
</Typography>
|
|
228
|
+
}
|
|
229
|
+
>
|
|
230
|
+
<div className="mt-4 pt-0">
|
|
231
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
232
|
+
{section.tools.map((tool) => (
|
|
233
|
+
<Card
|
|
234
|
+
key={tool.path}
|
|
235
|
+
onClick={() => {
|
|
236
|
+
navigate(tool.path);
|
|
237
|
+
context.analyticsController?.onAnalyticsEvent?.(
|
|
238
|
+
"home_navigate_to_view",
|
|
239
|
+
{ path: tool.path }
|
|
240
|
+
);
|
|
241
|
+
}}
|
|
242
|
+
className={cls(
|
|
243
|
+
"group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out",
|
|
244
|
+
"hover:bg-primary/5 dark:hover:bg-primary/5"
|
|
245
|
+
)}
|
|
246
|
+
>
|
|
247
|
+
<div className="flex flex-col h-full">
|
|
248
|
+
{/* Header: icon + title */}
|
|
249
|
+
<div className="flex items-center w-full justify-between mb-1">
|
|
250
|
+
<div className="flex items-center gap-3">
|
|
251
|
+
<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">
|
|
252
|
+
<IconForView
|
|
253
|
+
collectionOrView={{ slug: tool.path, name: tool.name, icon: tool.icon }}
|
|
254
|
+
size="small"
|
|
255
|
+
/>
|
|
256
|
+
</div>
|
|
257
|
+
<Typography variant="subtitle1" component="h2">
|
|
258
|
+
{tool.name}
|
|
259
|
+
</Typography>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
200
262
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<Typography variant="caption" color="secondary" component="div">
|
|
213
|
-
{tool.description}
|
|
214
|
-
</Typography>
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
{/* Arrow */}
|
|
218
|
-
<div style={{ alignSelf: "flex-end" }}>
|
|
219
|
-
<div className="p-2">
|
|
220
|
-
<ArrowRightIcon className="text-primary" size={iconSize.small}/>
|
|
263
|
+
{/* Description indented to align with title */}
|
|
264
|
+
<div className="pl-8">
|
|
265
|
+
{tool.description && (
|
|
266
|
+
<Typography variant="caption" color="secondary" component="div">
|
|
267
|
+
{tool.description}
|
|
268
|
+
</Typography>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* Spacer */}
|
|
273
|
+
<div className="grow"/>
|
|
221
274
|
</div>
|
|
222
|
-
</
|
|
223
|
-
|
|
224
|
-
</
|
|
225
|
-
|
|
226
|
-
</
|
|
227
|
-
</
|
|
228
|
-
)
|
|
229
|
-
|
|
275
|
+
</Card>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</ExpandablePanel>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
})}
|
|
230
283
|
|
|
231
284
|
{/* ── SDK Quick Start ── */}
|
|
232
285
|
<div className="mt-10 mb-6">
|
|
@@ -235,7 +288,10 @@ icon: tool.icon }}
|
|
|
235
288
|
variant="caption"
|
|
236
289
|
component="h2"
|
|
237
290
|
color="secondary"
|
|
238
|
-
className=
|
|
291
|
+
className={cls(
|
|
292
|
+
"px-4 py-1 rounded",
|
|
293
|
+
"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"
|
|
294
|
+
)}
|
|
239
295
|
>
|
|
240
296
|
Quick Start
|
|
241
297
|
</Typography>
|
|
@@ -281,7 +337,10 @@ icon: tool.icon }}
|
|
|
281
337
|
variant="caption"
|
|
282
338
|
component="h2"
|
|
283
339
|
color="secondary"
|
|
284
|
-
className=
|
|
340
|
+
className={cls(
|
|
341
|
+
"px-4 py-1 rounded",
|
|
342
|
+
"font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"
|
|
343
|
+
)}
|
|
285
344
|
>
|
|
286
345
|
{s.title}
|
|
287
346
|
</Typography>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityCollection, Property, StringProperty, NumberProperty, ArrayProperty, TableColumnInfo, TableMetadata, SecurityOperation, SecurityRule } from "@rebasepro/types";
|
|
1
|
+
import type { EntityCollection, PostgresProperties, Property, StringProperty, NumberProperty, ArrayProperty, TableColumnInfo, TableMetadata, SecurityOperation, SecurityRule } from "@rebasepro/types";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Maps a PostgreSQL column data type to a Rebase property type.
|
|
@@ -301,7 +301,7 @@ export function buildCollectionFromTableMetadata(
|
|
|
301
301
|
name: prettifiedName,
|
|
302
302
|
slug: tableName,
|
|
303
303
|
table: tableName,
|
|
304
|
-
properties,
|
|
304
|
+
properties: properties as PostgresProperties,
|
|
305
305
|
propertiesOrder,
|
|
306
306
|
...(relations.length > 0 ? { relations } : {}),
|
|
307
307
|
...(securityRules.length > 0 ? { securityRules } : {})
|