@rebasepro/studio 0.6.1 → 0.7.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-DSucz6wV.js} +4 -4
- package/dist/{JSEditor-BhAbEjCP.js.map → JSEditor-DSucz6wV.js.map} +1 -1
- 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/components/ApiKeys/ApiKeysView.d.ts +2 -0
- package/dist/index.es.js +37 -18
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1154 -305
- package/dist/index.umd.js.map +1 -1
- package/package.json +17 -17
- 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/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/StudioHomePage.tsx +5 -1
- package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
- package/dist/RLSEditor-DpF1u9EC.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as MonacoEditor } from "./MonacoEditor-COZqrIJ1.js";
|
|
2
2
|
import { ErrorView, useRebaseContext, useSnackbarController, useStudioCollectionRegistry, useTranslation } from "@rebasepro/core";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, HelpCircleIcon, IconButton, KeyIcon, MultiSelect, MultiSelectItem, Paper, RefreshCwIcon, ResizablePanels, Select, SelectItem, ShieldIcon, Tab, Tabs, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
4
|
+
import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, DatabaseIcon, Dialog, DialogActions, DialogContent, DialogTitle, HelpCircleIcon, IconButton, KeyIcon, Link2Icon, LockIcon, MultiSelect, MultiSelectItem, Paper, RefreshCwIcon, ResizablePanels, Select, SelectItem, ShieldIcon, Tab, Tabs, TextField, Tooltip, Trash2Icon, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { isPostgresCollection } from "@rebasepro/types";
|
|
7
|
+
import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS } from "@rebasepro/common";
|
|
7
8
|
//#region src/components/RLSEditor/PolicyEditor.tsx
|
|
8
9
|
var COMMAND_OPTIONS = [
|
|
9
10
|
"ALL",
|
|
@@ -573,6 +574,12 @@ var PolicyEditor = ({ policy, schema, table, onSave, onCancel }) => {
|
|
|
573
574
|
};
|
|
574
575
|
//#endregion
|
|
575
576
|
//#region src/components/RLSEditor/RLSEditor.tsx
|
|
577
|
+
function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
|
|
578
|
+
if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
|
|
579
|
+
if (isMappedToCollection) return "collection";
|
|
580
|
+
if (junctionTableNames.has(tableName)) return "junction";
|
|
581
|
+
return "other";
|
|
582
|
+
}
|
|
576
583
|
/**
|
|
577
584
|
* Validates and double-quotes a SQL identifier to prevent injection.
|
|
578
585
|
* Only allows safe Postgres identifiers (letters, digits, underscores).
|
|
@@ -582,6 +589,81 @@ function sanitizeSqlIdentifier(name) {
|
|
|
582
589
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`Invalid SQL identifier: "${name}". Only letters, digits, and underscores are allowed.`);
|
|
583
590
|
return `"${name}"`;
|
|
584
591
|
}
|
|
592
|
+
function SidebarSection({ title, icon, expanded, onToggle, count, children }) {
|
|
593
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
594
|
+
className: "mb-2",
|
|
595
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
596
|
+
className: "flex items-center p-1.5 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
|
|
597
|
+
onClick: onToggle,
|
|
598
|
+
children: [
|
|
599
|
+
/* @__PURE__ */ jsx("svg", {
|
|
600
|
+
className: cls("w-3 h-3 mr-1.5 transition-transform text-text-disabled dark:text-text-disabled-dark", expanded ? "rotate-90" : ""),
|
|
601
|
+
fill: "currentColor",
|
|
602
|
+
viewBox: "0 0 20 20",
|
|
603
|
+
children: /* @__PURE__ */ jsx("path", { d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" })
|
|
604
|
+
}),
|
|
605
|
+
icon,
|
|
606
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
607
|
+
variant: "body2",
|
|
608
|
+
className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow ml-1.5",
|
|
609
|
+
children: title
|
|
610
|
+
}),
|
|
611
|
+
count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
612
|
+
className: "text-[10px] text-text-disabled dark:text-text-disabled-dark font-medium tabular-nums mr-1",
|
|
613
|
+
children: count
|
|
614
|
+
})
|
|
615
|
+
]
|
|
616
|
+
}), expanded && /* @__PURE__ */ jsx("div", {
|
|
617
|
+
className: "ml-3 mt-0.5 space-y-0.5",
|
|
618
|
+
children
|
|
619
|
+
})]
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
function SidebarTableRow({ table, isSelected, onSelect, badge, dimmed, t }) {
|
|
623
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
624
|
+
onClick: onSelect,
|
|
625
|
+
className: cls("flex items-center p-1 cursor-pointer rounded transition-colors group relative", isSelected ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light" : "hover:bg-surface-100 dark:hover:bg-surface-900 text-text-secondary dark:text-text-secondary-dark", dimmed && !isSelected && "opacity-60"),
|
|
626
|
+
children: [
|
|
627
|
+
/* @__PURE__ */ jsx("svg", {
|
|
628
|
+
className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
|
|
629
|
+
fill: "none",
|
|
630
|
+
stroke: "currentColor",
|
|
631
|
+
viewBox: "0 0 24 24",
|
|
632
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
633
|
+
strokeLinecap: "round",
|
|
634
|
+
strokeLinejoin: "round",
|
|
635
|
+
strokeWidth: 2,
|
|
636
|
+
d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
637
|
+
})
|
|
638
|
+
}),
|
|
639
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
640
|
+
variant: "body2",
|
|
641
|
+
className: "text-xs truncate flex-1 min-w-0",
|
|
642
|
+
children: table.tableName
|
|
643
|
+
}),
|
|
644
|
+
/* @__PURE__ */ jsxs("div", {
|
|
645
|
+
className: "flex items-center gap-1.5 shrink-0 ml-2",
|
|
646
|
+
children: [
|
|
647
|
+
badge && /* @__PURE__ */ jsx("span", {
|
|
648
|
+
className: "text-[9px] uppercase tracking-wider font-semibold text-text-disabled dark:text-text-disabled-dark bg-surface-200 dark:bg-surface-800 rounded px-1 py-px",
|
|
649
|
+
children: badge
|
|
650
|
+
}),
|
|
651
|
+
table.rlsEnabled ? /* @__PURE__ */ jsx(Tooltip, {
|
|
652
|
+
title: t("studio_rls_enabled"),
|
|
653
|
+
children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
|
|
654
|
+
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
655
|
+
title: t("studio_rls_disabled"),
|
|
656
|
+
children: /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
|
|
657
|
+
}),
|
|
658
|
+
/* @__PURE__ */ jsx("span", {
|
|
659
|
+
className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
|
|
660
|
+
children: table.policies.length
|
|
661
|
+
})
|
|
662
|
+
]
|
|
663
|
+
})
|
|
664
|
+
]
|
|
665
|
+
});
|
|
666
|
+
}
|
|
585
667
|
var RLSEditor = ({ apiUrl = "" }) => {
|
|
586
668
|
const { databaseAdmin } = useRebaseContext();
|
|
587
669
|
const snackbarController = useSnackbarController();
|
|
@@ -592,6 +674,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
592
674
|
const [tables, setTables] = useState([]);
|
|
593
675
|
const [selectedTable, setSelectedTable] = useState(null);
|
|
594
676
|
const [activeTab, setActiveTab] = useState(0);
|
|
677
|
+
const [junctionTableNames, setJunctionTableNames] = useState(/* @__PURE__ */ new Set());
|
|
595
678
|
const [editingPolicy, setEditingPolicy] = useState(null);
|
|
596
679
|
const [sidebarSize, setSidebarSize] = useState(() => {
|
|
597
680
|
try {
|
|
@@ -601,7 +684,11 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
601
684
|
return 20;
|
|
602
685
|
}
|
|
603
686
|
});
|
|
604
|
-
const [
|
|
687
|
+
const [expandedSections, setExpandedSections] = useState({
|
|
688
|
+
collection: true,
|
|
689
|
+
other: true,
|
|
690
|
+
internal: false
|
|
691
|
+
});
|
|
605
692
|
const [sidebarTab, setSidebarTab] = useState("tables");
|
|
606
693
|
useEffect(() => {
|
|
607
694
|
try {
|
|
@@ -683,7 +770,15 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
683
770
|
});
|
|
684
771
|
const sortedTables = Object.values(tableMap).sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
685
772
|
setTables(sortedTables);
|
|
686
|
-
|
|
773
|
+
try {
|
|
774
|
+
const jRows = extractRows(await databaseAdmin.executeSql(JUNCTION_TABLES_SQL));
|
|
775
|
+
setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
|
|
776
|
+
} catch {}
|
|
777
|
+
if (sortedTables.length > 0 && !selectedTable) {
|
|
778
|
+
const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
|
|
779
|
+
if (firstUserTable) setSelectedTable(`${firstUserTable.schemaName}.${firstUserTable.tableName}`);
|
|
780
|
+
else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
|
|
781
|
+
}
|
|
687
782
|
} catch (e) {
|
|
688
783
|
console.error("RLS fetch error:", e);
|
|
689
784
|
setError("Failed to fetch RLS policies: " + (e instanceof Error ? e.message : String(e)));
|
|
@@ -701,18 +796,38 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
701
796
|
if (!selectedTable) return null;
|
|
702
797
|
return tables.find((t) => `${t.schemaName}.${t.tableName}` === selectedTable) || null;
|
|
703
798
|
}, [selectedTable, tables]);
|
|
704
|
-
|
|
705
|
-
|
|
799
|
+
/** Categorize tables into 4 buckets for the sidebar. */
|
|
800
|
+
const categorizedTables = useMemo(() => {
|
|
801
|
+
const groups = {
|
|
802
|
+
collection: [],
|
|
803
|
+
junction: [],
|
|
804
|
+
internal: [],
|
|
805
|
+
other: []
|
|
806
|
+
};
|
|
706
807
|
tables.forEach((table) => {
|
|
707
|
-
|
|
708
|
-
groups[table.schemaName].push(table);
|
|
808
|
+
const isMapped = !!collectionRegistry.collections?.find((c) => c.id === table.tableName || c.path === table.tableName || c.table === table.tableName || c.slug === table.tableName || c.collectionId === table.tableName);
|
|
809
|
+
groups[classifyTableClient(table.tableName, table.schemaName, junctionTableNames, isMapped)].push(table);
|
|
709
810
|
});
|
|
710
811
|
return groups;
|
|
711
|
-
}, [
|
|
812
|
+
}, [
|
|
813
|
+
tables,
|
|
814
|
+
junctionTableNames,
|
|
815
|
+
collectionRegistry.collections
|
|
816
|
+
]);
|
|
712
817
|
const activeCollection = useMemo(() => {
|
|
713
818
|
if (!activeTableData) return null;
|
|
714
819
|
return collectionRegistry.collections?.find((c) => c.id === activeTableData.tableName || c.path === activeTableData.tableName || c.table === activeTableData.tableName || c.slug === activeTableData.tableName || c.collectionId === activeTableData.tableName) || null;
|
|
715
820
|
}, [activeTableData, collectionRegistry.collections]);
|
|
821
|
+
/** The category of the currently selected table. */
|
|
822
|
+
const activeTableCategory = useMemo(() => {
|
|
823
|
+
if (!activeTableData) return null;
|
|
824
|
+
const isMapped = !!activeCollection;
|
|
825
|
+
return classifyTableClient(activeTableData.tableName, activeTableData.schemaName, junctionTableNames, isMapped);
|
|
826
|
+
}, [
|
|
827
|
+
activeTableData,
|
|
828
|
+
activeCollection,
|
|
829
|
+
junctionTableNames
|
|
830
|
+
]);
|
|
716
831
|
const mergedPolicies = useMemo(() => {
|
|
717
832
|
if (!activeTableData) return [];
|
|
718
833
|
const policiesMap = {};
|
|
@@ -810,74 +925,78 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
810
925
|
children: isLoading && tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
811
926
|
className: "flex justify-center p-4",
|
|
812
927
|
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
813
|
-
}) :
|
|
928
|
+
}) : tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
814
929
|
className: "p-4 text-center",
|
|
815
930
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
816
931
|
variant: "caption",
|
|
817
932
|
className: "text-text-disabled dark:text-text-disabled-dark italic",
|
|
818
933
|
children: t("studio_rls_no_tables")
|
|
819
934
|
})
|
|
820
|
-
}) :
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
935
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
936
|
+
categorizedTables.collection.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
|
|
937
|
+
title: "Schema Collections",
|
|
938
|
+
icon: /* @__PURE__ */ jsx(DatabaseIcon, {
|
|
939
|
+
size: 12,
|
|
940
|
+
className: "text-primary dark:text-primary-light"
|
|
941
|
+
}),
|
|
942
|
+
expanded: expandedSections.collection ?? true,
|
|
943
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
825
944
|
...prev,
|
|
826
|
-
|
|
945
|
+
collection: !(prev.collection ?? true)
|
|
827
946
|
})),
|
|
828
|
-
children:
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
})
|
|
879
|
-
})
|
|
880
|
-
}
|
|
947
|
+
children: categorizedTables.collection.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
948
|
+
table,
|
|
949
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
950
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
951
|
+
t
|
|
952
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
953
|
+
}),
|
|
954
|
+
(categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ jsxs(SidebarSection, {
|
|
955
|
+
title: "Other Tables",
|
|
956
|
+
icon: /* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
957
|
+
size: 12,
|
|
958
|
+
className: "text-yellow-500 dark:text-yellow-400"
|
|
959
|
+
}),
|
|
960
|
+
expanded: expandedSections.other ?? true,
|
|
961
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
962
|
+
...prev,
|
|
963
|
+
other: !(prev.other ?? true)
|
|
964
|
+
})),
|
|
965
|
+
children: [categorizedTables.other.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
966
|
+
table,
|
|
967
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
968
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
969
|
+
t
|
|
970
|
+
}, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
971
|
+
table,
|
|
972
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
973
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
974
|
+
badge: "Junction",
|
|
975
|
+
dimmed: true,
|
|
976
|
+
t
|
|
977
|
+
}, `${table.schemaName}.${table.tableName}`))]
|
|
978
|
+
}),
|
|
979
|
+
categorizedTables.internal.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
|
|
980
|
+
title: "Rebase Internal",
|
|
981
|
+
icon: /* @__PURE__ */ jsx(LockIcon, {
|
|
982
|
+
size: 12,
|
|
983
|
+
className: "text-text-disabled dark:text-text-disabled-dark"
|
|
984
|
+
}),
|
|
985
|
+
expanded: expandedSections.internal ?? false,
|
|
986
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
987
|
+
...prev,
|
|
988
|
+
internal: !(prev.internal ?? false)
|
|
989
|
+
})),
|
|
990
|
+
count: categorizedTables.internal.length,
|
|
991
|
+
children: categorizedTables.internal.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
992
|
+
table,
|
|
993
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
994
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
995
|
+
dimmed: true,
|
|
996
|
+
t
|
|
997
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
998
|
+
})
|
|
999
|
+
] })
|
|
881
1000
|
})]
|
|
882
1001
|
}), sidebarTab === "info" && /* @__PURE__ */ jsxs("div", {
|
|
883
1002
|
className: "flex flex-col h-full",
|
|
@@ -1040,10 +1159,11 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1040
1159
|
size: "small",
|
|
1041
1160
|
onClick: async () => {
|
|
1042
1161
|
const table = activeTableData.tableName;
|
|
1162
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
1043
1163
|
const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
|
|
1044
1164
|
if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
|
|
1045
1165
|
try {
|
|
1046
|
-
await databaseAdmin.executeSql(`ALTER TABLE ${
|
|
1166
|
+
await databaseAdmin.executeSql(`ALTER TABLE ${qualifiedTable} ${action} ROW LEVEL SECURITY`);
|
|
1047
1167
|
snackbarController.open({
|
|
1048
1168
|
type: "success",
|
|
1049
1169
|
message: `RLS ${action.toLowerCase()}d on ${table}`
|
|
@@ -1070,7 +1190,6 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1070
1190
|
/* @__PURE__ */ jsx(Button, {
|
|
1071
1191
|
size: "small",
|
|
1072
1192
|
color: "primary",
|
|
1073
|
-
disabled: !activeCollection,
|
|
1074
1193
|
onClick: () => setEditingPolicy("new"),
|
|
1075
1194
|
children: t("studio_rls_create_policy")
|
|
1076
1195
|
})
|
|
@@ -1111,31 +1230,57 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1111
1230
|
schema: activeTableData.schemaName,
|
|
1112
1231
|
table: activeTableData.tableName,
|
|
1113
1232
|
onSave: async (newPolicy) => {
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1233
|
+
if (activeCollection) {
|
|
1234
|
+
const rule = {
|
|
1235
|
+
name: newPolicy.policyname,
|
|
1236
|
+
operation: newPolicy.cmd?.toLowerCase(),
|
|
1237
|
+
mode: newPolicy.permissive?.toLowerCase(),
|
|
1238
|
+
using: newPolicy.qual || void 0,
|
|
1239
|
+
withCheck: newPolicy.with_check || void 0,
|
|
1240
|
+
roles: newPolicy.roles
|
|
1241
|
+
};
|
|
1242
|
+
const existingRules = (isPostgresCollection(activeCollection) ? activeCollection.securityRules : void 0) || [];
|
|
1243
|
+
let newRules;
|
|
1244
|
+
if (editingPolicy === "new") newRules = [...existingRules, rule];
|
|
1245
|
+
else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
|
|
1246
|
+
try {
|
|
1247
|
+
if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
1248
|
+
method: "POST",
|
|
1249
|
+
headers: { "Content-Type": "application/json" },
|
|
1250
|
+
body: JSON.stringify({
|
|
1251
|
+
collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
|
|
1252
|
+
collectionData: { securityRules: newRules }
|
|
1253
|
+
})
|
|
1254
|
+
})).ok) throw new Error("Failed to save policy");
|
|
1255
|
+
snackbarController.open({
|
|
1256
|
+
type: "success",
|
|
1257
|
+
message: "Policy saved successfully"
|
|
1258
|
+
});
|
|
1259
|
+
setEditingPolicy(null);
|
|
1260
|
+
fetchRLSData();
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
snackbarController.open({
|
|
1263
|
+
type: "error",
|
|
1264
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
} else try {
|
|
1268
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
|
|
1269
|
+
const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
|
|
1270
|
+
const cmd = newPolicy.cmd || "ALL";
|
|
1271
|
+
const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
|
|
1272
|
+
const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
|
|
1273
|
+
if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
|
|
1274
|
+
let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
|
|
1275
|
+
sql += ` AS ${permissive}`;
|
|
1276
|
+
sql += ` FOR ${cmd}`;
|
|
1277
|
+
sql += ` TO ${roles}`;
|
|
1278
|
+
if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
|
|
1279
|
+
if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
|
|
1280
|
+
await databaseAdmin.executeSql(sql);
|
|
1136
1281
|
snackbarController.open({
|
|
1137
1282
|
type: "success",
|
|
1138
|
-
message:
|
|
1283
|
+
message: `Policy "${newPolicy.policyname}" applied directly via SQL`
|
|
1139
1284
|
});
|
|
1140
1285
|
setEditingPolicy(null);
|
|
1141
1286
|
fetchRLSData();
|
|
@@ -1154,17 +1299,59 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1154
1299
|
children: /* @__PURE__ */ jsxs("div", {
|
|
1155
1300
|
className: "max-w-4xl mx-auto flex flex-col gap-6",
|
|
1156
1301
|
children: [
|
|
1157
|
-
activeTableData &&
|
|
1302
|
+
activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
|
|
1303
|
+
color: "info",
|
|
1304
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1305
|
+
className: "flex items-start gap-2",
|
|
1306
|
+
children: [/* @__PURE__ */ jsx(LockIcon, {
|
|
1307
|
+
size: 16,
|
|
1308
|
+
className: "shrink-0 mt-0.5"
|
|
1309
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1310
|
+
variant: "body2",
|
|
1311
|
+
className: "mb-1 font-semibold",
|
|
1312
|
+
children: "Rebase System Table"
|
|
1313
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1314
|
+
variant: "caption",
|
|
1315
|
+
className: "opacity-80",
|
|
1316
|
+
children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
|
|
1317
|
+
})] })]
|
|
1318
|
+
})
|
|
1319
|
+
}),
|
|
1320
|
+
activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ jsx(Alert, {
|
|
1321
|
+
color: "info",
|
|
1322
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1323
|
+
className: "flex items-start gap-2",
|
|
1324
|
+
children: [/* @__PURE__ */ jsx(Link2Icon, {
|
|
1325
|
+
size: 16,
|
|
1326
|
+
className: "shrink-0 mt-0.5"
|
|
1327
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1328
|
+
variant: "body2",
|
|
1329
|
+
className: "mb-1 font-semibold",
|
|
1330
|
+
children: "Junction Table"
|
|
1331
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1332
|
+
variant: "caption",
|
|
1333
|
+
className: "opacity-80",
|
|
1334
|
+
children: "This is an auto-generated junction table for a many-to-many relation. Its access is typically managed through the related collections' policies. You can still add RLS policies directly if needed."
|
|
1335
|
+
})] })]
|
|
1336
|
+
})
|
|
1337
|
+
}),
|
|
1338
|
+
activeTableData && activeTableCategory === "other" && /* @__PURE__ */ jsx(Alert, {
|
|
1158
1339
|
color: "warning",
|
|
1159
|
-
children:
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1340
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1341
|
+
className: "flex items-start gap-2",
|
|
1342
|
+
children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1343
|
+
size: 16,
|
|
1344
|
+
className: "shrink-0 mt-0.5"
|
|
1345
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1346
|
+
variant: "body2",
|
|
1347
|
+
className: "mb-1 font-semibold",
|
|
1348
|
+
children: "Unmapped Table"
|
|
1349
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1350
|
+
variant: "caption",
|
|
1351
|
+
className: "opacity-80",
|
|
1352
|
+
children: "This table exists in the database but isn't mapped to a collection definition. Import it into a Schema configuration file to manage security policies visually."
|
|
1353
|
+
})] })]
|
|
1354
|
+
})
|
|
1168
1355
|
}),
|
|
1169
1356
|
activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
|
|
1170
1357
|
className: cls("p-4 sm:p-5 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-900/50 rounded-lg flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between", defaultBorderMixin),
|
|
@@ -1278,7 +1465,6 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1278
1465
|
variant: "text",
|
|
1279
1466
|
color: "primary",
|
|
1280
1467
|
onClick: () => setEditingPolicy(policy),
|
|
1281
|
-
disabled: !activeCollection,
|
|
1282
1468
|
children: t("studio_rls_edit")
|
|
1283
1469
|
}),
|
|
1284
1470
|
policy.status !== "code_only" && /* @__PURE__ */ jsx(Tooltip, {
|
|
@@ -1288,9 +1474,10 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1288
1474
|
size: "small",
|
|
1289
1475
|
onClick: async () => {
|
|
1290
1476
|
const table = activeTableData.tableName;
|
|
1477
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
1291
1478
|
if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
|
|
1292
1479
|
try {
|
|
1293
|
-
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${
|
|
1480
|
+
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${qualifiedTable}`);
|
|
1294
1481
|
snackbarController.open({
|
|
1295
1482
|
type: "success",
|
|
1296
1483
|
message: `Policy "${policy.policyname}" dropped`
|
|
@@ -1327,7 +1514,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1327
1514
|
className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
|
|
1328
1515
|
children: "RLS is enabled on this table but no policies exist. All access is denied by default (Postgres deny-all). Create a policy to allow specific access."
|
|
1329
1516
|
}),
|
|
1330
|
-
|
|
1517
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1331
1518
|
size: "small",
|
|
1332
1519
|
variant: "filled",
|
|
1333
1520
|
color: "primary",
|
|
@@ -1366,4 +1553,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1366
1553
|
//#endregion
|
|
1367
1554
|
export { RLSEditor };
|
|
1368
1555
|
|
|
1369
|
-
//# sourceMappingURL=RLSEditor-
|
|
1556
|
+
//# sourceMappingURL=RLSEditor-BM64laoW.js.map
|