@rebasepro/studio 0.23.0 → 0.24.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 (57) hide show
  1. package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-CCtczj3F.js} +15 -28
  2. package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
  3. package/dist/ApiKeysView-B66TdzhH.js +1738 -0
  4. package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
  5. package/dist/BackupsView-DmSMzAbT.js +368 -0
  6. package/dist/BackupsView-DmSMzAbT.js.map +1 -0
  7. package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
  8. package/dist/BranchesView-DP1a87E-.js.map +1 -0
  9. package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-TDErHTuK.js} +126 -60
  10. package/dist/CronJobsView-TDErHTuK.js.map +1 -0
  11. package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
  12. package/dist/JSEditor-B59pwg4c.js.map +1 -0
  13. package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
  14. package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
  15. package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-BlLLV6xs.js} +237 -77
  16. package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
  17. package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-1An7w0j6.js} +369 -294
  18. package/dist/SQLEditor-1An7w0j6.js.map +1 -0
  19. package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
  20. package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
  21. package/dist/{StorageView-CMG1DOVX.js → StorageView-CbsHuCBf.js} +355 -225
  22. package/dist/StorageView-CbsHuCBf.js.map +1 -0
  23. package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
  24. package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
  25. package/dist/components/ApiKeys/scope-words.d.ts +37 -0
  26. package/dist/components/ApiKeys/scopes.d.ts +113 -0
  27. package/dist/components/AuthSimulationSelector.d.ts +5 -1
  28. package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
  29. package/dist/components/RLSEditor/policyListing.d.ts +44 -0
  30. package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
  31. package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
  32. package/dist/components/load-failure.d.ts +1 -1
  33. package/dist/components/useRunAsUsers.d.ts +25 -0
  34. package/dist/index.es.js +11 -11
  35. package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
  36. package/dist/load-failure-Bd8lFIO7.js.map +1 -0
  37. package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
  38. package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
  39. package/dist/utils/sql_utils.d.ts +90 -49
  40. package/package.json +9 -9
  41. package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
  42. package/dist/ApiKeysView-qLSs47tw.js +0 -1239
  43. package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
  44. package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
  45. package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
  46. package/dist/BackupsView-DYTAD6aW.js +0 -253
  47. package/dist/BackupsView-DYTAD6aW.js.map +0 -1
  48. package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
  49. package/dist/CronJobsView-7hjgSYId.js.map +0 -1
  50. package/dist/JSEditor-qiuwmcvk.js.map +0 -1
  51. package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
  52. package/dist/RLSEditor-Djh43lBE.js.map +0 -1
  53. package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
  54. package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
  55. package/dist/StorageView-CMG1DOVX.js.map +0 -1
  56. package/dist/components/ApiKeys/permissions.d.ts +0 -66
  57. 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`
@@ -506,23 +506,20 @@ var PolicyEditor = ({ policy, schema, table, roleOptions, onSave, onCancel }) =>
506
506
  }),
507
507
  /* @__PURE__ */ jsxs("ul", {
508
508
  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
- ]
509
+ children: [/* @__PURE__ */ jsxs("li", { children: [
510
+ /* @__PURE__ */ jsx("strong", { children: "public" }),
511
+ ": ",
512
+ t("studio_policy_help_role_public")
513
+ ] }), /* @__PURE__ */ jsxs("li", { children: [
514
+ /* @__PURE__ */ jsx("strong", { children: REBASE_USER_ROLE }),
515
+ ": ",
516
+ t("studio_policy_help_role_rebase_user")
517
+ ] })]
518
+ }),
519
+ /* @__PURE__ */ jsx(Typography, {
520
+ variant: "body2",
521
+ className: "text-text-secondary dark:text-text-secondary-dark mt-1",
522
+ children: t("studio_policy_help_roles_not_users")
526
523
  })
527
524
  ]
528
525
  }),
@@ -814,7 +811,7 @@ function editedRule(rule, original, edited) {
814
811
  result.operation = next.operation;
815
812
  }
816
813
  if (edited.permissive !== original.permissive) result.mode = next.mode;
817
- if (!sameRoles(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
814
+ if (!sameRoles$1(edited.roles, original.roles)) if (next.pgRoles) result.pgRoles = next.pgRoles;
818
815
  else delete result.pgRoles;
819
816
  if ((edited.qual || null) !== (original.qual || null) || (edited.with_check || null) !== (original.with_check || null)) {
820
817
  delete result.ownerField;
@@ -828,12 +825,123 @@ function editedRule(rule, original, edited) {
828
825
  }
829
826
  return result;
830
827
  }
831
- function sameRoles(a, b) {
828
+ function sameRoles$1(a, b) {
832
829
  const left = [...a ?? []].sort();
833
830
  const right = [...b ?? []].sort();
834
831
  return left.length === right.length && left.every((role, i) => role === right[i]);
835
832
  }
836
833
  //#endregion
834
+ //#region src/components/RLSEditor/policyListing.ts
835
+ var COMMANDS = {
836
+ select: "SELECT",
837
+ insert: "INSERT",
838
+ update: "UPDATE",
839
+ delete: "DELETE",
840
+ all: "ALL"
841
+ };
842
+ /** A compiled policy, in the shape `pg_policies` describes one. */
843
+ function asPostgresPolicy(compiled, tableName) {
844
+ return {
845
+ policyname: compiled.name,
846
+ tablename: tableName,
847
+ permissive: compiled.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
848
+ cmd: COMMANDS[compiled.operation],
849
+ roles: compiled.roles,
850
+ qual: compiled.using,
851
+ with_check: compiled.withCheck
852
+ };
853
+ }
854
+ /**
855
+ * The rule's own clauses, for a rule the compiler could not compile — an
856
+ * `existsIn` naming a collection the registry does not hold, say. Only ever a
857
+ * `code_only` row's content: nothing is compared against it.
858
+ */
859
+ function uncompiledPolicy(rule, operation, name, tableName) {
860
+ return {
861
+ policyname: name,
862
+ tablename: tableName,
863
+ permissive: rule.mode === "restrictive" ? "RESTRICTIVE" : "PERMISSIVE",
864
+ cmd: COMMANDS[operation],
865
+ roles: [...rule.pgRoles ?? ["public"]],
866
+ qual: rule.using || null,
867
+ with_check: rule.withCheck || null
868
+ };
869
+ }
870
+ function sameRoles(a, b) {
871
+ const left = [...a].sort();
872
+ const right = [...b].sort();
873
+ return left.length === right.length && left.every((role, i) => role === right[i]);
874
+ }
875
+ /**
876
+ * Where a live policy differs from the one the code compiles to.
877
+ *
878
+ * The same comparison `rebase doctor` makes: the `TO` list, the command, the
879
+ * mode, and whether each clause is there at all. Never the clause text —
880
+ * Postgres rewrites an expression when it stores it, so the text of a policy
881
+ * that matches its declaration exactly still differs from the declaration's.
882
+ * A clause that is missing is not a rewrite, though: a `USING` that is gone
883
+ * changes who the policy admits.
884
+ */
885
+ function policyDrift(declared, live) {
886
+ const drift = [];
887
+ if (!sameRoles(declared.roles, live.roles)) drift.push("roles");
888
+ if (declared.cmd !== live.cmd) drift.push("command");
889
+ if (declared.permissive !== live.permissive) drift.push("mode");
890
+ if (declared.qual != null !== (live.qual != null)) drift.push("USING");
891
+ if (declared.with_check != null !== (live.with_check != null)) drift.push("WITH CHECK");
892
+ return drift;
893
+ }
894
+ /**
895
+ * The policies of one table: every policy the database has, as the database
896
+ * has it, and every policy the code declares for the table that it does not.
897
+ *
898
+ * A policy in both keeps the database's fields — this list is what Postgres
899
+ * enforces — and carries the declaration beside them, with where the two
900
+ * differ.
901
+ *
902
+ * @param live The table's `pg_policies` rows.
903
+ * @param collection The collection the table belongs to, if any.
904
+ * @param collections Every collection, to resolve the ones a rule refers to.
905
+ */
906
+ function listPolicies(live, collection, tableName, collections) {
907
+ const listed = /* @__PURE__ */ new Map();
908
+ for (const policy of live) listed.set(policy.policyname, {
909
+ ...policy,
910
+ status: "live"
911
+ });
912
+ if (!collection || !isPostgresCollectionConfig(collection)) return [...listed.values()];
913
+ const resolveCollection = (slug) => collections.find((c) => c.slug === slug || getTableName(c) === slug);
914
+ const injectedNames = getPolicyNamesForRules(getInjectedSecurityRules(collection), tableName);
915
+ const livePolicies = new Map(live.map((policy) => [policy.policyname, policy]));
916
+ for (const rule of getEffectiveSecurityRules(collection)) {
917
+ const operations = getPolicyOperations(rule);
918
+ let compiled;
919
+ try {
920
+ compiled = compileRulePolicies(collection, rule, { resolveCollection });
921
+ } catch {
922
+ compiled = void 0;
923
+ }
924
+ getPolicyNamesForRule(rule, tableName).forEach((name, index) => {
925
+ const declared = compiled ? asPostgresPolicy(compiled[index], tableName) : void 0;
926
+ const generated = injectedNames.has(name);
927
+ const existing = livePolicies.get(name);
928
+ if (existing) listed.set(name, {
929
+ ...existing,
930
+ status: "both",
931
+ declared,
932
+ drift: declared ? policyDrift(declared, existing) : void 0,
933
+ generated
934
+ });
935
+ else listed.set(name, {
936
+ ...declared ?? uncompiledPolicy(rule, operations[index] ?? "all", name, tableName),
937
+ status: "code_only",
938
+ generated
939
+ });
940
+ });
941
+ }
942
+ return [...listed.values()];
943
+ }
944
+ //#endregion
837
945
  //#region src/components/RLSEditor/RLSEditor.tsx
838
946
  /**
839
947
  * Whether `collection` is the one `db push` writes `table` for: the table
@@ -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(() => {
@@ -1548,7 +1670,8 @@ var RLSEditor = ({ apiUrl = "" }) => {
1548
1670
  onClick: () => setConfirmToggleRls({
1549
1671
  table: activeTableData.tableName,
1550
1672
  schema: activeTableData.schemaName,
1551
- enabled: activeTableData.rlsEnabled
1673
+ enabled: activeTableData.rlsEnabled,
1674
+ managed: activeTableManaged
1552
1675
  }),
1553
1676
  children: activeTableData.rlsEnabled ? t("studio_rls_disable_rls") : t("studio_rls_enable_rls")
1554
1677
  }),
@@ -1619,6 +1742,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
1619
1742
  return;
1620
1743
  }
1621
1744
  newRules = edit.rules;
1745
+ if (JSON.stringify(newRules) === JSON.stringify(existingRules)) {
1746
+ snackbarController.open({
1747
+ type: "info",
1748
+ message: t("studio_rls_nothing_changed")
1749
+ });
1750
+ return;
1751
+ }
1622
1752
  }
1623
1753
  try {
1624
1754
  await saveSecurityRules(activeCollection.slug, newRules);
@@ -1631,31 +1761,40 @@ var RLSEditor = ({ apiUrl = "" }) => {
1631
1761
  } catch (e) {
1632
1762
  reportSaveFailure(e);
1633
1763
  }
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
- });
1764
+ } else {
1765
+ if (editingPolicy !== "new" && editingPolicy.generated) {
1766
+ snackbarController.open({
1767
+ type: "error",
1768
+ message: t(POLICY_EDIT_REFUSALS.generated, { policy: editingPolicy.policyname })
1769
+ });
1770
+ return;
1771
+ }
1772
+ try {
1773
+ const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
1774
+ const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
1775
+ const cmd = newPolicy.cmd || "ALL";
1776
+ const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
1777
+ const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
1778
+ let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
1779
+ sql += ` AS ${permissive}`;
1780
+ sql += ` FOR ${cmd}`;
1781
+ sql += ` TO ${roles}`;
1782
+ if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
1783
+ if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
1784
+ if (editingPolicy !== "new") sql = `DROP POLICY IF EXISTS ${sanitizeSqlIdentifier(editingPolicy.policyname)} ON ${qualifiedTable}; ${sql}`;
1785
+ await databaseAdmin.executeSql(sql);
1786
+ snackbarController.open({
1787
+ type: "success",
1788
+ message: `Policy "${newPolicy.policyname}" applied to the database`
1789
+ });
1790
+ setEditingPolicy(null);
1791
+ fetchRLSData();
1792
+ } catch (e) {
1793
+ snackbarController.open({
1794
+ type: "error",
1795
+ message: e instanceof Error ? e.message : String(e)
1796
+ });
1797
+ }
1659
1798
  }
1660
1799
  },
1661
1800
  onCancel: () => setEditingPolicy(null)
@@ -1666,6 +1805,19 @@ var RLSEditor = ({ apiUrl = "" }) => {
1666
1805
  children: /* @__PURE__ */ jsxs("div", {
1667
1806
  className: "max-w-4xl mx-auto flex flex-col gap-6",
1668
1807
  children: [
1808
+ activeTableData && activeTableManaged && !(activeCollection && hasCodebase) && /* @__PURE__ */ jsx(Alert, {
1809
+ color: "info",
1810
+ children: /* @__PURE__ */ jsxs("div", {
1811
+ className: "flex items-start gap-2",
1812
+ children: [/* @__PURE__ */ jsx(RefreshCwIcon, {
1813
+ size: 16,
1814
+ className: "shrink-0 mt-0.5"
1815
+ }), /* @__PURE__ */ jsx(Typography, {
1816
+ variant: "caption",
1817
+ children: t("studio_rls_reapplied_on_start", { table: activeTableData.tableName })
1818
+ })]
1819
+ })
1820
+ }),
1669
1821
  activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ jsx(Alert, {
1670
1822
  color: "info",
1671
1823
  children: /* @__PURE__ */ jsxs("div", {
@@ -1775,6 +1927,13 @@ var RLSEditor = ({ apiUrl = "" }) => {
1775
1927
  children: t("studio_rls_unapplied")
1776
1928
  })
1777
1929
  }),
1930
+ policy.drift && policy.drift.length > 0 && /* @__PURE__ */ jsx(Tooltip, {
1931
+ title: t("studio_rls_drift_tooltip", { fields: policy.drift.join(", ") }),
1932
+ children: /* @__PURE__ */ jsx("div", {
1933
+ className: "px-1.5 py-0.5 rounded text-[10px] bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0",
1934
+ children: `${t("studio_rls_drift")}: ${policy.drift.join(", ")}`
1935
+ })
1936
+ }),
1778
1937
  policy.status === "live" && hasCodebase && /* @__PURE__ */ jsx(Tooltip, {
1779
1938
  title: "This policy is live in the database but missing from your codebase schema.",
1780
1939
  children: /* @__PURE__ */ jsx("div", {
@@ -1821,6 +1980,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1821
1980
  title: t("studio_rls_delete"),
1822
1981
  asChild: true,
1823
1982
  children: /* @__PURE__ */ jsx(IconButton, {
1983
+ "aria-label": t("studio_rls_delete"),
1824
1984
  size: "small",
1825
1985
  onClick: () => setConfirmDropPolicy({
1826
1986
  policyName: policy.policyname,
@@ -1891,7 +2051,7 @@ var RLSEditor = ({ apiUrl = "" }) => {
1891
2051
  onAccept: applyToggleRls,
1892
2052
  onCancel: () => setConfirmToggleRls(null),
1893
2053
  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 ?? "" })
2054
+ 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
2055
  }),
1896
2056
  /* @__PURE__ */ jsx(ConfirmationDialog, {
1897
2057
  open: confirmDropPolicy !== null,
@@ -1910,4 +2070,4 @@ var RLSEditor = ({ apiUrl = "" }) => {
1910
2070
  //#endregion
1911
2071
  export { RLSEditor };
1912
2072
 
1913
- //# sourceMappingURL=RLSEditor-Djh43lBE.js.map
2073
+ //# sourceMappingURL=RLSEditor-BlLLV6xs.js.map