@rebasepro/studio 0.23.0 → 0.24.1-canary.g56af4a4

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 (68) hide show
  1. package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-Bzt7Ye49.js} +18 -29
  2. package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
  3. package/dist/ApiKeysView-Bqf6XYR1.js +1739 -0
  4. package/dist/ApiKeysView-Bqf6XYR1.js.map +1 -0
  5. package/dist/BackupsView-BhOHB6iP.js +354 -0
  6. package/dist/BackupsView-BhOHB6iP.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-BHfwGTYc.js} +16 -10
  8. package/dist/BranchesView-BHfwGTYc.js.map +1 -0
  9. package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-CMNK7eWO.js} +175 -116
  10. package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-DIO2Mw8q.js} +230 -165
  12. package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-C-bwDFIl.js} +66 -44
  14. package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
  15. package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-jFD_d06f.js} +246 -84
  16. package/dist/RLSEditor-jFD_d06f.js.map +1 -0
  17. package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-D5Ci4pyW.js} +502 -600
  18. package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-D-rAqwul.js} +496 -227
  20. package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
  21. package/dist/{StorageView-CMG1DOVX.js → StorageView-CPkDKbsX.js} +838 -451
  22. package/dist/StorageView-CPkDKbsX.js.map +1 -0
  23. package/dist/assets/dagre-layout.worker-BFnhAzuR.js.map +1 -0
  24. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  25. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  26. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  27. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  28. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  29. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  30. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  31. package/dist/components/SQLEditor/SQLEditor.d.ts +11 -0
  32. package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
  33. package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
  34. package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
  35. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -11
  36. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +44 -1
  37. package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
  38. package/dist/components/load-failure.d.ts +1 -1
  39. package/dist/components/useRunAsUsers.d.ts +25 -0
  40. package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
  41. package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
  42. package/dist/index.es.js +16 -14
  43. package/dist/index.es.js.map +1 -1
  44. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  45. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  46. package/dist/sql_utils-BZD31xE3.js +478 -0
  47. package/dist/sql_utils-BZD31xE3.js.map +1 -0
  48. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  49. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  50. package/dist/utils/sql_utils.d.ts +150 -49
  51. package/package.json +9 -9
  52. package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
  53. package/dist/ApiKeysView-qLSs47tw.js +0 -1239
  54. package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
  55. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  56. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  57. package/dist/BackupsView-DYTAD6aW.js +0 -253
  58. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  59. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  60. package/dist/CronJobsView-7hjgSYId.js.map +0 -1
  61. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  62. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  63. package/dist/RLSEditor-Djh43lBE.js.map +0 -1
  64. package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
  65. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  66. package/dist/StorageView-CMG1DOVX.js.map +0 -1
  67. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  68. package/dist/load-failure-C6OMJSGC.js.map +0 -1
@@ -5,7 +5,7 @@ import { Alert, AlertTriangleIcon, Button, Chip, CircularProgress, DatabaseIcon,
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL, isPostgresCollectionConfig, policy } from "@rebasepro/types";
7
7
  import { getPolicyNamesForRule, getPolicyNamesForRules, getPolicyOperations } from "@rebasepro/utils";
8
- import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, getEffectiveSecurityRules, getJunctionSecurityRules, getTableName, policyToPostgres, policyToSecurityRule, resolveJunctionSpecs } from "@rebasepro/common";
8
+ import { JUNCTION_TABLES_SQL, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_USER_ROLE, compileRulePolicies, getEffectiveSecurityRules, getInjectedSecurityRules, getJunctionSecurityRules, getTableName, policyToPostgres, policyToSecurityRule, resolveJunctionSpecs } from "@rebasepro/common";
9
9
  //#region src/components/RLSEditor/policy-presets.ts
