@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.
Files changed (81) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +12 -4
  3. package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
  4. package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
  5. package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
  6. package/dist/BackupsView-DVkKzaPR.js.map +1 -0
  7. package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
  8. package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
  9. package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
  10. package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
  11. package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
  12. package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
  13. package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
  14. package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
  15. package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
  16. package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
  17. package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
  18. package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
  19. package/dist/components/RLSEditor/saveRules.d.ts +38 -0
  20. package/dist/components/RebaseStudio.d.ts +1 -1
  21. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
  22. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
  23. package/dist/components/load-failure-view.d.ts +17 -0
  24. package/dist/components/load-failure.d.ts +47 -0
  25. package/dist/index.es.js +56 -36
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/load-failure-C6OMJSGC.js +31 -0
  28. package/dist/load-failure-C6OMJSGC.js.map +1 -0
  29. package/dist/load-failure-view-CBTB_h-h.js +48 -0
  30. package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
  31. package/package.json +36 -21
  32. package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
  33. package/dist/BackupsView-DNS6LdVg.js.map +0 -1
  34. package/dist/BranchesView-CM3B-ER3.js.map +0 -1
  35. package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
  36. package/dist/RLSEditor-CBX4uD66.js.map +0 -1
  37. package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
  38. package/dist/StorageView-BGDeC5GI.js.map +0 -1
  39. package/dist/components/StorageView/storage-failure.d.ts +0 -34
  40. package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
  41. package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
  42. package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
  43. package/src/components/ApiExplorer/parseSpec.ts +0 -104
  44. package/src/components/ApiExplorer/types.ts +0 -84
  45. package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
  46. package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
  47. package/src/components/ApiKeys/permissions.ts +0 -145
  48. package/src/components/AuthSimulationSelector.tsx +0 -72
  49. package/src/components/Backups/BackupsView.tsx +0 -185
  50. package/src/components/Branches/BranchesView.tsx +0 -386
  51. package/src/components/CronJobs/CronJobsView.tsx +0 -413
  52. package/src/components/JSEditor/JSEditor.tsx +0 -1100
  53. package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
  54. package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
  55. package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
  56. package/src/components/LogsExplorer/useLogTail.ts +0 -356
  57. package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
  58. package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
  59. package/src/components/RLSEditor/index.ts +0 -1
  60. package/src/components/RLSEditor/policy-presets.ts +0 -176
  61. package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
  62. package/src/components/RebaseStudio.tsx +0 -174
  63. package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
  64. package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
  65. package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
  66. package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
  67. package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
  68. package/src/components/SQLEditor/sql_editor_types.ts +0 -18
  69. package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
  70. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
  71. package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
  72. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
  73. package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
  74. package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
  75. package/src/components/StorageView/StorageView.tsx +0 -1588
  76. package/src/components/StorageView/storage-failure.ts +0 -63
  77. package/src/components/StudioHomePage.tsx +0 -430
  78. package/src/index.ts +0 -31
  79. package/src/utils/parseSpec.test.ts +0 -309
  80. package/src/utils/sql_utils.test.ts +0 -265
  81. 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 saveSecurityRules = useCallback((collectionId, securityRules) => saveSecurityRulesToCodebase({
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
- }), [apiBase, apiConfig]);
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__ */ jsx("div", {
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: /* @__PURE__ */ jsx(ResizablePanels, {
1058
- orientation: "horizontal",
1059
- panelSizePercent: sidebarSize,
1060
- onPanelSizeChange: setSidebarSize,
1061
- minPanelSizePx: 220,
1062
- firstPanel: /* @__PURE__ */ jsxs("div", {
1063
- className: cls("flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r", defaultBorderMixin),
1064
- children: [/* @__PURE__ */ jsxs(Tabs, {
1065
- value: sidebarTab,
1066
- onValueChange: (v) => setSidebarTab(v),
1067
- variant: "boxy",
1068
- className: "border-b border-surface-200 dark:border-surface-950",
1069
- children: [/* @__PURE__ */ jsx(Tab, {
1070
- value: "tables",
1071
- children: "Tables"
1072
- }), /* @__PURE__ */ jsx(Tab, {
1073
- value: "info",
1074
- children: "Info"
1075
- })]
1076
- }), /* @__PURE__ */ jsxs("div", {
1077
- className: "flex-grow overflow-hidden relative",
1078
- children: [sidebarTab === "tables" && /* @__PURE__ */ jsxs("div", {
1079
- className: "flex flex-col h-full",
1080
- children: [/* @__PURE__ */ jsxs("div", {
1081
- className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
1082
- children: [/* @__PURE__ */ jsx(Typography, {
1083
- variant: "caption",
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 italic",
1102
- children: t("studio_rls_no_tables")
1103
- })
1104
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1105
- categorizedTables.collection.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
1106
- title: "Schema Collections",
1107
- icon: /* @__PURE__ */ jsx(DatabaseIcon, {
1108
- size: 12,
1109
- className: "text-primary dark:text-primary-light"
1110
- }),
1111
- expanded: expandedSections.collection ?? true,
1112
- onToggle: () => setExpandedSections((prev) => ({
1113
- ...prev,
1114
- collection: !(prev.collection ?? true)
1115
- })),
1116
- children: categorizedTables.collection.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
1117
- table,
1118
- isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
1119
- onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
1120
- t
1121
- }, `${table.schemaName}.${table.tableName}`))
1122
- }),
1123
- (categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ jsxs(SidebarSection, {
1124
- title: "Other Tables",
1125
- icon: /* @__PURE__ */ jsx(AlertTriangleIcon, {
1126
- size: 12,
1127
- className: "text-yellow-500 dark:text-yellow-400"
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
- expanded: expandedSections.other ?? true,
1130
- onToggle: () => setExpandedSections((prev) => ({
1131
- ...prev,
1132
- other: !(prev.other ?? true)
1133
- })),
1134
- children: [categorizedTables.other.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
1135
- table,
1136
- isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
1137
- onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
1138
- t
1139
- }, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
1140
- table,
1141
- isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
1142
- onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
1143
- badge: "Junction",
1144
- dimmed: true,
1145
- t
1146
- }, `${table.schemaName}.${table.tableName}`))]
1147
- }),
1148
- categorizedTables.internal.length > 0 && /* @__PURE__ */ jsx(SidebarSection, {
1149
- title: "Rebase Internal",
1150
- icon: /* @__PURE__ */ jsx(LockIcon, {
1151
- size: 12,
1152
- className: "text-text-disabled dark:text-text-disabled-dark"
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
- expanded: expandedSections.internal ?? false,
1155
- onToggle: () => setExpandedSections((prev) => ({
1156
- ...prev,
1157
- internal: !(prev.internal ?? false)
1158
- })),
1159
- count: categorizedTables.internal.length,
1160
- children: categorizedTables.internal.map((table) => /* @__PURE__ */ jsx(SidebarTableRow, {
1161
- table,
1162
- isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
1163
- onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
1164
- dimmed: true,
1165
- t
1166
- }, `${table.schemaName}.${table.tableName}`))
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
- }), sidebarTab === "info" && /* @__PURE__ */ jsxs("div", {
1171
- className: "flex flex-col h-full",
1172
- children: [/* @__PURE__ */ jsx("div", {
1173
- className: cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin),
1174
- children: /* @__PURE__ */ jsx(Typography, {
1175
- variant: "caption",
1176
- className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
1177
- children: "Overview"
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-mono text-[13px] font-medium",
1212
- children: rlsStats.total
1313
+ className: "font-semibold text-[13px]",
1314
+ children: "RLS Studio"
1213
1315
  })]
1214
- }),
1215
- /* @__PURE__ */ jsxs("div", {
1216
- className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
1217
- children: [/* @__PURE__ */ jsx(Typography, {
1218
- variant: "caption",
1219
- className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
1220
- children: "RLS enabled"
1221
- }), /* @__PURE__ */ jsxs("div", {
1222
- className: "flex items-center gap-1.5",
1223
- children: [/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" }), /* @__PURE__ */ jsx(Typography, {
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.enabled
1334
+ children: rlsStats.total
1227
1335
  })]
1228
- })]
1229
- }),
1230
- /* @__PURE__ */ jsxs("div", {
1231
- className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
1232
- children: [/* @__PURE__ */ jsx(Typography, {
1233
- variant: "caption",
1234
- className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
1235
- children: "Tables with policies"
1236
- }), /* @__PURE__ */ jsx(Typography, {
1237
- variant: "body2",
1238
- className: "font-mono text-[13px] font-medium",
1239
- children: rlsStats.withPolicies
1240
- })]
1241
- }),
1242
- /* @__PURE__ */ jsxs("div", {
1243
- className: cls("p-2.5 rounded border bg-white dark:bg-surface-900 flex items-center justify-between", defaultBorderMixin),
1244
- children: [/* @__PURE__ */ jsx(Typography, {
1245
- variant: "caption",
1246
- className: "text-text-secondary dark:text-text-secondary-dark text-[11px]",
1247
- children: "Total policies"
1248
- }), /* @__PURE__ */ jsx(Typography, {
1249
- variant: "body2",
1250
- className: "font-mono text-[13px] font-medium",
1251
- children: rlsStats.totalPolicies
1252
- })]
1253
- })
1254
- ]
1255
- }),
1256
- rlsStats.total - rlsStats.enabled > 0 && /* @__PURE__ */ jsxs("div", {
1257
- 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),
1258
- children: [/* @__PURE__ */ jsx(AlertTriangleIcon, {
1259
- size: 14,
1260
- className: "text-yellow-600 dark:text-yellow-500 mt-0.5 shrink-0"
1261
- }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Typography, {
1262
- variant: "caption",
1263
- className: "text-yellow-800 dark:text-yellow-400 text-[11px] font-semibold block",
1264
- children: [
1265
- rlsStats.total - rlsStats.enabled,
1266
- " table",
1267
- rlsStats.total - rlsStats.enabled > 1 ? "s" : "",
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
- }), /* @__PURE__ */ jsx(Typography, {
1291
- variant: "caption",
1292
- className: "text-blue-700 dark:text-blue-500 text-[10px] block mt-0.5",
1293
- children: "RLS is enabled but no permissive policies exist. All access is denied by default (Postgres deny-all)."
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
- secondPanel: /* @__PURE__ */ jsxs("div", {
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 flex-grow overflow-hidden px-4",
1307
- children: [/* @__PURE__ */ jsx(Typography, {
1308
- variant: "subtitle2",
1309
- className: "font-mono text-text-secondary dark:text-text-secondary-dark truncate",
1310
- children: activeTableData ? `${activeTableData.schemaName}.${activeTableData.tableName}` : t("studio_rls_select_table")
1311
- }), activeTableData && /* @__PURE__ */ jsx("div", {
1312
- className: "ml-3",
1313
- children: activeTableData.rlsEnabled ? /* @__PURE__ */ jsx(Chip, {
1314
- size: "smallest",
1315
- className: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800",
1316
- children: t("studio_rls_enabled")
1317
- }) : /* @__PURE__ */ jsx(Chip, {
1318
- size: "smallest",
1319
- className: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",
1320
- children: t("studio_rls_disabled")
1321
- })
1322
- })]
1323
- }), /* @__PURE__ */ jsx("div", {
1324
- className: "flex shrink-0 items-center justify-end gap-1.5",
1325
- children: activeTableData && /* @__PURE__ */ jsxs(Fragment, { children: [
1326
- /* @__PURE__ */ jsx(Button, {
1327
- variant: "text",
1328
- size: "small",
1329
- onClick: async () => {
1330
- const table = activeTableData.tableName;
1331
- const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
1332
- const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
1333
- if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
1334
- try {
1335
- await databaseAdmin.executeSql(`ALTER TABLE ${qualifiedTable} ${action} ROW LEVEL SECURITY`);
1336
- snackbarController.open({
1337
- type: "success",
1338
- message: `RLS ${action.toLowerCase()}d on ${table}`
1339
- });
1340
- fetchRLSData();
1341
- } catch (e) {
1342
- snackbarController.open({
1343
- type: "error",
1344
- message: e instanceof Error ? e.message : String(e)
1345
- });
1346
- }
1347
- },
1348
- children: activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")
1349
- }),
1350
- /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-surface-200 dark:bg-surface-950 mx-1" }),
1351
- /* @__PURE__ */ jsx(Button, {
1352
- variant: "text",
1353
- size: "small",
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
- }) : editingPolicy ? /* @__PURE__ */ jsx(PolicyEditor, {
1398
- policy: editingPolicy === "new" ? void 0 : editingPolicy,
1399
- schema: activeTableData.schemaName,
1400
- table: activeTableData.tableName,
1401
- roleOptions: pgRoleOptions,
1402
- onSave: async (newPolicy) => {
1403
- if (activeCollection && hasCodebase) {
1404
- const rule = {
1405
- name: newPolicy.policyname,
1406
- operation: newPolicy.cmd?.toLowerCase(),
1407
- mode: newPolicy.permissive?.toLowerCase(),
1408
- using: newPolicy.qual || void 0,
1409
- withCheck: newPolicy.with_check || void 0,
1410
- ...newPolicy.roles && !(newPolicy.roles.length === 1 && newPolicy.roles[0] === "public") ? { pgRoles: newPolicy.roles } : {}
1411
- };
1412
- const existingRules = (isPostgresCollectionConfig(activeCollection) ? activeCollection.securityRules : void 0) || [];
1413
- let newRules;
1414
- if (editingPolicy === "new") newRules = [...existingRules, rule];
1415
- else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
1416
- try {
1417
- await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
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: "Policy saved successfully"
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
- } else try {
1431
- const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
1432
- const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
1433
- const cmd = newPolicy.cmd || "ALL";
1434
- const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1435
- const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1436
- if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
1437
- let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1438
- sql += ` AS ${permissive}`;
1439
- sql += ` FOR ${cmd}`;
1440
- sql += ` TO ${roles}`;
1441
- if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1442
- if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1443
- await databaseAdmin.executeSql(sql);
1444
- snackbarController.open({
1445
- type: "success",
1446
- message: `Policy "${newPolicy.policyname}" applied to the database`
1447
- });
1448
- setEditingPolicy(null);
1449
- fetchRLSData();
1450
- } catch (e) {
1451
- snackbarController.open({
1452
- type: "error",
1453
- message: e instanceof Error ? e.message : String(e)
1454
- });
1455
- }
1456
- },
1457
- onCancel: () => setEditingPolicy(null)
1458
- }) : /* @__PURE__ */ jsx("div", {
1459
- className: "flex-grow flex flex-col overflow-hidden",
1460
- children: /* @__PURE__ */ jsx("div", {
1461
- className: "p-6 pt-4 flex-grow overflow-auto bg-surface-50 dark:bg-surface-900",
1462
- children: /* @__PURE__ */ jsxs("div", {
1463
- className: "max-w-4xl mx-auto flex flex-col gap-6",
1464
- children: [
1465
- activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
1466
- color: "info",
1467
- children: /* @__PURE__ */ jsxs("div", {
1468
- className: "flex items-start gap-2",
1469
- children: [/* @__PURE__ */ jsx(LockIcon, {
1470
- size: 16,
1471
- className: "shrink-0 mt-0.5"
1472
- }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1473
- variant: "body2",
1474
- className: "mb-1 font-semibold",
1475
- children: "Rebase System Table"
1476
- }), /* @__PURE__ */ jsx(Typography, {
1477
- variant: "caption",
1478
- className: "opacity-80",
1479
- children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
1480
- })] })]
1481
- })
1482
- }),
1483
- activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ jsx(Alert, {
1484
- color: "info",
1485
- children: /* @__PURE__ */ jsxs("div", {
1486
- className: "flex items-start gap-2",
1487
- children: [/* @__PURE__ */ jsx(Link2Icon, {
1488
- size: 16,
1489
- className: "shrink-0 mt-0.5"
1490
- }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1491
- variant: "body2",
1492
- className: "mb-1 font-semibold",
1493
- children: "Junction Table"
1494
- }), /* @__PURE__ */ jsx(Typography, {
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 flex-col gap-2 min-w-0",
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 items-center gap-2",
1557
- children: [
1558
- /* @__PURE__ */ jsx(KeyIcon, {
1559
- size: iconSize.smallest,
1560
- className: "text-text-secondary dark:text-text-secondary-dark shrink-0"
1561
- }),
1562
- /* @__PURE__ */ jsx(Typography, {
1563
- variant: "body2",
1564
- className: "truncate",
1565
- children: policy.policyname
1566
- }),
1567
- policy.status === "code_only" && /* @__PURE__ */ jsx(Tooltip, {
1568
- title: "This policy is defined in your code but hasn't been applied to the database yet.",
1569
- children: /* @__PURE__ */ jsx("div", {
1570
- className: "px-1.5 py-0.5 rounded text-[10px] uppercase bg-primary/10 text-primary border border-primary/20 shrink-0",
1571
- children: "Unapplied"
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
- policy.status === "live" && hasCodebase && /* @__PURE__ */ jsx(Tooltip, {
1575
- title: "This policy is live in the database but missing from your codebase schema.",
1576
- children: /* @__PURE__ */ jsx("div", {
1577
- 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",
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 flex-wrap gap-1.5 text-sm",
1584
- children: [renderPolicyTag("Action", policy.cmd), renderPolicyTag("Roles", Array.isArray(policy.roles) ? policy.roles.join(", ") : policy.roles)]
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 table = activeTableData.tableName;
1633
- const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
1634
- if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
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 databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${qualifiedTable}`);
1709
+ await saveSecurityRules(activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName, newRules);
1637
1710
  snackbarController.open({
1638
1711
  type: "success",
1639
- message: `Policy "${policy.policyname}" dropped`
1712
+ message: "Policy imported successfully"
1640
1713
  });
1641
1714
  fetchRLSData();
1642
1715
  } catch (e) {
1643
- snackbarController.open({
1644
- type: "error",
1645
- message: e instanceof Error ? e.message : String(e)
1646
- });
1716
+ reportSaveFailure(e);
1647
1717
  }
1648
1718
  },
1649
- children: /* @__PURE__ */ jsx(Trash2Icon, { size: iconSize.smallest })
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
- }, policy.policyname))]
1655
- }),
1656
- activeTableData && mergedPolicies.length === 0 && activeTableData.rlsEnabled && /* @__PURE__ */ jsxs("div", {
1657
- className: "flex flex-col items-center justify-center py-12 text-center",
1658
- children: [
1659
- /* @__PURE__ */ jsx(ShieldIcon, {
1660
- size: 40,
1661
- className: "text-surface-300 dark:text-surface-600 mb-4"
1662
- }),
1663
- /* @__PURE__ */ jsx(Typography, {
1664
- variant: "subtitle2",
1665
- className: "text-text-secondary dark:text-text-secondary-dark mb-2",
1666
- children: "No policies defined"
1667
- }),
1668
- /* @__PURE__ */ jsx(Typography, {
1669
- variant: "caption",
1670
- className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
1671
- 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."
1672
- }),
1673
- /* @__PURE__ */ jsx(Button, {
1674
- size: "small",
1675
- variant: "filled",
1676
- color: "primary",
1677
- onClick: () => setEditingPolicy("new"),
1678
- children: t("studio_rls_create_policy")
1679
- })
1680
- ]
1681
- }),
1682
- activeTableData && mergedPolicies.length === 0 && !activeTableData.rlsEnabled && activeCollection && /* @__PURE__ */ jsxs("div", {
1683
- className: "flex flex-col items-center justify-center py-12 text-center",
1684
- children: [
1685
- /* @__PURE__ */ jsx(AlertTriangleIcon, {
1686
- size: 40,
1687
- className: "text-yellow-400 dark:text-yellow-600 mb-4"
1688
- }),
1689
- /* @__PURE__ */ jsx(Typography, {
1690
- variant: "subtitle2",
1691
- className: "text-text-secondary dark:text-text-secondary-dark mb-2",
1692
- children: "No access control"
1693
- }),
1694
- /* @__PURE__ */ jsx(Typography, {
1695
- variant: "caption",
1696
- className: "text-text-disabled dark:text-text-disabled-dark max-w-sm",
1697
- children: "This table has neither RLS nor policies. Enable RLS and create policies to restrict row-level access."
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-CBX4uD66.js.map
1821
+ //# sourceMappingURL=RLSEditor-Sa1D34ny.js.map