@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.
- package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-CCtczj3F.js} +15 -28
- package/dist/ApiExplorer-CCtczj3F.js.map +1 -0
- package/dist/ApiKeysView-B66TdzhH.js +1738 -0
- package/dist/ApiKeysView-B66TdzhH.js.map +1 -0
- package/dist/BackupsView-DmSMzAbT.js +368 -0
- package/dist/BackupsView-DmSMzAbT.js.map +1 -0
- package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-DP1a87E-.js} +12 -8
- package/dist/BranchesView-DP1a87E-.js.map +1 -0
- package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-TDErHTuK.js} +126 -60
- package/dist/CronJobsView-TDErHTuK.js.map +1 -0
- package/dist/{JSEditor-qiuwmcvk.js → JSEditor-B59pwg4c.js} +120 -74
- package/dist/JSEditor-B59pwg4c.js.map +1 -0
- package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-BKM4yfP8.js} +48 -22
- package/dist/LogsExplorer-BKM4yfP8.js.map +1 -0
- package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-BlLLV6xs.js} +237 -77
- package/dist/RLSEditor-BlLLV6xs.js.map +1 -0
- package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-1An7w0j6.js} +369 -294
- package/dist/SQLEditor-1An7w0j6.js.map +1 -0
- package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-DCLVfA5t.js} +20 -24
- package/dist/SchemaVisualizer-DCLVfA5t.js.map +1 -0
- package/dist/{StorageView-CMG1DOVX.js → StorageView-CbsHuCBf.js} +355 -225
- package/dist/StorageView-CbsHuCBf.js.map +1 -0
- package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
- package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
- package/dist/components/ApiKeys/scope-words.d.ts +37 -0
- package/dist/components/ApiKeys/scopes.d.ts +113 -0
- package/dist/components/AuthSimulationSelector.d.ts +5 -1
- package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
- package/dist/components/RLSEditor/policyListing.d.ts +44 -0
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +0 -1
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +6 -0
- package/dist/components/load-failure.d.ts +1 -1
- package/dist/components/useRunAsUsers.d.ts +25 -0
- package/dist/index.es.js +11 -11
- package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
- package/dist/load-failure-Bd8lFIO7.js.map +1 -0
- package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
- package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
- package/dist/utils/sql_utils.d.ts +90 -49
- package/package.json +9 -9
- package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
- package/dist/ApiKeysView-qLSs47tw.js +0 -1239
- package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
- package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
- package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
- package/dist/BackupsView-DYTAD6aW.js +0 -253
- package/dist/BackupsView-DYTAD6aW.js.map +0 -1
- package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
- package/dist/CronJobsView-7hjgSYId.js.map +0 -1
- package/dist/JSEditor-qiuwmcvk.js.map +0 -1
- package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
- package/dist/RLSEditor-Djh43lBE.js.map +0 -1
- package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
- package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
- package/dist/StorageView-CMG1DOVX.js.map +0 -1
- package/dist/components/ApiKeys/permissions.d.ts +0 -66
- 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__ */
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
status
|
|
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
|
-
}
|
|
1247
|
-
return
|
|
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
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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
|
|
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-
|
|
2073
|
+
//# sourceMappingURL=RLSEditor-BlLLV6xs.js.map
|