10
10
  /**
11
11
  * What the RLS editor offers before the user types anything: the roles a `TO`
@@ -241,6 +241,7 @@ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) =>
241
241
  })
242
242
  ]
243
243
  })] }), /* @__PURE__ */ jsx(IconButton, {
244
+ "aria-label": t("help"),
244
245
  size: "small",
245
246
  onClick: () => setHelpOpen(true),
246
247
  children: /* @__PURE__ */ jsx(HelpCircleIcon, { size: iconSize.smallest })
@@ -506,23 +507,20 @@ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) =>
506
507
  }),
507
508
  /* @__PURE__ */ jsxs("ul", {
508
509
  className: "list-disc pl-5 space-y-1 text-sm text-text-secondary dark:text-text-secondary-dark",
509
- children: [
510
- /* @__PURE__ */ jsxs("li", { children: [
511
- /* @__PURE__ */ jsx("strong", { children: "public" }),
512
- ": ",
513
- t("studio_policy_help_role_public")
514
- ] }),
515
- /* @__PURE__ */ jsxs("li", { children: [
516
- /* @__PURE__ */ jsx("strong", { children: "authenticated" }),
517
- ": ",
518
- t("studio_policy_help_role_authenticated")
519
- ] }),
520
- /* @__PURE__ */ jsxs("li", { children: [
521
- /* @__PURE__ */ jsx("strong", { children: "anon" }),
522
- ": ",
523
- t("studio_policy_help_role_anon")
524
- ] })
525
- ]
510
+ children: [/* @__PURE__ */ jsxs("li", { children: [
511
+ /* @__PURE__ */ jsx("strong", { children: "public" }),
512
+ ": ",
513
+ t("studio_policy_help_role_public")
514
+ ] }), /* @__PURE__ */ jsxs("li", { children: [
515
+ /* @__PURE__ */ jsx("strong", { children: REBASE_USER_ROLE }),
516
+ ": ",
517
+ t("studio_policy_help_role_rebase_user")
518
+ ] })]
519
+ }),
520
+ /* @__PURE__ */ jsx(Typography, {
521
+ variant: "body2",
522
+ className: "text-text-secondary dark:text-text-secondary-dark mt-1",
523
+ children: t("studio_policy_help_roles_not_users")
526
524
  })
527
525
  ]
528
526
  }),
