@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as MonacoEditor } from "./MonacoEditor-D0_QljcX.js";
|
|
2
|
-
import { ErrorView, useApiBase, useApiConfig, useRebaseContext, useSnackbarController, useStudioCapabilities, useStudioCollectionRegistry, useTranslation } from "@rebasepro/app";
|
|
2
|
+
import { ConfirmationDialog, ErrorView, useApiBase, useApiConfig, useRebaseContext, useSnackbarController, useStudioCapabilities, useStudioCollectionRegistry, useStudioSchemaEditing, useTranslation } from "@rebasepro/app";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
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";
|
|
@@ -711,6 +711,55 @@ async function saveSecurityRulesToCodebase(options) {
|
|
|
711
711
|
throw new Error(message ?? `Failed to save policy (HTTP ${response.status}).`);
|
|
712
712
|
}
|
|
713
713
|
//#endregion
|
|
714
|
+
//#region src/components/RLSEditor/saveRules.ts
|
|
715
|
+
/**
|
|
716
|
+
* Where a collection's security rules go when the RLS editor saves them.
|
|
717
|
+
*
|
|
718
|
+
* There are two doors onto one file. The collection editor's save is planned,
|
|
719
|
+
* shown as SQL, confirmed, and applied to the database. The RLS editor's save
|
|
720
|
+
* POSTed the rules to `/schema-editor/collection/save` and stopped there: the
|
|
721
|
+
* source changed, nothing said what the change implied, and the database was
|
|
722
|
+
* left to a later `db push`. A policy declared and not enforced is the worst
|
|
723
|
+
* outcome this editor can produce, and it was the default one.
|
|
724
|
+
*
|
|
725
|
+
* So a mapped-table write goes through the admin's plan/apply flow when there
|
|
726
|
+
* is one. Studio cannot call `useLiveSchemaEditing` — `@rebasepro/cms` is a
|
|
727
|
+
* *peer* of `@rebasepro/studio`, which has to run without it — so it arrives
|
|
728
|
+
* over the Studio bridge as {@link StudioSchemaEditing}, and `available` is
|
|
729
|
+
* false in the hosted console, where there is no source to edit and the direct
|
|
730
|
+
* write is the only thing there is.
|
|
731
|
+
*
|
|
732
|
+
* A separate module from the component for the same reason `saveSecurityRules`
|
|
733
|
+
* is: the decision can be asserted without a database and a rendered editor.
|
|
734
|
+
*/
|
|
735
|
+
async function saveRules(schemaEditing, transport, collectionId, securityRules) {
|
|
736
|
+
if (schemaEditing.available) {
|
|
737
|
+
await schemaEditing.updateCollection(collectionId, { securityRules });
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
await saveSecurityRulesToCodebase({
|
|
741
|
+
apiBase: transport.apiBase,
|
|
742
|
+
collectionId,
|
|
743
|
+
securityRules,
|
|
744
|
+
getAuthToken: transport.getAuthToken
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Was this rejection the person closing the plan dialog?
|
|
749
|
+
*
|
|
750
|
+
* Closing it is an answer, not a failure, and a caller that treats every
|
|
751
|
+
* rejection as an error reports someone's own choice back to them in red — as
|
|
752
|
+
* the collection editor once did, with "Error persisting collection: The schema
|
|
753
|
+
* change was not applied."
|
|
754
|
+
*
|
|
755
|
+
* Matched by name rather than `instanceof`: `SchemaChangeCancelled` is declared
|
|
756
|
+
* in `@rebasepro/cms`, which this package cannot import, and two copies of a
|
|
757
|
+
* class in one bundle make `instanceof` silently false anyway.
|
|
758
|
+
*/
|
|
759
|
+
function isCancellation(err) {
|
|
760
|
+
return err instanceof Error && err.name === "SchemaChangeCancelled";
|
|
761
|
+
}
|
|
762
|
+
//#endregion
|
|
714
763
|
//#region src/components/RLSEditor/RLSEditor.tsx
|
|
715
764
|
function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
|
|
716
765
|
if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
|
|
@@ -809,12 +858,85 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
809
858
|
const { codebase: hasCodebase } = useStudioCapabilities();
|
|
810
859
|
const apiConfig = useApiConfig();
|
|
811
860
|
const apiBase = useApiBase() ?? `${apiUrl.replace(/\/+$/, "")}/api`;
|
|
812
|
-
const
|
|
861
|
+
const schemaEditing = useStudioSchemaEditing();
|
|
862
|
+
/** Through the plan/apply dialog when there is one — see `saveRules`. */
|
|
863
|
+
const saveSecurityRules = useCallback((collectionId, securityRules) => saveRules(schemaEditing, {
|
|
813
864
|
apiBase,
|
|
814
|
-
collectionId,
|
|
815
|
-
securityRules,
|
|
816
865
|
getAuthToken: apiConfig?.getAuthToken
|
|
817
|
-
}
|
|
866
|
+
}, collectionId, securityRules), [
|
|
867
|
+
apiBase,
|
|
868
|
+
apiConfig,
|
|
869
|
+
schemaEditing
|
|
870
|
+
]);
|
|
871
|
+
/** Closing the plan dialog is an answer, not a failure — see `saveRules`. */
|
|
872
|
+
const reportSaveFailure = useCallback((e) => {
|
|
873
|
+
if (isCancellation(e)) return;
|
|
874
|
+
snackbarController.open({
|
|
875
|
+
type: "error",
|
|
876
|
+
message: e instanceof Error ? e.message : String(e)
|
|
877
|
+
});
|
|
878
|
+
}, [snackbarController]);
|
|
879
|
+
const [editingPolicy, setEditingPolicy] = useState(null);
|
|
880
|
+
/**
|
|
881
|
+
* The two destructive actions used `window.confirm`, which is the browser's
|
|
882
|
+
* dialog and not this app's: it cannot be styled, cannot be translated, and
|
|
883
|
+
* is suppressed outright in a sandboxed iframe — where the click then did
|
|
884
|
+
* the thing silently, with no confirmation at all.
|
|
885
|
+
*/
|
|
886
|
+
const [confirmToggleRls, setConfirmToggleRls] = useState(null);
|
|
887
|
+
const [confirmDropPolicy, setConfirmDropPolicy] = useState(null);
|
|
888
|
+
const [confirming, setConfirming] = useState(false);
|
|
889
|
+
const applyToggleRls = useCallback(async () => {
|
|
890
|
+
if (!confirmToggleRls) return;
|
|
891
|
+
const { table, schema, enabled } = confirmToggleRls;
|
|
892
|
+
const action = enabled ? "DISABLE" : "ENABLE";
|
|
893
|
+
setConfirming(true);
|
|
894
|
+
try {
|
|
895
|
+
await databaseAdmin.executeSql(`ALTER TABLE ${sanitizeSqlIdentifier(schema)}.${sanitizeSqlIdentifier(table)} ${action} ROW LEVEL SECURITY`);
|
|
896
|
+
snackbarController.open({
|
|
897
|
+
type: "success",
|
|
898
|
+
message: `RLS ${action.toLowerCase()}d on ${table}`
|
|
899
|
+
});
|
|
900
|
+
setConfirmToggleRls(null);
|
|
901
|
+
fetchRLSData();
|
|
902
|
+
} catch (e) {
|
|
903
|
+
snackbarController.open({
|
|
904
|
+
type: "error",
|
|
905
|
+
message: e instanceof Error ? e.message : String(e)
|
|
906
|
+
});
|
|
907
|
+
} finally {
|
|
908
|
+
setConfirming(false);
|
|
909
|
+
}
|
|
910
|
+
}, [
|
|
911
|
+
confirmToggleRls,
|
|
912
|
+
databaseAdmin,
|
|
913
|
+
snackbarController
|
|
914
|
+
]);
|
|
915
|
+
const applyDropPolicy = useCallback(async () => {
|
|
916
|
+
if (!confirmDropPolicy) return;
|
|
917
|
+
const { policyName, table, schema } = confirmDropPolicy;
|
|
918
|
+
setConfirming(true);
|
|
919
|
+
try {
|
|
920
|
+
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policyName)} ON ${sanitizeSqlIdentifier(schema)}.${sanitizeSqlIdentifier(table)}`);
|
|
921
|
+
snackbarController.open({
|
|
922
|
+
type: "success",
|
|
923
|
+
message: `Policy "${policyName}" dropped`
|
|
924
|
+
});
|
|
925
|
+
setConfirmDropPolicy(null);
|
|
926
|
+
fetchRLSData();
|
|
927
|
+
} catch (e) {
|
|
928
|
+
snackbarController.open({
|
|
929
|
+
type: "error",
|
|
930
|
+
message: e instanceof Error ? e.message : String(e)
|
|
931
|
+
});
|
|
932
|
+
} finally {
|
|
933
|
+
setConfirming(false);
|
|
934
|
+
}
|
|
935
|
+
}, [
|
|
936
|
+
confirmDropPolicy,
|
|
937
|
+
databaseAdmin,
|
|
938
|
+
snackbarController
|
|
939
|
+
]);
|
|
818
940
|
const { t } = useTranslation();
|
|
819
941
|
const [isLoading, setIsLoading] = useState(true);
|
|
820
942
|
const [error, setError] = useState(null);
|
|
@@ -822,7 +944,6 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
822
944
|
const [selectedTable, setSelectedTable] = useState(null);
|
|
823
945
|
const [activeTab, setActiveTab] = useState(0);
|
|
824
946
|
const [junctionTableNames, setJunctionTableNames] = useState(/* @__PURE__ */ new Set());
|
|
825
|
-
const [editingPolicy, setEditingPolicy] = useState(null);
|
|
826
947
|
/**
|
|
827
948
|
* Native PostgreSQL roles, for the policy editor's `TO` list.
|
|
828
949
|
*
|
|
@@ -1052,372 +1173,382 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1052
1173
|
})]
|
|
1053
1174
|
});
|
|
1054
1175
|
};
|
|
1055
|
-
return /* @__PURE__ */
|
|
1176
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1056
1177
|
className: "flex h-full w-full bg-white dark:bg-surface-950 overflow-hidden text-text-primary dark:text-text-primary-dark",
|
|
1057
|
-
children:
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
1085
|
-
children: t("studio_schema_tables")
|
|
1086
|
-
}), /* @__PURE__ */ jsx(IconButton, {
|
|
1087
|
-
size: "small",
|
|
1088
|
-
onClick: fetchRLSData,
|
|
1089
|
-
title: "Refresh",
|
|
1090
|
-
children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
|
|
1091
|
-
})]
|
|
1092
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1093
|
-
className: "flex-grow overflow-y-auto no-scrollbar p-1",
|
|
1094
|
-
children: isLoading && tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
1095
|
-
className: "flex justify-center p-4",
|
|
1096
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
1097
|
-
}) : tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
1098
|
-
className: "p-4 text-center",
|
|
1099
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
1178
|
+
children: [
|
|
1179
|
+
/* @__PURE__ */ jsx(ResizablePanels, {
|
|
1180
|
+
orientation: "horizontal",
|
|
1181
|
+
panelSizePercent: sidebarSize,
|
|
1182
|
+
onPanelSizeChange: setSidebarSize,
|
|
1183
|
+
minPanelSizePx: 220,
|
|
1184
|
+
firstPanel: /* @__PURE__ */ jsxs("div", {
|
|
1185
|
+
className: cls("flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r", defaultBorderMixin),
|
|
1186
|
+
children: [/* @__PURE__ */ jsxs(Tabs, {
|
|
1187
|
+
value: sidebarTab,
|
|
1188
|
+
onValueChange: (v) => setSidebarTab(v),
|
|
1189
|
+
variant: "boxy",
|
|
1190
|
+
className: "border-b border-surface-200 dark:border-surface-950",
|
|
1191
|
+
children: [/* @__PURE__ */ jsx(Tab, {
|
|
1192
|
+
value: "tables",
|
|
1193
|
+
children: "Tables"
|
|
1194
|
+
}), /* @__PURE__ */ jsx(Tab, {
|
|
1195
|
+
value: "info",
|
|
1196
|
+
children: "Info"
|
|
1197
|
+
})]
|
|
1198
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1199
|
+
className: "flex-grow overflow-hidden relative",
|
|
1200
|
+
children: [sidebarTab === "tables" && /* @__PURE__ */ jsxs("div", {
|
|
1201
|
+
className: "flex flex-col h-full",
|
|
1202
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1203
|
+
className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
|
|
1204
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1100
1205
|
variant: "caption",
|
|
1101
|
-
className: "text-text-disabled dark:text-text-disabled-dark
|
|
1102
|
-
children: t("
|
|
1103
|
-
})
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
title: "
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
children:
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1206
|
+
className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
1207
|
+
children: t("studio_schema_tables")
|
|
1208
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
1209
|
+
size: "small",
|
|
1210
|
+
onClick: fetchRLSData,
|
|
1211
|
+
title: "Refresh",
|
|
1212
|
+
children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
|
|
1213
|
+
})]
|
|
1214
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1215
|
+
className: "flex-grow overflow-y-auto no-scrollbar p-1",
|
|
1216
|
+
children: isLoading && tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
1217
|
+
className: "flex justify-center p-4",
|
|
1218
|
+
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
1219
|
+
}) : tables.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
1220
|
+
className: "p-4 text-center",
|
|
1221
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
1222
|
+
variant: "caption",
|
|
1223
|
+
className: "text-text-disabled dark:text-text-disabled-dark italic",
|
|
1224
|
+
children: t("studio_rls_no_tables")
|
|
1225
|
+
})
|
|
1226
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1227
|
+
categorizedTables.collection.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
|
|
1228
|
+
title: "Schema Collections",
|
|
1229
|
+
icon: /* @__PURE__ */ jsx(DatabaseIcon, {
|
|
1230
|
+
size: 12,
|
|
1231
|
+
className: "text-primary dark:text-primary-light"
|
|
1232
|
+
}),
|
|
1233
|
+
expanded: expandedSections.collection ?? true,
|
|
1234
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
1235
|
+
...prev,
|
|
1236
|
+
collection: !(prev.collection ?? true)
|
|
1237
|
+
})),
|
|
1238
|
+
children: categorizedTables.collection.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
1239
|
+
table,
|
|
1240
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
1241
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
1242
|
+
t
|
|
1243
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
1128
1244
|
}),
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
table,
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1245
|
+
(categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ jsxs(SidebarSection, {
|
|
1246
|
+
title: "Other Tables",
|
|
1247
|
+
icon: /* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1248
|
+
size: 12,
|
|
1249
|
+
className: "text-yellow-500 dark:text-yellow-400"
|
|
1250
|
+
}),
|
|
1251
|
+
expanded: expandedSections.other ?? true,
|
|
1252
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
1253
|
+
...prev,
|
|
1254
|
+
other: !(prev.other ?? true)
|
|
1255
|
+
})),
|
|
1256
|
+
children: [categorizedTables.other.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
1257
|
+
table,
|
|
1258
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
1259
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
1260
|
+
t
|
|
1261
|
+
}, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
1262
|
+
table,
|
|
1263
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
1264
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
1265
|
+
badge: "Junction",
|
|
1266
|
+
dimmed: true,
|
|
1267
|
+
t
|
|
1268
|
+
}, `${table.schemaName}.${table.tableName}`))]
|
|
1153
1269
|
}),
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1270
|
+
categorizedTables.internal.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
|
|
1271
|
+
title: "Rebase Internal",
|
|
1272
|
+
icon: /* @__PURE__ */ jsx(LockIcon, {
|
|
1273
|
+
size: 12,
|
|
1274
|
+
className: "text-text-disabled dark:text-text-disabled-dark"
|
|
1275
|
+
}),
|
|
1276
|
+
expanded: expandedSections.internal ?? false,
|
|
1277
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
1278
|
+
...prev,
|
|
1279
|
+
internal: !(prev.internal ?? false)
|
|
1280
|
+
})),
|
|
1281
|
+
count: categorizedTables.internal.length,
|
|
1282
|
+
children: categorizedTables.internal.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
|
|
1283
|
+
table,
|
|
1284
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
1285
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
1286
|
+
dimmed: true,
|
|
1287
|
+
t
|
|
1288
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
1289
|
+
})
|
|
1290
|
+
] })
|
|
1291
|
+
})]
|
|
1292
|
+
}), sidebarTab === "info" && /* @__PURE__ */ jsxs("div", {
|
|
1293
|
+
className: "flex flex-col h-full",
|
|
1294
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1295
|
+
className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
|
|
1296
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
1297
|
+
variant: "caption",
|
|
1298
|
+
className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
1299
|
+
children: "Overview"
|
|
1167
1300
|
})
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
})
|
|
1179
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1180
|
-
className: "flex-grow overflow-y-auto p-3 space-y-3 no-scrollbar",
|
|
1181
|
-
children: [
|
|
1182
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1183
|
-
className: cls("p-3 rounded-lg border bg-white dark:bg-surface-900", defaultBorderMixin),
|
|
1184
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1185
|
-
className: "flex items-center gap-2 mb-2",
|
|
1186
|
-
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
1187
|
-
size: iconSize.smallest,
|
|
1188
|
-
className: "text-primary"
|
|
1189
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1190
|
-
variant: "body2",
|
|
1191
|
-
className: "font-semibold text-[13px]",
|
|
1192
|
-
children: "RLS Studio"
|
|
1193
|
-
})]
|
|
1194
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1195
|
-
variant: "caption",
|
|
1196
|
-
className: "text-text-secondary dark:text-text-secondary-dark text-[11px] leading-relaxed block",
|
|
1197
|
-
children: "Manage Row Level Security policies for your PostgreSQL tables. Enable RLS and create fine-grained access policies."
|
|
1198
|
-
})]
|
|
1199
|
-
}),
|
|
1200
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1201
|
-
className: "space-y-2",
|
|
1202
|
-
children: [
|
|
1203
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1204
|
-
className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
|
|
1205
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1206
|
-
variant: "caption",
|
|
1207
|
-
className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
|
|
1208
|
-
children: "Total tables"
|
|
1301
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1302
|
+
className: "flex-grow overflow-y-auto p-3 space-y-3 no-scrollbar",
|
|
1303
|
+
children: [
|
|
1304
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1305
|
+
className: cls("p-3 rounded-lg border bg-white dark:bg-surface-900", defaultBorderMixin),
|
|
1306
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1307
|
+
className: "flex items-center gap-2 mb-2",
|
|
1308
|
+
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
1309
|
+
size: iconSize.smallest,
|
|
1310
|
+
className: "text-primary"
|
|
1209
1311
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
1210
1312
|
variant: "body2",
|
|
1211
|
-
className: "font-
|
|
1212
|
-
children:
|
|
1313
|
+
className: "font-semibold text-[13px]",
|
|
1314
|
+
children: "RLS Studio"
|
|
1213
1315
|
})]
|
|
1214
|
-
}),
|
|
1215
|
-
|
|
1216
|
-
className:
|
|
1217
|
-
children:
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1316
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1317
|
+
variant: "caption",
|
|
1318
|
+
className: "text-text-secondary dark:text-text-secondary-dark text-[11px] leading-relaxed block",
|
|
1319
|
+
children: "Manage Row Level Security policies for your PostgreSQL tables. Enable RLS and create fine-grained access policies."
|
|
1320
|
+
})]
|
|
1321
|
+
}),
|
|
1322
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1323
|
+
className: "space-y-2",
|
|
1324
|
+
children: [
|
|
1325
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1326
|
+
className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
|
|
1327
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1328
|
+
variant: "caption",
|
|
1329
|
+
className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
|
|
1330
|
+
children: "Total tables"
|
|
1331
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1224
1332
|
variant: "body2",
|
|
1225
1333
|
className: "font-mono text-[13px] font-medium",
|
|
1226
|
-
children: rlsStats.
|
|
1334
|
+
children: rlsStats.total
|
|
1227
1335
|
})]
|
|
1228
|
-
})
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
" without RLS"
|
|
1269
|
-
]
|
|
1270
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1271
|
-
variant: "caption",
|
|
1272
|
-
className: "text-yellow-700 dark:text-yellow-600 text-[10px] block mt-0.5",
|
|
1273
|
-
children: "These tables have no row-level access control. If auth enforcement is disabled, data may be publicly accessible."
|
|
1274
|
-
})] })]
|
|
1275
|
-
}),
|
|
1276
|
-
rlsStats.enabled > 0 && rlsStats.enabled - rlsStats.withPolicies > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1277
|
-
className: cls("p-2.5 rounded border border-blue-200 dark:border-blue-900/50 bg-blue-50 dark:bg-blue-900/20 flex items-start gap-2", defaultBorderMixin),
|
|
1278
|
-
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
1279
|
-
size: 14,
|
|
1280
|
-
className: "text-blue-600 dark:text-blue-400 mt-0.5 shrink-0"
|
|
1281
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Typography, {
|
|
1282
|
-
variant: "caption",
|
|
1283
|
-
className: "text-blue-800 dark:text-blue-300 text-[11px] font-semibold block",
|
|
1284
|
-
children: [
|
|
1285
|
-
rlsStats.enabled - rlsStats.withPolicies,
|
|
1286
|
-
" table",
|
|
1287
|
-
rlsStats.enabled - rlsStats.withPolicies > 1 ? "s" : "",
|
|
1288
|
-
" with RLS but no policies"
|
|
1336
|
+
}),
|
|
1337
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1338
|
+
className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
|
|
1339
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1340
|
+
variant: "caption",
|
|
1341
|
+
className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
|
|
1342
|
+
children: "RLS enabled"
|
|
1343
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1344
|
+
className: "flex items-center gap-1.5",
|
|
1345
|
+
children: [/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" }), /* @__PURE__ */ jsx(Typography, {
|
|
1346
|
+
variant: "body2",
|
|
1347
|
+
className: "font-mono text-[13px] font-medium",
|
|
1348
|
+
children: rlsStats.enabled
|
|
1349
|
+
})]
|
|
1350
|
+
})]
|
|
1351
|
+
}),
|
|
1352
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1353
|
+
className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
|
|
1354
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1355
|
+
variant: "caption",
|
|
1356
|
+
className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
|
|
1357
|
+
children: "Tables with policies"
|
|
1358
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1359
|
+
variant: "body2",
|
|
1360
|
+
className: "font-mono text-[13px] font-medium",
|
|
1361
|
+
children: rlsStats.withPolicies
|
|
1362
|
+
})]
|
|
1363
|
+
}),
|
|
1364
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1365
|
+
className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
|
|
1366
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1367
|
+
variant: "caption",
|
|
1368
|
+
className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
|
|
1369
|
+
children: "Total policies"
|
|
1370
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1371
|
+
variant: "body2",
|
|
1372
|
+
className: "font-mono text-[13px] font-medium",
|
|
1373
|
+
children: rlsStats.totalPolicies
|
|
1374
|
+
})]
|
|
1375
|
+
})
|
|
1289
1376
|
]
|
|
1290
|
-
}),
|
|
1291
|
-
|
|
1292
|
-
className: "
|
|
1293
|
-
children:
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1377
|
+
}),
|
|
1378
|
+
rlsStats.total - rlsStats.enabled > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1379
|
+
className: cls("p-2.5 rounded border border-yellow-200 dark:border-yellow-900/50 bg-yellow-50 dark:bg-yellow-900/20 flex items-start gap-2", defaultBorderMixin),
|
|
1380
|
+
children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1381
|
+
size: 14,
|
|
1382
|
+
className: "text-yellow-600 dark:text-yellow-500 mt-0.5 shrink-0"
|
|
1383
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Typography, {
|
|
1384
|
+
variant: "caption",
|
|
1385
|
+
className: "text-yellow-800 dark:text-yellow-400 text-[11px] font-semibold block",
|
|
1386
|
+
children: [
|
|
1387
|
+
rlsStats.total - rlsStats.enabled,
|
|
1388
|
+
" table",
|
|
1389
|
+
rlsStats.total - rlsStats.enabled > 1 ? "s" : "",
|
|
1390
|
+
" without RLS"
|
|
1391
|
+
]
|
|
1392
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1393
|
+
variant: "caption",
|
|
1394
|
+
className: "text-yellow-700 dark:text-yellow-600 text-[10px] block mt-0.5",
|
|
1395
|
+
children: "These tables have no row-level access control. If auth enforcement is disabled, data may be publicly accessible."
|
|
1396
|
+
})] })]
|
|
1397
|
+
}),
|
|
1398
|
+
rlsStats.enabled > 0 && rlsStats.enabled - rlsStats.withPolicies > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1399
|
+
className: cls("p-2.5 rounded border border-blue-200 dark:border-blue-900/50 bg-blue-50 dark:bg-blue-900/20 flex items-start gap-2", defaultBorderMixin),
|
|
1400
|
+
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
1401
|
+
size: 14,
|
|
1402
|
+
className: "text-blue-600 dark:text-blue-400 mt-0.5 shrink-0"
|
|
1403
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Typography, {
|
|
1404
|
+
variant: "caption",
|
|
1405
|
+
className: "text-blue-800 dark:text-blue-300 text-[11px] font-semibold block",
|
|
1406
|
+
children: [
|
|
1407
|
+
rlsStats.enabled - rlsStats.withPolicies,
|
|
1408
|
+
" table",
|
|
1409
|
+
rlsStats.enabled - rlsStats.withPolicies > 1 ? "s" : "",
|
|
1410
|
+
" with RLS but no policies"
|
|
1411
|
+
]
|
|
1412
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1413
|
+
variant: "caption",
|
|
1414
|
+
className: "text-blue-700 dark:text-blue-500 text-[10px] block mt-0.5",
|
|
1415
|
+
children: "RLS is enabled but no permissive policies exist. All access is denied by default (Postgres deny-all)."
|
|
1416
|
+
})] })]
|
|
1417
|
+
})
|
|
1418
|
+
]
|
|
1419
|
+
})]
|
|
1297
1420
|
})]
|
|
1298
1421
|
})]
|
|
1299
|
-
})
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
className: "flex-grow flex flex-col min-w-0 h-full w-full bg-white dark:bg-surface-950",
|
|
1303
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1304
|
-
className: cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950 min-h-[46px]", defaultBorderMixin),
|
|
1422
|
+
}),
|
|
1423
|
+
secondPanel: /* @__PURE__ */ jsxs("div", {
|
|
1424
|
+
className: "flex-grow flex flex-col min-w-0 h-full w-full bg-white dark:bg-surface-950",
|
|
1305
1425
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
1306
|
-
className: "flex items-center
|
|
1307
|
-
children: [/* @__PURE__ */
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
onClick: fetchRLSData,
|
|
1355
|
-
startIcon: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest }),
|
|
1356
|
-
children: "Refresh"
|
|
1357
|
-
}),
|
|
1358
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1" }),
|
|
1359
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1360
|
-
size: "small",
|
|
1361
|
-
color: "primary",
|
|
1362
|
-
onClick: () => setEditingPolicy("new"),
|
|
1363
|
-
children: t("studio_rls_create_policy")
|
|
1364
|
-
})
|
|
1365
|
-
] })
|
|
1366
|
-
})]
|
|
1367
|
-
}), isLoading && !activeTableData ? /* @__PURE__ */ jsx("div", {
|
|
1368
|
-
className: "flex-grow flex items-center justify-center h-full",
|
|
1369
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
1370
|
-
}) : error ? /* @__PURE__ */ jsx("div", {
|
|
1371
|
-
className: "p-6 h-full flex items-center justify-center",
|
|
1372
|
-
children: /* @__PURE__ */ jsx(ErrorView, {
|
|
1373
|
-
title: t("studio_rls_error"),
|
|
1374
|
-
error,
|
|
1375
|
-
onRetry: fetchRLSData
|
|
1376
|
-
})
|
|
1377
|
-
}) : !activeTableData ? /* @__PURE__ */ jsx("div", {
|
|
1378
|
-
className: "flex-grow flex items-center justify-center text-text-disabled h-full",
|
|
1379
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1380
|
-
className: "text-center",
|
|
1381
|
-
children: [/* @__PURE__ */ jsx("svg", {
|
|
1382
|
-
className: "w-12 h-12 mx-auto mb-4 opacity-50",
|
|
1383
|
-
fill: "none",
|
|
1384
|
-
stroke: "currentColor",
|
|
1385
|
-
viewBox: "0 0 24 24",
|
|
1386
|
-
children: /* @__PURE__ */ jsx("path", {
|
|
1387
|
-
strokeLinecap: "round",
|
|
1388
|
-
strokeLinejoin: "round",
|
|
1389
|
-
strokeWidth: 1,
|
|
1390
|
-
d: "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
1391
|
-
})
|
|
1392
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1393
|
-
variant: "body2",
|
|
1394
|
-
children: t("studio_rls_select_table")
|
|
1426
|
+
className: cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950 min-h-[46px]", defaultBorderMixin),
|
|
1427
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1428
|
+
className: "flex items-center flex-grow overflow-hidden px-4",
|
|
1429
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1430
|
+
variant: "subtitle2",
|
|
1431
|
+
className: "font-mono text-text-secondary dark:text-text-secondary-dark truncate",
|
|
1432
|
+
children: activeTableData ? `${activeTableData.schemaName}.${activeTableData.tableName}` : t("studio_rls_select_table")
|
|
1433
|
+
}), activeTableData && /* @__PURE__ */ jsx("div", {
|
|
1434
|
+
className: "ml-3",
|
|
1435
|
+
children: activeTableData.rlsEnabled ? /* @__PURE__ */ jsx(Chip, {
|
|
1436
|
+
size: "smallest",
|
|
1437
|
+
className: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800",
|
|
1438
|
+
children: t("studio_rls_enabled")
|
|
1439
|
+
}) : /* @__PURE__ */ jsx(Chip, {
|
|
1440
|
+
size: "smallest",
|
|
1441
|
+
className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",
|
|
1442
|
+
children: t("studio_rls_disabled")
|
|
1443
|
+
})
|
|
1444
|
+
})]
|
|
1445
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1446
|
+
className: "flex shrink-0 items-center justify-end gap-1.5",
|
|
1447
|
+
children: activeTableData && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1448
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1449
|
+
variant: "text",
|
|
1450
|
+
size: "small",
|
|
1451
|
+
onClick: () => setConfirmToggleRls({
|
|
1452
|
+
table: activeTableData.tableName,
|
|
1453
|
+
schema: activeTableData.schemaName,
|
|
1454
|
+
enabled: activeTableData.rlsEnabled
|
|
1455
|
+
}),
|
|
1456
|
+
children: activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")
|
|
1457
|
+
}),
|
|
1458
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1" }),
|
|
1459
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1460
|
+
variant: "text",
|
|
1461
|
+
size: "small",
|
|
1462
|
+
onClick: fetchRLSData,
|
|
1463
|
+
startIcon: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest }),
|
|
1464
|
+
children: "Refresh"
|
|
1465
|
+
}),
|
|
1466
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1" }),
|
|
1467
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1468
|
+
size: "small",
|
|
1469
|
+
color: "primary",
|
|
1470
|
+
onClick: () => setEditingPolicy("new"),
|
|
1471
|
+
children: t("studio_rls_create_policy")
|
|
1472
|
+
})
|
|
1473
|
+
] })
|
|
1395
1474
|
})]
|
|
1396
|
-
})
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1475
|
+
}), isLoading && !activeTableData ? /* @__PURE__ */ jsx("div", {
|
|
1476
|
+
className: "flex-grow flex items-center justify-center h-full",
|
|
1477
|
+
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
1478
|
+
}) : error ? /* @__PURE__ */ jsx("div", {
|
|
1479
|
+
className: "p-6 h-full flex items-center justify-center",
|
|
1480
|
+
children: /* @__PURE__ */ jsx(ErrorView, {
|
|
1481
|
+
title: t("studio_rls_error"),
|
|
1482
|
+
error,
|
|
1483
|
+
onRetry: fetchRLSData
|
|
1484
|
+
})
|
|
1485
|
+
}) : !activeTableData ? /* @__PURE__ */ jsx("div", {
|
|
1486
|
+
className: "flex-grow flex items-center justify-center text-text-disabled h-full",
|
|
1487
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1488
|
+
className: "text-center",
|
|
1489
|
+
children: [/* @__PURE__ */ jsx("svg", {
|
|
1490
|
+
className: "w-12 h-12 mx-auto mb-4 opacity-50",
|
|
1491
|
+
fill: "none",
|
|
1492
|
+
stroke: "currentColor",
|
|
1493
|
+
viewBox: "0 0 24 24",
|
|
1494
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
1495
|
+
strokeLinecap: "round",
|
|
1496
|
+
strokeLinejoin: "round",
|
|
1497
|
+
strokeWidth: 1,
|
|
1498
|
+
d: "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
|
1499
|
+
})
|
|
1500
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1501
|
+
variant: "body2",
|
|
1502
|
+
children: t("studio_rls_select_table")
|
|
1503
|
+
})]
|
|
1504
|
+
})
|
|
1505
|
+
}) : editingPolicy ? /* @__PURE__ */ jsx(PolicyEditor, {
|
|
1506
|
+
policy: editingPolicy === "new" ? void 0 : editingPolicy,
|
|
1507
|
+
schema: activeTableData.schemaName,
|
|
1508
|
+
table: activeTableData.tableName,
|
|
1509
|
+
roleOptions: pgRoleOptions,
|
|
1510
|
+
onSave: async (newPolicy) => {
|
|
1511
|
+
if (activeCollection && hasCodebase) {
|
|
1512
|
+
const rule = {
|
|
1513
|
+
name: newPolicy.policyname,
|
|
1514
|
+
operation: newPolicy.cmd?.toLowerCase(),
|
|
1515
|
+
mode: newPolicy.permissive?.toLowerCase(),
|
|
1516
|
+
using: newPolicy.qual || void 0,
|
|
1517
|
+
withCheck: newPolicy.with_check || void 0,
|
|
1518
|
+
...newPolicy.roles && !(newPolicy.roles.length === 1 && newPolicy.roles[0] === "public") ? { pgRoles: newPolicy.roles } : {}
|
|
1519
|
+
};
|
|
1520
|
+
const existingRules = (isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [];
|
|
1521
|
+
let newRules;
|
|
1522
|
+
if (editingPolicy === "new") newRules = [...existingRules, rule];
|
|
1523
|
+
else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
|
|
1524
|
+
try {
|
|
1525
|
+
await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
|
|
1526
|
+
snackbarController.open({
|
|
1527
|
+
type: "success",
|
|
1528
|
+
message: "Policy saved successfully"
|
|
1529
|
+
});
|
|
1530
|
+
setEditingPolicy(null);
|
|
1531
|
+
fetchRLSData();
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
reportSaveFailure(e);
|
|
1534
|
+
}
|
|
1535
|
+
} else try {
|
|
1536
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
|
|
1537
|
+
const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
|
|
1538
|
+
const cmd = newPolicy.cmd || "ALL";
|
|
1539
|
+
const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
|
|
1540
|
+
const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
|
|
1541
|
+
if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
|
|
1542
|
+
let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
|
|
1543
|
+
sql += ` AS ${permissive}`;
|
|
1544
|
+
sql += ` FOR ${cmd}`;
|
|
1545
|
+
sql += ` TO ${roles}`;
|
|
1546
|
+
if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
|
|
1547
|
+
if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
|
|
1548
|
+
await databaseAdmin.executeSql(sql);
|
|
1418
1549
|
snackbarController.open({
|
|
1419
1550
|
type: "success",
|
|
1420
|
-
message:
|
|
1551
|
+
message: `Policy "${newPolicy.policyname}" applied to the database`
|
|
1421
1552
|
});
|
|
1422
1553
|
setEditingPolicy(null);
|
|
1423
1554
|
fetchRLSData();
|
|
@@ -1427,286 +1558,264 @@ var RLSEditor = ({ apiUrl = "" }) => {
|
|
|
1427
1558
|
message: e instanceof Error ? e.message : String(e)
|
|
1428
1559
|
});
|
|
1429
1560
|
}
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
className: "
|
|
1479
|
-
children:
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
variant: "caption",
|
|
1496
|
-
className: "opacity-80",
|
|
1497
|
-
children: "This is an auto-generated junction table for a many-to-many relation. Rebase derives its policies: rows are readable when both related rows are, and writable following the declaring collection's update rules (plus the server/admin baseline). You can still add RLS policies directly to broaden access."
|
|
1498
|
-
})] })]
|
|
1499
|
-
})
|
|
1500
|
-
}),
|
|
1501
|
-
activeTableData && activeTableCategory === "other" && /* @__PURE__ */ jsx(Alert, {
|
|
1502
|
-
color: "warning",
|
|
1503
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1504
|
-
className: "flex items-start gap-2",
|
|
1505
|
-
children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1506
|
-
size: 16,
|
|
1507
|
-
className: "shrink-0 mt-0.5"
|
|
1508
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1509
|
-
variant: "body2",
|
|
1510
|
-
className: "mb-1 font-semibold",
|
|
1511
|
-
children: "Unmapped Table"
|
|
1512
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1513
|
-
variant: "caption",
|
|
1514
|
-
className: "opacity-80",
|
|
1515
|
-
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."
|
|
1516
|
-
})] })]
|
|
1517
|
-
})
|
|
1518
|
-
}),
|
|
1519
|
-
activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
|
|
1520
|
-
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),
|
|
1521
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
1522
|
-
className: "flex gap-3 items-start",
|
|
1523
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1524
|
-
className: "mt-1 bg-yellow-100 dark:bg-yellow-900/50 p-1.5 rounded-md shrink-0 flex items-center justify-center",
|
|
1525
|
-
children: /* @__PURE__ */ jsx(AlertTriangleIcon, { size: iconSize.smallest })
|
|
1526
|
-
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1527
|
-
variant: "subtitle2",
|
|
1528
|
-
className: "text-yellow-800 dark:text-yellow-500",
|
|
1529
|
-
children: "Row Level Security (RLS) is disabled"
|
|
1530
|
-
}), /* @__PURE__ */ jsx(Typography, {
|
|
1531
|
-
variant: "body2",
|
|
1532
|
-
className: "text-yellow-700 dark:text-yellow-600/90 mt-1 max-w-2xl",
|
|
1533
|
-
children: "Your table is completely readable and writable by anyone with access privileges. Enable RLS to create policies that restrict access to specific rows."
|
|
1534
|
-
})] })]
|
|
1535
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
1536
|
-
size: "medium",
|
|
1537
|
-
variant: "filled",
|
|
1538
|
-
color: "neutral",
|
|
1539
|
-
onClick: () => setEditingPolicy("new"),
|
|
1540
|
-
className: "shrink-0 whitespace-nowrap",
|
|
1541
|
-
disabled: !activeCollection,
|
|
1542
|
-
children: t("studio_rls_create_policy")
|
|
1543
|
-
})]
|
|
1544
|
-
}),
|
|
1545
|
-
activeTableData && mergedPolicies && mergedPolicies.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1546
|
-
className: "flex flex-col gap-3",
|
|
1547
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1548
|
-
variant: "subtitle2",
|
|
1549
|
-
className: "text-text-secondary dark:text-text-secondary-dark uppercase tracking-wider mb-1",
|
|
1550
|
-
children: t("studio_rls_policies")
|
|
1551
|
-
}), mergedPolicies.map((policy) => /* @__PURE__ */ jsxs(Paper, {
|
|
1552
|
-
className: cls("p-3 sm:px-4 sm:py-3 flex flex-col sm:flex-row sm:items-center justify-between gap-4 border rounded-lg", defaultBorderMixin),
|
|
1561
|
+
},
|
|
1562
|
+
onCancel: () => setEditingPolicy(null)
|
|
1563
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1564
|
+
className: "flex-grow flex flex-col overflow-hidden",
|
|
1565
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1566
|
+
className: "p-6 pt-4 flex-grow overflow-auto bg-surface-50 dark:bg-surface-900",
|
|
1567
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1568
|
+
className: "max-w-4xl mx-auto flex flex-col gap-6",
|
|
1569
|
+
children: [
|
|
1570
|
+
activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
|
|
1571
|
+
color: "info",
|
|
1572
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1573
|
+
className: "flex items-start gap-2",
|
|
1574
|
+
children: [/* @__PURE__ */ jsx(LockIcon, {
|
|
1575
|
+
size: 16,
|
|
1576
|
+
className: "shrink-0 mt-0.5"
|
|
1577
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1578
|
+
variant: "body2",
|
|
1579
|
+
className: "mb-1 font-semibold",
|
|
1580
|
+
children: "Rebase System Table"
|
|
1581
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1582
|
+
variant: "caption",
|
|
1583
|
+
className: "opacity-80",
|
|
1584
|
+
children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
|
|
1585
|
+
})] })]
|
|
1586
|
+
})
|
|
1587
|
+
}),
|
|
1588
|
+
activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ jsx(Alert, {
|
|
1589
|
+
color: "info",
|
|
1590
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1591
|
+
className: "flex items-start gap-2",
|
|
1592
|
+
children: [/* @__PURE__ */ jsx(Link2Icon, {
|
|
1593
|
+
size: 16,
|
|
1594
|
+
className: "shrink-0 mt-0.5"
|
|
1595
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1596
|
+
variant: "body2",
|
|
1597
|
+
className: "mb-1 font-semibold",
|
|
1598
|
+
children: "Junction Table"
|
|
1599
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1600
|
+
variant: "caption",
|
|
1601
|
+
className: "opacity-80",
|
|
1602
|
+
children: "This is an auto-generated junction table for a many-to-many relation. Rebase derives its policies: rows are readable when both related rows are, and writable following the declaring collection's update rules (plus the server/admin baseline). You can still add RLS policies directly to broaden access."
|
|
1603
|
+
})] })]
|
|
1604
|
+
})
|
|
1605
|
+
}),
|
|
1606
|
+
activeTableData && activeTableCategory === "other" && /* @__PURE__ */ jsx(Alert, {
|
|
1607
|
+
color: "warning",
|
|
1608
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1609
|
+
className: "flex items-start gap-2",
|
|
1610
|
+
children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1611
|
+
size: 16,
|
|
1612
|
+
className: "shrink-0 mt-0.5"
|
|
1613
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1614
|
+
variant: "body2",
|
|
1615
|
+
className: "mb-1 font-semibold",
|
|
1616
|
+
children: "Unmapped Table"
|
|
1617
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1618
|
+
variant: "caption",
|
|
1619
|
+
className: "opacity-80",
|
|
1620
|
+
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."
|
|
1621
|
+
})] })]
|
|
1622
|
+
})
|
|
1623
|
+
}),
|
|
1624
|
+
activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
|
|
1625
|
+
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),
|
|
1553
1626
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
1554
|
-
className: "flex
|
|
1627
|
+
className: "flex gap-3 items-start",
|
|
1628
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1629
|
+
className: "mt-1 bg-yellow-100 dark:bg-yellow-900/50 p-1.5 rounded-md shrink-0 flex items-center justify-center",
|
|
1630
|
+
children: /* @__PURE__ */ jsx(AlertTriangleIcon, { size: iconSize.smallest })
|
|
1631
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
|
|
1632
|
+
variant: "subtitle2",
|
|
1633
|
+
className: "text-yellow-800 dark:text-yellow-500",
|
|
1634
|
+
children: "Row Level Security (RLS) is disabled"
|
|
1635
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1636
|
+
variant: "body2",
|
|
1637
|
+
className: "text-yellow-700 dark:text-yellow-600/90 mt-1 max-w-2xl",
|
|
1638
|
+
children: "Your table is completely readable and writable by anyone with access privileges. Enable RLS to create policies that restrict access to specific rows."
|
|
1639
|
+
})] })]
|
|
1640
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1641
|
+
size: "medium",
|
|
1642
|
+
variant: "filled",
|
|
1643
|
+
color: "neutral",
|
|
1644
|
+
onClick: () => setEditingPolicy("new"),
|
|
1645
|
+
className: "shrink-0 whitespace-nowrap",
|
|
1646
|
+
disabled: !activeCollection,
|
|
1647
|
+
children: t("studio_rls_create_policy")
|
|
1648
|
+
})]
|
|
1649
|
+
}),
|
|
1650
|
+
activeTableData && mergedPolicies && mergedPolicies.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
1651
|
+
className: "flex flex-col gap-3",
|
|
1652
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1653
|
+
variant: "subtitle2",
|
|
1654
|
+
className: "text-text-secondary dark:text-text-secondary-dark uppercase tracking-wider mb-1",
|
|
1655
|
+
children: t("studio_rls_policies")
|
|
1656
|
+
}), mergedPolicies.map((policy) => /* @__PURE__ */ jsxs(Paper, {
|
|
1657
|
+
className: cls("p-3 sm:px-4 sm:py-3 flex flex-col sm:flex-row sm:items-center justify-between gap-4 border rounded-lg", defaultBorderMixin),
|
|
1555
1658
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
1556
|
-
className: "flex
|
|
1557
|
-
children: [
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
children: "
|
|
1659
|
+
className: "flex flex-col gap-2 min-w-0",
|
|
1660
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1661
|
+
className: "flex items-center gap-2",
|
|
1662
|
+
children: [
|
|
1663
|
+
/* @__PURE__ */ jsx(KeyIcon, {
|
|
1664
|
+
size: iconSize.smallest,
|
|
1665
|
+
className: "text-text-secondary dark:text-text-secondary-dark shrink-0"
|
|
1666
|
+
}),
|
|
1667
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
1668
|
+
variant: "body2",
|
|
1669
|
+
className: "truncate",
|
|
1670
|
+
children: policy.policyname
|
|
1671
|
+
}),
|
|
1672
|
+
policy.status === "code_only" && /* @__PURE__ */ jsx(Tooltip, {
|
|
1673
|
+
title: t("studio_rls_unapplied_tooltip"),
|
|
1674
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1675
|
+
className: "px-1.5 py-0.5 rounded text-[10px] uppercase bg-primary/10 text-primary border border-primary/20 shrink-0",
|
|
1676
|
+
children: t("studio_rls_unapplied")
|
|
1677
|
+
})
|
|
1678
|
+
}),
|
|
1679
|
+
policy.status === "live" && hasCodebase && /* @__PURE__ */ jsx(Tooltip, {
|
|
1680
|
+
title: "This policy is live in the database but missing from your codebase schema.",
|
|
1681
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1682
|
+
className: "px-1.5 py-0.5 rounded text-[10px] uppercase bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0",
|
|
1683
|
+
children: "DB Only"
|
|
1684
|
+
})
|
|
1572
1685
|
})
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
children: "DB Only"
|
|
1579
|
-
})
|
|
1580
|
-
})
|
|
1581
|
-
]
|
|
1686
|
+
]
|
|
1687
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1688
|
+
className: "flex flex-wrap gap-1.5 text-sm",
|
|
1689
|
+
children: [renderPolicyTag("Action", policy.cmd), renderPolicyTag("Roles", Array.isArray(policy.roles) ? policy.roles.join(", ") : policy.roles)]
|
|
1690
|
+
})]
|
|
1582
1691
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1583
|
-
className: "flex
|
|
1584
|
-
children: [
|
|
1585
|
-
|
|
1586
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1587
|
-
className: "flex gap-2 shrink-0 items-center",
|
|
1588
|
-
children: [
|
|
1589
|
-
policy.status === "live" && activeCollection && hasCodebase && /* @__PURE__ */ jsx(Button, {
|
|
1590
|
-
size: "small",
|
|
1591
|
-
variant: "outlined",
|
|
1592
|
-
color: "primary",
|
|
1593
|
-
onClick: async () => {
|
|
1594
|
-
const rule = {
|
|
1595
|
-
name: policy.policyname,
|
|
1596
|
-
operation: policy.cmd?.toLowerCase(),
|
|
1597
|
-
mode: policy.permissive?.toLowerCase(),
|
|
1598
|
-
using: policy.qual || void 0,
|
|
1599
|
-
withCheck: policy.with_check || void 0,
|
|
1600
|
-
roles: policy.roles
|
|
1601
|
-
};
|
|
1602
|
-
const newRules = [...(isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [], rule];
|
|
1603
|
-
try {
|
|
1604
|
-
await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
|
|
1605
|
-
snackbarController.open({
|
|
1606
|
-
type: "success",
|
|
1607
|
-
message: "Policy imported successfully"
|
|
1608
|
-
});
|
|
1609
|
-
fetchRLSData();
|
|
1610
|
-
} catch (e) {
|
|
1611
|
-
snackbarController.open({
|
|
1612
|
-
type: "error",
|
|
1613
|
-
message: e instanceof Error ? e.message : String(e)
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
|
-
},
|
|
1617
|
-
children: "Import to codebase"
|
|
1618
|
-
}),
|
|
1619
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1620
|
-
size: "small",
|
|
1621
|
-
variant: "text",
|
|
1622
|
-
color: "primary",
|
|
1623
|
-
onClick: () => setEditingPolicy(policy),
|
|
1624
|
-
children: t("studio_rls_edit")
|
|
1625
|
-
}),
|
|
1626
|
-
policy.status !== "code_only" && /* @__PURE__ */ jsx(Tooltip, {
|
|
1627
|
-
title: t("studio_rls_delete"),
|
|
1628
|
-
asChild: true,
|
|
1629
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1692
|
+
className: "flex gap-2 shrink-0 items-center",
|
|
1693
|
+
children: [
|
|
1694
|
+
policy.status === "live" && activeCollection && hasCodebase && /* @__PURE__ */ jsx(Button, {
|
|
1630
1695
|
size: "small",
|
|
1696
|
+
variant: "outlined",
|
|
1697
|
+
color: "primary",
|
|
1631
1698
|
onClick: async () => {
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1699
|
+
const rule = {
|
|
1700
|
+
name: policy.policyname,
|
|
1701
|
+
operation: policy.cmd?.toLowerCase(),
|
|
1702
|
+
mode: policy.permissive?.toLowerCase(),
|
|
1703
|
+
using: policy.qual || void 0,
|
|
1704
|
+
withCheck: policy.with_check || void 0,
|
|
1705
|
+
roles: policy.roles
|
|
1706
|
+
};
|
|
1707
|
+
const newRules = [...(isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [], rule];
|
|
1635
1708
|
try {
|
|
1636
|
-
await
|
|
1709
|
+
await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
|
|
1637
1710
|
snackbarController.open({
|
|
1638
1711
|
type: "success",
|
|
1639
|
-
message:
|
|
1712
|
+
message: "Policy imported successfully"
|
|
1640
1713
|
});
|
|
1641
1714
|
fetchRLSData();
|
|
1642
1715
|
} catch (e) {
|
|
1643
|
-
|
|
1644
|
-
type: "error",
|
|
1645
|
-
message: e instanceof Error ? e.message : String(e)
|
|
1646
|
-
});
|
|
1716
|
+
reportSaveFailure(e);
|
|
1647
1717
|
}
|
|
1648
1718
|
},
|
|
1649
|
-
children:
|
|
1719
|
+
children: "Import to codebase"
|
|
1720
|
+
}),
|
|
1721
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1722
|
+
size: "small",
|
|
1723
|
+
variant: "text",
|
|
1724
|
+
color: "primary",
|
|
1725
|
+
onClick: () => setEditingPolicy(policy),
|
|
1726
|
+
children: t("studio_rls_edit")
|
|
1727
|
+
}),
|
|
1728
|
+
policy.status !== "code_only" && /* @__PURE__ */ jsx(Tooltip, {
|
|
1729
|
+
title: t("studio_rls_delete"),
|
|
1730
|
+
asChild: true,
|
|
1731
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1732
|
+
size: "small",
|
|
1733
|
+
onClick: () => setConfirmDropPolicy({
|
|
1734
|
+
policyName: policy.policyname,
|
|
1735
|
+
table: activeTableData.tableName,
|
|
1736
|
+
schema: activeTableData.schemaName
|
|
1737
|
+
}),
|
|
1738
|
+
children: /* @__PURE__ */ jsx(Trash2Icon, { size: iconSize.smallest })
|
|
1739
|
+
})
|
|
1650
1740
|
})
|
|
1651
|
-
|
|
1652
|
-
]
|
|
1653
|
-
})]
|
|
1654
|
-
},
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1741
|
+
]
|
|
1742
|
+
})]
|
|
1743
|
+
}, policy.policyname))]
|
|
1744
|
+
}),
|
|
1745
|
+
activeTableData && mergedPolicies.length === 0 && activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
|
|
1746
|
+
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
1747
|
+
children: [
|
|
1748
|
+
/* @__PURE__ */ jsx(ShieldIcon, {
|
|
1749
|
+
size: 40,
|
|
1750
|
+
className: "text-surface-300 dark:text-surface-600 mb-4"
|
|
1751
|
+
}),
|
|
1752
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
1753
|
+
variant: "subtitle2",
|
|
1754
|
+
className: "text-text-secondary dark:text-text-secondary-dark mb-2",
|
|
1755
|
+
children: "No policies defined"
|
|
1756
|
+
}),
|
|
1757
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
1758
|
+
variant: "caption",
|
|
1759
|
+
className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
|
|
1760
|
+
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."
|
|
1761
|
+
}),
|
|
1762
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1763
|
+
size: "small",
|
|
1764
|
+
variant: "filled",
|
|
1765
|
+
color: "primary",
|
|
1766
|
+
onClick: () => setEditingPolicy("new"),
|
|
1767
|
+
children: t("studio_rls_create_policy")
|
|
1768
|
+
})
|
|
1769
|
+
]
|
|
1770
|
+
}),
|
|
1771
|
+
activeTableData && mergedPolicies.length === 0 && !activeTableData.rlsEnabled && activeCollection && /* @__PURE__ */ jsxs("div", {
|
|
1772
|
+
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
1773
|
+
children: [
|
|
1774
|
+
/* @__PURE__ */ jsx(AlertTriangleIcon, {
|
|
1775
|
+
size: 40,
|
|
1776
|
+
className: "text-yellow-400 dark:text-yellow-600 mb-4"
|
|
1777
|
+
}),
|
|
1778
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
1779
|
+
variant: "subtitle2",
|
|
1780
|
+
className: "text-text-secondary dark:text-text-secondary-dark mb-2",
|
|
1781
|
+
children: "No access control"
|
|
1782
|
+
}),
|
|
1783
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
1784
|
+
variant: "caption",
|
|
1785
|
+
className: "text-text-disabled dark:text-text-disabled-dark max-w-sm",
|
|
1786
|
+
children: "This table has neither RLS nor policies. Enable RLS and create policies to restrict row-level access."
|
|
1787
|
+
})
|
|
1788
|
+
]
|
|
1789
|
+
})
|
|
1790
|
+
]
|
|
1791
|
+
})
|
|
1702
1792
|
})
|
|
1703
|
-
})
|
|
1704
|
-
})
|
|
1793
|
+
})]
|
|
1794
|
+
})
|
|
1795
|
+
}),
|
|
1796
|
+
/* @__PURE__ */ jsx(ConfirmationDialog, {
|
|
1797
|
+
open: confirmToggleRls !== null,
|
|
1798
|
+
loading: confirming,
|
|
1799
|
+
onAccept: applyToggleRls,
|
|
1800
|
+
onCancel: () => setConfirmToggleRls(null),
|
|
1801
|
+
title: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_title") : t("studio_rls_enable_confirm_title"),
|
|
1802
|
+
body: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_body", { table: confirmToggleRls?.table ?? "" }) : t("studio_rls_enable_confirm_body", { table: confirmToggleRls?.table ?? "" })
|
|
1803
|
+
}),
|
|
1804
|
+
/* @__PURE__ */ jsx(ConfirmationDialog, {
|
|
1805
|
+
open: confirmDropPolicy !== null,
|
|
1806
|
+
loading: confirming,
|
|
1807
|
+
onAccept: applyDropPolicy,
|
|
1808
|
+
onCancel: () => setConfirmDropPolicy(null),
|
|
1809
|
+
title: t("studio_rls_drop_policy_title"),
|
|
1810
|
+
body: t("studio_rls_drop_policy_body", {
|
|
1811
|
+
policy: confirmDropPolicy?.policyName ?? "",
|
|
1812
|
+
table: confirmDropPolicy?.table ?? ""
|
|
1813
|
+
})
|
|
1705
1814
|
})
|
|
1706
|
-
|
|
1815
|
+
]
|
|
1707
1816
|
});
|
|
1708
1817
|
};
|
|
1709
1818
|
//#endregion
|
|
1710
1819
|
export { RLSEditor };
|
|
1711
1820
|
|
|
1712
|
-
//# sourceMappingURL=RLSEditor-
|
|
1821
|
+
//# sourceMappingURL=RLSEditor-Sa1D34ny.js.map
|