@rebasepro/studio 0.17.3 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -1
- package/README.md +12 -4
- package/dist/{ApiKeysView-DiCurTEU.js → ApiKeysView-EIvegrUk.js} +54 -39
- package/dist/ApiKeysView-EIvegrUk.js.map +1 -0
- package/dist/{BackupsView-DNS6LdVg.js → BackupsView-DVkKzaPR.js} +29 -31
- package/dist/BackupsView-DVkKzaPR.js.map +1 -0
- package/dist/{BranchesView-CM3B-ER3.js → BranchesView-Df8hQ8wE.js} +85 -24
- package/dist/BranchesView-Df8hQ8wE.js.map +1 -0
- package/dist/{CronJobsView-BHtJZeJZ.js → CronJobsView-CLOGjb-L.js} +32 -27
- package/dist/CronJobsView-CLOGjb-L.js.map +1 -0
- package/dist/{RLSEditor-CBX4uD66.js → RLSEditor-Sa1D34ny.js} +724 -615
- package/dist/RLSEditor-Sa1D34ny.js.map +1 -0
- package/dist/{SQLEditor-ByecwX8B.js → SQLEditor-BfNA8xM3.js} +9 -4
- package/dist/{SQLEditor-ByecwX8B.js.map → SQLEditor-BfNA8xM3.js.map} +1 -1
- package/dist/{SchemaVisualizer-D6tMZgeC.js → SchemaVisualizer-DTDMYL6o.js} +84 -110
- package/dist/SchemaVisualizer-DTDMYL6o.js.map +1 -0
- package/dist/{StorageView-BGDeC5GI.js → StorageView-Ed9Q_VlB.js} +5 -32
- package/dist/StorageView-Ed9Q_VlB.js.map +1 -0
- package/dist/components/RLSEditor/saveRules.d.ts +38 -0
- package/dist/components/RebaseStudio.d.ts +1 -1
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +5 -2
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +0 -4
- package/dist/components/load-failure-view.d.ts +17 -0
- package/dist/components/load-failure.d.ts +47 -0
- package/dist/index.es.js +56 -36
- package/dist/index.es.js.map +1 -1
- package/dist/load-failure-C6OMJSGC.js +31 -0
- package/dist/load-failure-C6OMJSGC.js.map +1 -0
- package/dist/load-failure-view-CBTB_h-h.js +48 -0
- package/dist/load-failure-view-CBTB_h-h.js.map +1 -0
- package/package.json +36 -21
- package/dist/ApiKeysView-DiCurTEU.js.map +0 -1
- package/dist/BackupsView-DNS6LdVg.js.map +0 -1
- package/dist/BranchesView-CM3B-ER3.js.map +0 -1
- package/dist/CronJobsView-BHtJZeJZ.js.map +0 -1
- package/dist/RLSEditor-CBX4uD66.js.map +0 -1
- package/dist/SchemaVisualizer-D6tMZgeC.js.map +0 -1
- package/dist/StorageView-BGDeC5GI.js.map +0 -1
- package/dist/components/StorageView/storage-failure.d.ts +0 -34
- package/src/components/ApiExplorer/ApiExplorer.tsx +0 -299
- package/src/components/ApiExplorer/EndpointDetail.tsx +0 -279
- package/src/components/ApiExplorer/TryItPanel.tsx +0 -499
- package/src/components/ApiExplorer/parseSpec.ts +0 -104
- package/src/components/ApiExplorer/types.ts +0 -84
- package/src/components/ApiKeys/ApiKeysView.tsx +0 -479
- package/src/components/ApiKeys/CreateApiKeyDialog.tsx +0 -711
- package/src/components/ApiKeys/permissions.ts +0 -145
- package/src/components/AuthSimulationSelector.tsx +0 -72
- package/src/components/Backups/BackupsView.tsx +0 -185
- package/src/components/Branches/BranchesView.tsx +0 -386
- package/src/components/CronJobs/CronJobsView.tsx +0 -413
- package/src/components/JSEditor/JSEditor.tsx +0 -1100
- package/src/components/JSEditor/JSEditorSidebar.tsx +0 -350
- package/src/components/JSEditor/JSMonacoEditor.tsx +0 -410
- package/src/components/LogsExplorer/LogsExplorer.tsx +0 -273
- package/src/components/LogsExplorer/useLogTail.ts +0 -356
- package/src/components/RLSEditor/PolicyEditor.tsx +0 -399
- package/src/components/RLSEditor/RLSEditor.tsx +0 -1128
- package/src/components/RLSEditor/index.ts +0 -1
- package/src/components/RLSEditor/policy-presets.ts +0 -176
- package/src/components/RLSEditor/saveSecurityRules.ts +0 -48
- package/src/components/RebaseStudio.tsx +0 -174
- package/src/components/SQLEditor/ExplainVisualizer.tsx +0 -128
- package/src/components/SQLEditor/MonacoEditor.tsx +0 -203
- package/src/components/SQLEditor/SQLEditor.tsx +0 -1518
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +0 -173
- package/src/components/SQLEditor/SchemaBrowser.tsx +0 -168
- package/src/components/SQLEditor/sql_editor_types.ts +0 -18
- package/src/components/SchemaVisualizer/RelationEdge.tsx +0 -102
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +0 -710
- package/src/components/SchemaVisualizer/TableNode.tsx +0 -257
- package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +0 -140
- package/src/components/SchemaVisualizer/useLiveRls.ts +0 -86
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +0 -426
- package/src/components/StorageView/StorageView.tsx +0 -1588
- package/src/components/StorageView/storage-failure.ts +0 -63
- package/src/components/StudioHomePage.tsx +0 -430
- package/src/index.ts +0 -31
- package/src/utils/parseSpec.test.ts +0 -309
- package/src/utils/sql_utils.test.ts +0 -265
- package/src/utils/sql_utils.ts +0 -291
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
cls,
|
|
5
|
-
defaultBorderMixin,
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogActions,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogTitle,
|
|
10
|
-
HelpCircleIcon,
|
|
11
|
-
IconButton,
|
|
12
|
-
iconSize,
|
|
13
|
-
MultiSelect,
|
|
14
|
-
MultiSelectItem,
|
|
15
|
-
Paper,
|
|
16
|
-
Select,
|
|
17
|
-
SelectItem,
|
|
18
|
-
TextField,
|
|
19
|
-
Typography
|
|
20
|
-
} from "@rebasepro/ui";
|
|
21
|
-
import { useTranslation } from "@rebasepro/app";
|
|
22
|
-
import { RLS_JWT_SQL, RLS_ROLES_SQL, RLS_UID_SQL } from "@rebasepro/types";
|
|
23
|
-
import {
|
|
24
|
-
COMMAND_OPTIONS,
|
|
25
|
-
POLICY_PRESETS,
|
|
26
|
-
roleOptionsFor,
|
|
27
|
-
type PolicyCommand
|
|
28
|
-
} from "./policy-presets";
|
|
29
|
-
import { MonacoEditor } from "../SQLEditor/MonacoEditor";
|
|
30
|
-
import type { PostgresPolicy } from "./RLSEditor";
|
|
31
|
-
|
|
32
|
-
export interface PolicyEditorProps {
|
|
33
|
-
policy?: PostgresPolicy;
|
|
34
|
-
schema: string;
|
|
35
|
-
table: string;
|
|
36
|
-
/**
|
|
37
|
-
* Native PostgreSQL roles this database actually has, for the `TO` list.
|
|
38
|
-
*
|
|
39
|
-
* Supplied by the caller from `fetchAvailableRoles()`. Merged with the
|
|
40
|
-
* always-offered defaults by `roleOptionsFor`, never used in place of them:
|
|
41
|
-
* `public` is a keyword and never comes back from that query.
|
|
42
|
-
*/
|
|
43
|
-
roleOptions?: readonly string[];
|
|
44
|
-
onSave: (policyData: Partial<PostgresPolicy>) => void;
|
|
45
|
-
onCancel: () => void;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const PolicyEditor = ({
|
|
49
|
-
policy,
|
|
50
|
-
schema,
|
|
51
|
-
table,
|
|
52
|
-
roleOptions,
|
|
53
|
-
onSave,
|
|
54
|
-
onCancel
|
|
55
|
-
}: PolicyEditorProps) => {
|
|
56
|
-
|
|
57
|
-
const { t } = useTranslation();
|
|
58
|
-
const [name, setName] = useState("");
|
|
59
|
-
const [helpOpen, setHelpOpen] = useState(false);
|
|
60
|
-
const [behavior, setBehavior] = useState<"PERMISSIVE" | "RESTRICTIVE">("PERMISSIVE");
|
|
61
|
-
const [command, setCommand] = useState<PolicyCommand>("ALL");
|
|
62
|
-
const [roles, setRoles] = useState<string[]>(["public"]);
|
|
63
|
-
const [usingExpr, setUsingExpr] = useState<string>("");
|
|
64
|
-
const [checkExpr, setCheckExpr] = useState<string>("");
|
|
65
|
-
|
|
66
|
-
const [selectedPreset, setSelectedPreset] = useState<string>("");
|
|
67
|
-
|
|
68
|
-
const availableRoles = useMemo(
|
|
69
|
-
() => roleOptionsFor(roleOptions, policy?.roles),
|
|
70
|
-
[roleOptions, policy]
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
if (policy) {
|
|
75
|
-
setName(policy.policyname || "");
|
|
76
|
-
setBehavior(policy.permissive || "PERMISSIVE");
|
|
77
|
-
setCommand((policy.cmd as PolicyCommand) || "ALL");
|
|
78
|
-
|
|
79
|
-
const initialRoles = policy.roles
|
|
80
|
-
? (Array.isArray(policy.roles) ? policy.roles : [policy.roles])
|
|
81
|
-
: ["public"];
|
|
82
|
-
setRoles(initialRoles as string[]);
|
|
83
|
-
|
|
84
|
-
setUsingExpr(policy.qual || "");
|
|
85
|
-
setCheckExpr(policy.with_check || "");
|
|
86
|
-
} else {
|
|
87
|
-
setName("");
|
|
88
|
-
setBehavior("PERMISSIVE");
|
|
89
|
-
setCommand("ALL");
|
|
90
|
-
setRoles(["public"]);
|
|
91
|
-
setUsingExpr("");
|
|
92
|
-
setCheckExpr("");
|
|
93
|
-
setSelectedPreset("");
|
|
94
|
-
}
|
|
95
|
-
}, [policy]);
|
|
96
|
-
|
|
97
|
-
const handlePresetChange = (presetId: string) => {
|
|
98
|
-
const preset = POLICY_PRESETS.find(p => p.id === presetId);
|
|
99
|
-
if (!preset) return;
|
|
100
|
-
|
|
101
|
-
setSelectedPreset(presetId);
|
|
102
|
-
setName(preset.policyname);
|
|
103
|
-
setBehavior(preset.permissive);
|
|
104
|
-
setCommand(preset.cmd);
|
|
105
|
-
setRoles(preset.roles);
|
|
106
|
-
setUsingExpr(preset.qual);
|
|
107
|
-
setCheckExpr(preset.with_check);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const showCheck = command === "ALL" || command === "INSERT" || command === "UPDATE";
|
|
111
|
-
|
|
112
|
-
const handleSave = () => {
|
|
113
|
-
onSave({
|
|
114
|
-
policyname: name,
|
|
115
|
-
permissive: behavior,
|
|
116
|
-
cmd: command,
|
|
117
|
-
roles: roles,
|
|
118
|
-
qual: usingExpr,
|
|
119
|
-
with_check: showCheck ? checkExpr : null
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<>
|
|
125
|
-
{/* Inline header: this editor renders as a full pane, not inside a Dialog,
|
|
126
|
-
so DialogTitle (Radix-bound) cannot be used here. */}
|
|
127
|
-
<Typography variant="h6" gutterBottom className="mt-8 mx-8 flex justify-between items-center w-full">
|
|
128
|
-
<div>
|
|
129
|
-
<div>{policy ? t("studio_policy_edit") : t("studio_policy_create")}</div>
|
|
130
|
-
<div className="text-sm font-normal text-text-secondary dark:text-text-secondary-dark tracking-wide mt-1">
|
|
131
|
-
{t("studio_policy_defining_rules")} <span className="font-mono text-primary bg-primary-bg dark:bg-primary-bg-dark px-1 py-0.5 rounded">{schema}.{table}</span>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
<IconButton size="small" onClick={() => setHelpOpen(true)}>
|
|
135
|
-
<HelpCircleIcon size={iconSize.smallest}/>
|
|
136
|
-
</IconButton>
|
|
137
|
-
</Typography>
|
|
138
|
-
|
|
139
|
-
<DialogContent className="p-4 md:p-6 border-t dark:border-surface-950 bg-surface-50 dark:bg-surface-950" includeMargin={false}>
|
|
140
|
-
<div className="max-w-4xl mx-auto">
|
|
141
|
-
<Paper className={cls("p-4 md:p-6 flex flex-col gap-6 bg-white dark:bg-surface-900 border-none sm:border-solid rounded-none sm:rounded-xl", defaultBorderMixin)}>
|
|
142
|
-
|
|
143
|
-
{/* Presets - only for new policies */}
|
|
144
|
-
{!policy && (
|
|
145
|
-
<div className="flex flex-col gap-1.5 bg-primary/5 dark:bg-primary-bg-dark/20 p-3 sm:p-4 rounded-lg border border-primary/10 dark:border-primary/20">
|
|
146
|
-
<Typography variant="caption" className="text-primary dark:text-primary-light uppercase tracking-wider">{t("studio_policy_template")}</Typography>
|
|
147
|
-
<Select
|
|
148
|
-
size="small"
|
|
149
|
-
value={selectedPreset}
|
|
150
|
-
onValueChange={handlePresetChange}
|
|
151
|
-
position="item-aligned"
|
|
152
|
-
placeholder={t("studio_policy_select_template")}
|
|
153
|
-
className="bg-white dark:bg-surface-950"
|
|
154
|
-
>
|
|
155
|
-
{POLICY_PRESETS.map((preset) => (
|
|
156
|
-
<SelectItem key={preset.id} value={preset.id}>
|
|
157
|
-
<div className="flex flex-col text-left">
|
|
158
|
-
<span className="text-sm">{preset.label}</span>
|
|
159
|
-
<span className="text-xs text-text-secondary dark:text-text-secondary-dark">{preset.description}</span>
|
|
160
|
-
</div>
|
|
161
|
-
</SelectItem>
|
|
162
|
-
))}
|
|
163
|
-
</Select>
|
|
164
|
-
</div>
|
|
165
|
-
)}
|
|
166
|
-
|
|
167
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
|
|
168
|
-
{/* Policy Name */}
|
|
169
|
-
<div className="flex flex-col gap-1.5">
|
|
170
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_name")}</Typography>
|
|
171
|
-
<TextField
|
|
172
|
-
value={name}
|
|
173
|
-
aria-label={t("studio_policy_name")}
|
|
174
|
-
onChange={(e) => setName(e.target.value)}
|
|
175
|
-
placeholder={t("studio_policy_name_placeholder")}
|
|
176
|
-
className="w-full"
|
|
177
|
-
/>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
{/* Behavior */}
|
|
181
|
-
<div className="flex flex-col gap-1.5">
|
|
182
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
183
|
-
{t("studio_policy_behavior")} <code className="text-[10px] bg-surface-200 dark:bg-surface-950 text-text-secondary dark:text-text-secondary-dark px-1 py-0.5 rounded ml-1">AS</code>
|
|
184
|
-
</Typography>
|
|
185
|
-
<Select
|
|
186
|
-
value={behavior}
|
|
187
|
-
onValueChange={(val) => setBehavior(val as "PERMISSIVE" | "RESTRICTIVE")}
|
|
188
|
-
position="item-aligned"
|
|
189
|
-
>
|
|
190
|
-
<SelectItem value="PERMISSIVE">
|
|
191
|
-
<div className="flex flex-col text-left">
|
|
192
|
-
<span className="">{t("studio_policy_permissive")}</span>
|
|
193
|
-
<span className="text-xs text-text-secondary dark:text-text-secondary-dark">{t("studio_policy_permissive_desc")}</span>
|
|
194
|
-
</div>
|
|
195
|
-
</SelectItem>
|
|
196
|
-
<SelectItem value="RESTRICTIVE">
|
|
197
|
-
<div className="flex flex-col text-left">
|
|
198
|
-
<span className="">{t("studio_policy_restrictive")}</span>
|
|
199
|
-
<span className="text-xs text-text-secondary dark:text-text-secondary-dark">{t("studio_policy_restrictive_desc")}</span>
|
|
200
|
-
</div>
|
|
201
|
-
</SelectItem>
|
|
202
|
-
</Select>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
|
|
206
|
-
{/* Command */}
|
|
207
|
-
<div className="flex flex-col gap-1.5">
|
|
208
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
209
|
-
{t("studio_policy_command")} <code className="text-[10px] bg-surface-200 dark:bg-surface-950 text-text-secondary dark:text-text-secondary-dark px-1 py-0.5 rounded ml-1">FOR</code>
|
|
210
|
-
</Typography>
|
|
211
|
-
<div className="flex flex-wrap gap-1.5">
|
|
212
|
-
{COMMAND_OPTIONS.map(cmd => (
|
|
213
|
-
<Button
|
|
214
|
-
key={cmd}
|
|
215
|
-
size="small"
|
|
216
|
-
variant={command === cmd ? "filled" : "text"}
|
|
217
|
-
color="neutral"
|
|
218
|
-
onClick={() => setCommand(cmd)}
|
|
219
|
-
className="min-w-[80px]"
|
|
220
|
-
>
|
|
221
|
-
{cmd}
|
|
222
|
-
</Button>
|
|
223
|
-
))}
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
{/* Roles */}
|
|
228
|
-
<div className="flex flex-col gap-1.5">
|
|
229
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
230
|
-
{t("studio_policy_target_roles")} <code className="text-[10px] bg-surface-200 dark:bg-surface-950 text-text-secondary dark:text-text-secondary-dark px-1 py-0.5 rounded ml-1">TO</code>
|
|
231
|
-
</Typography>
|
|
232
|
-
<MultiSelect
|
|
233
|
-
size="small"
|
|
234
|
-
value={roles}
|
|
235
|
-
onValueChange={setRoles}
|
|
236
|
-
placeholder={t("studio_policy_roles_placeholder")}
|
|
237
|
-
>
|
|
238
|
-
{availableRoles.map(role => (
|
|
239
|
-
<MultiSelectItem key={role} value={role}>
|
|
240
|
-
{role}
|
|
241
|
-
</MultiSelectItem>
|
|
242
|
-
))}
|
|
243
|
-
</MultiSelect>
|
|
244
|
-
</div>
|
|
245
|
-
|
|
246
|
-
</Paper>
|
|
247
|
-
|
|
248
|
-
<div className="mt-8 flex flex-col gap-4">
|
|
249
|
-
{/* USING Expression */}
|
|
250
|
-
{command !== "INSERT" && (
|
|
251
|
-
<div className="flex flex-col gap-1.5">
|
|
252
|
-
<div className="flex flex-col gap-0.5">
|
|
253
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_using_expr")}</Typography>
|
|
254
|
-
<Typography variant="caption" className="text-text-secondary opacity-70">{t("studio_policy_using_expr_desc")}</Typography>
|
|
255
|
-
</div>
|
|
256
|
-
<div className={cls("h-32 border rounded-md overflow-hidden bg-white dark:bg-[#1e1e1e]", defaultBorderMixin)}>
|
|
257
|
-
<MonacoEditor
|
|
258
|
-
value={usingExpr}
|
|
259
|
-
onChange={(v) => setUsingExpr(v || "")}
|
|
260
|
-
readOnly={false}
|
|
261
|
-
autoFocus={true}
|
|
262
|
-
/>
|
|
263
|
-
</div>
|
|
264
|
-
</div>
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
{/* WITH CHECK Expression */}
|
|
268
|
-
{showCheck && (
|
|
269
|
-
<div className="flex flex-col gap-1.5 mt-2">
|
|
270
|
-
<div className="flex flex-col gap-0.5">
|
|
271
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_check_expr")}</Typography>
|
|
272
|
-
<Typography variant="caption" className="text-text-secondary opacity-70">{t("studio_policy_check_expr_desc")}</Typography>
|
|
273
|
-
</div>
|
|
274
|
-
<div className={cls("h-32 border rounded-md overflow-hidden bg-white dark:bg-[#1e1e1e]", defaultBorderMixin)}>
|
|
275
|
-
<MonacoEditor
|
|
276
|
-
value={checkExpr}
|
|
277
|
-
onChange={(v) => setCheckExpr(v || "")}
|
|
278
|
-
readOnly={false}
|
|
279
|
-
autoFocus={command === "INSERT"}
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
)}
|
|
284
|
-
</div>
|
|
285
|
-
|
|
286
|
-
</div>
|
|
287
|
-
</DialogContent>
|
|
288
|
-
|
|
289
|
-
<DialogActions>
|
|
290
|
-
<Button size="small" variant="text" color="neutral" onClick={onCancel}>
|
|
291
|
-
{t("studio_policy_cancel")}
|
|
292
|
-
</Button>
|
|
293
|
-
<Button size="small" variant="filled" color="primary" onClick={handleSave} disabled={!name}>
|
|
294
|
-
{t("studio_policy_save")}
|
|
295
|
-
</Button>
|
|
296
|
-
</DialogActions>
|
|
297
|
-
|
|
298
|
-
<Dialog open={helpOpen} onOpenChange={setHelpOpen} maxWidth="3xl">
|
|
299
|
-
<DialogTitle hidden>Row-Level Security Help</DialogTitle>
|
|
300
|
-
<DialogContent className="p-4 sm:p-6 lg:p-8 flex flex-col gap-6">
|
|
301
|
-
<div>
|
|
302
|
-
<Typography variant="h5" className="mb-2">{t("studio_policy_help_title")}</Typography>
|
|
303
|
-
<Typography className="text-text-secondary dark:text-text-secondary-dark">
|
|
304
|
-
{t("studio_policy_help_intro")}
|
|
305
|
-
</Typography>
|
|
306
|
-
</div>
|
|
307
|
-
|
|
308
|
-
<div className="flex flex-col gap-4">
|
|
309
|
-
<Paper className={cls("p-4 sm:p-5 flex flex-col gap-1", defaultBorderMixin)}>
|
|
310
|
-
<Typography variant="subtitle2" className="text-primary dark:text-primary-light font-medium">{t("studio_policy_help_step1_title")}</Typography>
|
|
311
|
-
<Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark">
|
|
312
|
-
{t("studio_policy_help_step1_desc")}
|
|
313
|
-
</Typography>
|
|
314
|
-
</Paper>
|
|
315
|
-
|
|
316
|
-
<Paper className={cls("p-4 sm:p-5 flex flex-col gap-1", defaultBorderMixin)}>
|
|
317
|
-
<Typography variant="subtitle2" className="text-primary dark:text-primary-light font-medium">{t("studio_policy_help_step2_title")}</Typography>
|
|
318
|
-
<Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark mb-1">
|
|
319
|
-
{t("studio_policy_help_step2_desc")}
|
|
320
|
-
</Typography>
|
|
321
|
-
<ul className="list-disc pl-5 space-y-1 text-sm text-text-secondary dark:text-text-secondary-dark">
|
|
322
|
-
<li><strong>public</strong>: {t("studio_policy_help_role_public")}</li>
|
|
323
|
-
<li><strong>authenticated</strong>: {t("studio_policy_help_role_authenticated")}</li>
|
|
324
|
-
<li><strong>anon</strong>: {t("studio_policy_help_role_anon")}</li>
|
|
325
|
-
</ul>
|
|
326
|
-
</Paper>
|
|
327
|
-
|
|
328
|
-
<Paper className={cls("p-4 sm:p-5 flex flex-col gap-1", defaultBorderMixin)}>
|
|
329
|
-
<Typography variant="subtitle2" className="text-primary dark:text-primary-light font-medium">{t("studio_policy_help_step3_title")}</Typography>
|
|
330
|
-
<Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark mb-1">
|
|
331
|
-
{t("studio_policy_help_step3_desc")}
|
|
332
|
-
</Typography>
|
|
333
|
-
<div className={cls("bg-surface-100 dark:bg-surface-950 px-3 py-2 rounded-md font-mono text-sm my-2", defaultBorderMixin)}>
|
|
334
|
-
Example: {RLS_UID_SQL} = uid
|
|
335
|
-
</div>
|
|
336
|
-
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark">
|
|
337
|
-
{t("studio_policy_help_step3_example")}
|
|
338
|
-
</Typography>
|
|
339
|
-
</Paper>
|
|
340
|
-
|
|
341
|
-
<Paper className={cls("p-4 sm:p-5 flex flex-col gap-1", defaultBorderMixin)}>
|
|
342
|
-
<Typography variant="subtitle2" className="text-primary dark:text-primary-light font-medium">{t("studio_policy_help_step4_title")}</Typography>
|
|
343
|
-
<Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark mb-1">
|
|
344
|
-
{t("studio_policy_help_step4_desc")}
|
|
345
|
-
</Typography>
|
|
346
|
-
<div className={cls("bg-surface-100 dark:bg-surface-950 px-3 py-2 rounded-md font-mono text-sm my-2", defaultBorderMixin)}>
|
|
347
|
-
Example: {RLS_UID_SQL} = uid
|
|
348
|
-
</div>
|
|
349
|
-
<Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark">
|
|
350
|
-
{t("studio_policy_help_step4_example")}
|
|
351
|
-
</Typography>
|
|
352
|
-
</Paper>
|
|
353
|
-
|
|
354
|
-
<Paper className={cls("p-4 sm:p-5 flex flex-col gap-2 bg-primary/5 dark:bg-primary-bg-dark/10", defaultBorderMixin)}>
|
|
355
|
-
<Typography variant="subtitle2" className="text-primary dark:text-primary-light font-medium">{t("studio_policy_help_auth_vars_title")}</Typography>
|
|
356
|
-
<Typography variant="body2" className="text-text-secondary dark:text-text-secondary-dark">
|
|
357
|
-
{t("studio_policy_help_auth_vars_desc")}
|
|
358
|
-
</Typography>
|
|
359
|
-
<ul className="list-disc pl-5 space-y-2 text-sm text-text-secondary dark:text-text-secondary-dark font-normal">
|
|
360
|
-
<li>
|
|
361
|
-
<code className="bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap">{RLS_UID_SQL}</code>
|
|
362
|
-
<span className="block mt-0.5">Returns the current user's ID as text. Example: <code className="bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]">{RLS_UID_SQL} = uid</code></span>
|
|
363
|
-
</li>
|
|
364
|
-
<li>
|
|
365
|
-
<code className="bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap">{RLS_JWT_SQL}</code>
|
|
366
|
-
<span className="block mt-0.5">Returns the full JWT payload as JSONB so you can check custom claims. Example: <code className="bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]">{RLS_JWT_SQL} ->> 'email' = 'admin@example.com'</code></span>
|
|
367
|
-
</li>
|
|
368
|
-
<li>
|
|
369
|
-
<code className="bg-surface-100 dark:bg-surface-950 px-1.5 py-0.5 rounded mr-1 whitespace-nowrap">{RLS_ROLES_SQL}</code>
|
|
370
|
-
<span className="block mt-0.5">Returns the user's role IDs as a comma-separated string. Best used with: <code className="bg-surface-100 dark:bg-surface-950 px-1 py-0.5 rounded text-[11px]">string_to_array({RLS_ROLES_SQL}, ',') @> ARRAY['admin']</code></span>
|
|
371
|
-
</li>
|
|
372
|
-
</ul>
|
|
373
|
-
</Paper>
|
|
374
|
-
</div>
|
|
375
|
-
|
|
376
|
-
<div className={cls("mt-2 flex flex-col sm:flex-row justify-between items-start sm:items-center bg-primary/5 dark:bg-primary-bg-dark/10 p-4 rounded-xl border border-primary/10 dark:border-primary/20", defaultBorderMixin)}>
|
|
377
|
-
<Typography variant="body2" className="text-primary dark:text-primary-light mb-4 sm:mb-0 max-w-md">
|
|
378
|
-
{t("studio_policy_help_docs_cta")}
|
|
379
|
-
</Typography>
|
|
380
|
-
<Button
|
|
381
|
-
component="a"
|
|
382
|
-
href="https://www.postgresql.org/docs/current/sql-createpolicy.html"
|
|
383
|
-
target="_blank"
|
|
384
|
-
variant="outlined"
|
|
385
|
-
color="primary"
|
|
386
|
-
size="small"
|
|
387
|
-
className="whitespace-nowrap flex-shrink-0"
|
|
388
|
-
>
|
|
389
|
-
{t("studio_policy_help_read_docs")}
|
|
390
|
-
</Button>
|
|
391
|
-
</div>
|
|
392
|
-
</DialogContent>
|
|
393
|
-
<DialogActions className="p-4 sm:px-6 sm:pb-6 pt-0 border-t-0">
|
|
394
|
-
<Button onClick={() => setHelpOpen(false)} variant="filled" color="primary">{t("studio_policy_help_got_it")}</Button>
|
|
395
|
-
</DialogActions>
|
|
396
|
-
</Dialog>
|
|
397
|
-
</>
|
|
398
|
-
);
|
|
399
|
-
};
|