@@ -690,7 +688,7 @@ async function saveSecurityRulesToCodebase(options) {
690
688
  const headers = { "Content-Type": "application/json" };
691
689
  const token = getAuthToken ? await getAuthToken() : null;
692
690
  if (token) headers["Authorization"] = `Bearer ${token}`;
693
- const response = await fetch(`${apiBase}/schema-editor/collection/save`, {
691
+ const response = await fetch(`${apiBase}/admin/schema-editor/collection/save`, {
694
692
  method: "POST",
695
693
  headers,
696
694
  body: JSON.stringify({
@@ -814,7 +812,7 @@ function editedRule(rule, original, edited) {
814
812
  result.operation = next.operation;
815
813
  }
816
814
  if (edited.permissive !== original.permissive) result.mode = next.mode;
817
- if (!sameRoles(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
815
+ if (!sameRoles$1(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
818
816
  else delete result.pgRoles;
819
817
  if ((edited.qual || null) !== (original.qual || null) || (edited.with_check || null) !== (original.with_check || null)) {
820
818
  delete result.ownerField;
@@ -828,12 +826,123 @@ function editedRule(rule, original, edited) {
828
826
  }
829
827
  return result;
830
828
  }
831
- function sameRoles(a, b) {
829
+ function sameRoles$1(a, b) {
832
830
  const left = [...a ?? []].sort();
833
831
  const right = [...b ?? []].sort();
834
832
  return left.length === right.length && left.every((role, i) => role === right[i]);
835
833
  }
836
834
  //#endregion
835
+ //#region src/components/RLSEditor/policyListing.ts
836
+ var COMMANDS = {
837
+ select: "SELECT",
838
+ insert: "INSERT",
839
+ update: "UPDATE",
840
+ delete: "DELETE",
841
+ all: "ALL"
842
+ };
843
+ /** A compiled policy, in the shape `pg_policies` describes one. */
844
+ function asPostgresPolicy(compiled, tableName) {
845
+ return {
846
+ policyname: compiled.name,
847
+ tablename: tableName,
848
+ permissive: compiled.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
849
+ cmd: COMMANDS[compiled.operation],
850
+ roles: compiled.roles,
851
+ qual: compiled.using,
852
+ with_check: compiled.withCheck
853
+ };
854
+ }
855
+ /**
856
+ * The rule's own clauses, for a rule the compiler could not compile — an
857
+ * `existsIn` naming a collection the registry does not hold, say. Only ever a
858
+ * `code_only` row's content: nothing is compared against it.
859
+ */
860
+ function uncompiledPolicy(rule, operation, name, tableName) {
861
+ return {
862
+ policyname: name,
863
+ tablename: tableName,
864
+ permissive: rule.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
865
+ cmd: COMMANDS[operation],
866
+ roles: [...rule.pgRoles ?? ["public"]],
867
+ qual: rule.using || null,
868
+ with_check: rule.withCheck || null
869
+ };
870
+ }
871
+ function sameRoles(a, b) {
872
+ const left = [...a].sort();
873
+ const right = [...b].sort();
874
+ return left.length === right.length && left.every((role, i) => role === right[i]);
875
+ }
876
+ /**
877
+ * Where a live policy differs from the one the code compiles to.
878
+ *
879
+ * The same comparison `rebase doctor` makes: the `TO` list, the command, the
880
+ * mode, and whether each clause is there at all. Never the clause text —
881
+ * Postgres rewrites an expression when it stores it, so the text of a policy
882
+ * that matches its declaration exactly still differs from the declaration's.
883
+ * A clause that is missing is not a rewrite, though: a `USING` that is gone
884
+ * changes who the policy admits.
885
+ */
886
+ function policyDrift(declared, live) {
887
+ const drift = [];
888
+ if (!sameRoles(declared.roles, live.roles)) drift.push("roles");
889
+ if (declared.cmd !== live.cmd) drift.push("command");
890
+ if (declared.permissive !== live.permissive) drift.push("mode");
891
+ if (declared.qual != null !== (live.qual != null)) drift.push("USING");
892
+ if (declared.with_check != null !== (live.with_check != null)) drift.push("WITH CHECK");
893
+ return drift;
894
+ }
895
+ /**
896
+ * The policies of one table: every policy the database has, as the database
897
+ * has it, and every policy the code declares for the table that it does not.
898
+ *
899
+ * A policy in both keeps the database's fields — this list is what Postgres
900
+ * enforces — and carries the declaration beside them, with where the two
901
+ * differ.
902
+ *
903
+ * @param live The table's `pg_policies` rows.
904
+ * @param collection The collection the table belongs to, if any.
905
+ * @param collections Every collection, to resolve the ones a rule refers to.
906
+ */
907
+ function listPolicies(live, collection, tableName, collections) {
908
+ const listed = /* @__PURE__ */ new Map();
909
+ for (const policy of live) listed.set(policy.policyname, {
910
+ ...policy,
911
+ status: "live"
912
+ });
913
+ if (!collection || !isPostgresCollectionConfig(collection)) return [...listed.values()];
914
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
915
+ const injectedNames = getPolicyNamesForRules(getInjectedSecurityRules(collection), tableName);
916
+ const livePolicies = new Map(live.map((policy) => [policy.policyname, policy]));
917
+ for (const rule of getEffectiveSecurityRules(collection)) {
918
+ const operations = getPolicyOperations(rule);
919
+ let compiled;
920
+ try {
921
+ compiled = compileRulePolicies(collection, rule, { resolveCollection });
922
+ } catch {
923
+ compiled = void 0;
924
+ }
925
+ getPolicyNamesForRule(rule, tableName).forEach((name, index) => {
926
+ const declared = compiled ? asPostgresPolicy(compiled[index], tableName) : void 0;
927
+ const generated = injectedNames.has(name);
928
+ const existing = livePolicies.get(name);
929
+ if (existing) listed.set(name, {
930
+ ...existing,
931
+ status: "both",
932
+ declared,
933
+ drift: declared ? policyDrift(declared, existing) : void 0,
934
+ generated
935
+ });
936
+ else listed.set(name, {
937
+ ...declared ?? uncompiledPolicy(rule, operations[index] ?? "all", name, tableName),
938
+ status: "code_only",
939
+ generated
940
+ });
941
+ });
942
+ }
943
+ return [...listed.values()];
944
+ }
945
+ //#endregion
837
946
  //#region src/components/RLSEditor/RLSEditor.tsx
838
947
  /**
839
948
  * Whether `collection` is the one `db push` writes `table` for: the table
@@ -1158,10 +1267,9 @@ var RLSEditor = ({ apiUrl = "" }) => {
1158
1267
  const jRows = extractRows(await databaseAdmin.executeSql(JUNCTION_TABLES_SQL));
1159
1268
  setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
1160
1269
  } catch {}
1161
- if (sortedTables.length > 0 && !selectedTable) {
1162
- const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
1163
- if (firstUserTable) setSelectedTable(`${firstUserTable.schemaName}.${firstUserTable.tableName}`);
1164
- else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
1270
+ if (sortedTables.length > 0) {
1271
+ const firstUserTable = sortedTables.find((t) => !REBASE_INTERNAL_SCHEMAS.includes(t.schemaName)) ?? sortedTables[0];
1272
+ setSelectedTable((previous) => previous ?? `${firstUserTable.schemaName}.${firstUserTable.tableName}`);
1165
1273
  }
1166
1274
  } catch (e) {
1167
1275
  console.error("RLS fetch error:", e);
@@ -1169,7 +1277,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1169
1277
  } finally {
1170
1278
  setIsLoading(false);
1171
1279
  }
1172
- }, [databaseAdmin, selectedTable]);
1280
+ }, [databaseAdmin]);
1173
1281
  useEffect(() => {
1174
1282
  setEditingPolicy(null);
1175
1283
  }, [selectedTable]);
@@ -1213,41 +1321,55 @@ var RLSEditor = ({ apiUrl = "" }) => {
1213
1321
  activeCollection,
1214
1322
  junctionTableNames
1215
1323
  ]);
1324
+ /**
1325
+ * The junction a relation derives this table as, when it is one. Its
1326
+ * policies are Rebase's — derived from the endpoints' relations — and the
1327
+ * server re-creates them on every start, like a collection's. `null` when
1328
+ * the registry's collections carry no resolvable relations (serialized
1329
+ * configs without relation closures).
1330
+ */
1331
+ const activeJunctionSpec = useMemo(() => {
1332
+ if (!activeTableData || activeCollection) return null;
1333
+ try {
1334
+ return resolveJunctionSpecs(collectionRegistry.collections ?? []).get(activeTableData.tableName) ?? null;
1335
+ } catch {
1336
+ return null;
1337
+ }
1338
+ }, [
1339
+ activeTableData,
1340
+ activeCollection,
1341
+ collectionRegistry.collections
1342
+ ]);
1343
+ /**
1344
+ * Whether the server re-asserts this table's RLS on every start: it switches
1345
+ * RLS back on and re-creates every policy the project declares for it.
1346
+ */
1347
+ const activeTableManaged = Boolean(activeCollection || activeJunctionSpec);
1348
+ /**
1349
+ * What Postgres enforces on the selected table, and what the code declares
1350
+ * that it does not have yet.
1351
+ *
1352
+ * A policy in both is listed with the *database's* fields, and the
1353
+ * declaration rides beside it with where the two differ (`listPolicies`).
1354
+ * The declaration's fields in their place would make a policy changed in
1355
+ * SQL look exactly like its declaration, and would show every rule written
1356
+ * as a structured condition — all of Rebase's own policies — with no USING.
1357
+ */
1216
1358
  const mergedPolicies = useMemo(() => {
1217
1359
  if (!activeTableData) return [];
1218
- const policiesMap = {};
1219
- (activeTableData.policies || []).forEach((p) => {
1220
- policiesMap[p.policyname] = {
1221
- ...p,
1222
- status: "live"
1223
- };
1224
- });
1225
- if (activeCollection && isPostgresCollectionConfig(activeCollection)) getEffectiveSecurityRules(activeCollection).forEach((rule) => {
1226
- const ops = getPolicyOperations(rule);
1227
- getPolicyNamesForRule(rule, activeTableData.tableName).forEach((policyName, opIdx) => {
1228
- policiesMap[policyName] = {
1229
- policyname: policyName,
1230
- tablename: activeTableData.tableName,
1231
- permissive: (rule.mode || "permissive").toUpperCase(),
1232
- cmd: (ops[opIdx] ?? rule.operation ?? "ALL").toUpperCase(),
1233
- roles: [...rule.pgRoles ?? ["public"]],
1234
- qual: rule.using || null,
1235
- with_check: rule.withCheck || null,
1236
- status: policiesMap[policyName] ? "both" : "code_only"
1237
- };
1238
- });
1239
- });
1240
- if (!activeCollection) try {
1241
- const spec = resolveJunctionSpecs(collectionRegistry.collections ?? []).get(activeTableData.tableName);
1242
- if (spec) {
1243
- const generatedNames = getPolicyNamesForRules(getJunctionSecurityRules(spec), spec.table);
1244
- for (const p of Object.values(policiesMap)) if (generatedNames.has(p.policyname)) p.status = "both";
1360
+ const listed = listPolicies(activeTableData.policies || [], activeCollection, activeTableData.tableName, collectionRegistry.collections ?? []);
1361
+ if (activeJunctionSpec) {
1362
+ const generatedNames = getPolicyNamesForRules(getJunctionSecurityRules(activeJunctionSpec), activeJunctionSpec.table);
1363
+ for (const p of listed) if (generatedNames.has(p.policyname)) {
1364
+ p.status = "both";
1365
+ p.generated = true;
1245
1366
  }
1246
- } catch {}
1247
- return Object.values(policiesMap).sort((a, b) => a.policyname.localeCompare(b.policyname));
1367
+ }
1368
+ return listed.sort((a, b) => a.policyname.localeCompare(b.policyname));
1248
1369
  }, [
1249
1370
  activeTableData,
1250
1371
  activeCollection,
1372
+ activeJunctionSpec,
1251
1373
  collectionRegistry.collections
1252
1374
  ]);
1253
1375
  const rlsStats = useMemo(() => {
@@ -1274,6 +1396,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1274
1396
  className: "flex h-full w-full bg-surface-card overflow-hidden text-text-primary dark:text-text-primary-dark",
1275
1397
  children: [
1276
1398
  /* @__PURE__ */ jsx(ResizablePanels, {
1399
+ dividerAriaLabel: t("resize_sidebar"),
1277
1400
  orientation: "horizontal",
1278
1401
  panelSizePercent: sidebarSize,
1279
1402
  onPanelSizeChange: setSidebarSize,
@@ -1303,9 +1426,10 @@ var RLSEditor = ({ apiUrl = "" }) => {
1303
1426
  className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
1304
1427
  children: t("studio_schema_tables")
1305
1428
  }), /* @__PURE__ */ jsx(IconButton, {
1429
+ "aria-label": t("refresh"),
1306
1430
  size: "small",
1307
1431
  onClick: fetchRLSData,
1308
- title: "Refresh",
1432
+ title: t("refresh"),
1309
1433
  children: /* @__PURE__ */ jsx(RefreshCwIcon, { size: iconSize.smallest })
1310
1434
  })]
1311
1435
  }), /* @__PURE__ */ jsx("div", {
@@ -1548,7 +1672,8 @@ var RLSEditor = ({ apiUrl = "" }) => {
1548
1672
  onClick: () => setConfirmToggleRls({
1549
1673
  table: activeTableData.tableName,
1550
1674
  schema: activeTableData.schemaName,
1551
- enabled: activeTableData.rlsEnabled
1675
+ enabled: activeTableData.rlsEnabled,
1676
+ managed: activeTableManaged
1552
1677
  }),
1553
1678
  children: activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")
1554
1679
  }),
@@ -1619,6 +1744,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
1619
1744
  return;
1620
1745
  }
1621
1746
  newRules = edit.rules;
1747
+ if (JSON.stringify(newRules) === JSON.stringify(existingRules)) {
1748
+ snackbarController.open({
1749
+ type: "info",
1750
+ message: t("studio_rls_nothing_changed")
1751
+ });
1752
+ return;
1753
+ }
1622
1754
  }
1623
1755
  try {
1624
1756
  await saveSecurityRules(activeCollection.slug, newRules);
@@ -1631,31 +1763,40 @@ var RLSEditor = ({ apiUrl = "" }) => {
1631
1763
  } catch (e) {
1632
1764
  reportSaveFailure(e);
1633
1765
  }
1634
- } else try {
1635
- const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
1636
- const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
1637
- const cmd = newPolicy.cmd || "ALL";
1638
- const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1639
- const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1640
- let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1641
- sql += ` AS ${permissive}`;
1642
- sql += ` FOR ${cmd}`;
1643
- sql += ` TO ${roles}`;
1644
- if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1645
- if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1646
- if (editingPolicy !== "new") sql = `DROP POLICY IF EXISTS ${sanitizeSqlIdentifier(editingPolicy.policyname)} ON ${qualifiedTable}; ${sql}`;
1647
- await databaseAdmin.executeSql(sql);
1648
- snackbarController.open({
1649
- type: "success",
1650
- message: `Policy "${newPolicy.policyname}" applied to the database`
1651
- });
1652
- setEditingPolicy(null);
1653
- fetchRLSData();
1654
- } catch (e) {
1655
- snackbarController.open({
1656
- type: "error",
1657
- message: e instanceof Error ? e.message : String(e)
1658
- });
1766
+ } else {
1767
+ if (editingPolicy !== "new" && editingPolicy.generated) {
1768
+ snackbarController.open({
1769
+ type: "error",
1770
+ message: t(POLICY_EDIT_REFUSALS.generated, { policy: editingPolicy.policyname })
1771
+ });
1772
+ return;
1773
+ }
1774
+ try {
1775
+ const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
1776
+ const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
1777
+ const cmd = newPolicy.cmd || "ALL";
1778
+ const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1779
+ const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1780
+ let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1781
+ sql += ` AS ${permissive}`;
1782
+ sql += ` FOR ${cmd}`;
1783
+ sql += ` TO ${roles}`;
1784
+ if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1785
+ if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1786
+ if (editingPolicy !== "new") sql = `DROP POLICY IF EXISTS ${sanitizeSqlIdentifier(editingPolicy.policyname)} ON ${qualifiedTable}; ${sql}`;
1787
+ await databaseAdmin.executeSql(sql);
1788
+ snackbarController.open({
1789
+ type: "success",
1790
+ message: `Policy "${newPolicy.policyname}" applied to the database`
1791
+ });
1792
+ setEditingPolicy(null);
1793
+ fetchRLSData();
1794
+ } catch (e) {
1795
+ snackbarController.open({
1796
+ type: "error",
1797
+ message: e instanceof Error ? e.message : String(e)
1798
+ });
1799
+ }
1659
1800
  }
1660
1801
  },
1661
1802
  onCancel: () => setEditingPolicy(null)
@@ -1666,6 +1807,19 @@ var RLSEditor = ({ apiUrl = "" }) => {
1666
1807
  children: /* @__PURE__ */ jsxs("div", {
1667
1808
  className: "max-w-4xl mx-auto flex flex-col gap-6",
1668
1809
  children: [
1810
+ activeTableData && activeTableManaged && !(activeCollection && hasCodebase) && /* @__PURE__ */ jsx(Alert, {
1811
+ color: "info",
1812
+ children: /* @__PURE__ */ jsxs("div", {
1813
+ className: "flex items-start gap-2",
1814
+ children: [/* @__PURE__ */ jsx(RefreshCwIcon, {
1815
+ size: 16,
1816
+ className: "shrink-0 mt-0.5"
1817
+ }), /* @__PURE__ */ jsx(Typography, {
1818
+ variant: "caption",
1819
+ children: t("studio_rls_reapplied_on_start", { table: activeTableData.tableName })
1820
+ })]
1821
+ })
1822
+ }),
1669
1823
  activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
1670
1824
  color: "info",
1671
1825
  children: /* @__PURE__ */ jsxs("div", {
@@ -1775,6 +1929,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
1775
1929
  children: t("studio_rls_unapplied")
1776
1930
  })
1777
1931
  }),
1932
+ policy.drift && policy.drift.length > 0 && /* @__PURE__ */ jsx(Tooltip, {
1933
+ title: t("studio_rls_drift_tooltip", { fields: policy.drift.join(", ") }),
1934
+ children: /* @__PURE__ */ jsx("div", {
1935
+ className: "px-1.5 py-0.5 rounded text-[10px] bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0",
1936
+ children: `${t("studio_rls_drift")}: ${policy.drift.join(", ")}`
1937
+ })
1938
+ }),
1778
1939
  policy.status === "live" && hasCodebase && /* @__PURE__ */ jsx(Tooltip, {
1779
1940
  title: "This policy is live in the database but missing from your codebase schema.",
1780
1941
  children: /* @__PURE__ */ jsx("div", {
@@ -1821,6 +1982,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1821
1982
  title: t("studio_rls_delete"),
1822
1983
  asChild: true,
1823
1984
  children: /* @__PURE__ */ jsx(IconButton, {
1985
+ "aria-label": t("studio_rls_delete"),
1824
1986
  size: "small",
1825
1987
  onClick: () => setConfirmDropPolicy({
1826
1988
  policyName: policy.policyname,
@@ -1891,7 +2053,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1891
2053
  onAccept: applyToggleRls,
1892
2054
  onCancel: () => setConfirmToggleRls(null),
1893
2055
  title: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_title") : t("studio_rls_enable_confirm_title"),
1894
- body: confirmToggleRls?.enabled ? t("studio_rls_disable_confirm_body", { table: confirmToggleRls?.table ?? "" }) : t("studio_rls_enable_confirm_body", { table: confirmToggleRls?.table ?? "" })
2056
+ body: confirmToggleRls?.enabled ? [t("studio_rls_disable_confirm_body", { table: confirmToggleRls.table }), confirmToggleRls.managed ? t("studio_rls_disable_reapplied", { table: confirmToggleRls.table }) : ""].filter(Boolean).join(" ") : t("studio_rls_enable_confirm_body", { table: confirmToggleRls?.table ?? "" })
1895
2057
  }),
1896
2058
  /* @__PURE__ */ jsx(ConfirmationDialog, {
1897
2059
  open: confirmDropPolicy !== null,
@@ -1910,4 +2072,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
1910
2072
  //#endregion
1911
2073
  export { RLSEditor };
1912
2074
 
1913
- //# sourceMappingURL=RLSEditor-Djh43lBE.js.map
2075
+ //# sourceMappingURL=RLSEditor-jFD_d06f.js.map