@rebasepro/studio 0.6.1 → 0.8.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/ApiKeysView-D-_FSlNL.js +684 -0
- package/dist/ApiKeysView-D-_FSlNL.js.map +1 -0
- package/dist/{BranchesView-DncIRcZt.js → BranchesView-Dlg78EQ8.js} +4 -5
- package/dist/{BranchesView-DncIRcZt.js.map → BranchesView-Dlg78EQ8.js.map} +1 -1
- package/dist/{JSEditor-BhAbEjCP.js → JSEditor-Ca4XYGRp.js} +7 -7
- package/dist/JSEditor-Ca4XYGRp.js.map +1 -0
- package/dist/{LogsExplorer-CqtKILj8.js → LogsExplorer-J4xfsuv3.js} +49 -83
- package/dist/LogsExplorer-J4xfsuv3.js.map +1 -0
- package/dist/{RLSEditor-DpF1u9EC.js → RLSEditor-BM64laoW.js} +294 -107
- package/dist/RLSEditor-BM64laoW.js.map +1 -0
- package/dist/{SQLEditor-BLuq_zDM.js → SQLEditor-CuAhR-zr.js} +4 -4
- package/dist/{SQLEditor-BLuq_zDM.js.map → SQLEditor-CuAhR-zr.js.map} +1 -1
- package/dist/{SchemaVisualizer-BJK2u3C0.js → SchemaVisualizer-OibKoD3g.js} +2 -2
- package/dist/{SchemaVisualizer-BJK2u3C0.js.map → SchemaVisualizer-OibKoD3g.js.map} +1 -1
- package/dist/{StorageView-CvrnHmDG.js → StorageView-BMhD29YO.js} +33 -5
- package/dist/StorageView-BMhD29YO.js.map +1 -0
- package/dist/components/ApiKeys/ApiKeysView.d.ts +2 -0
- package/dist/index.es.js +141 -94
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1221 -317
- package/dist/index.umd.js.map +1 -1
- package/package.json +8 -8
- package/src/components/ApiKeys/ApiKeysView.tsx +580 -0
- package/src/components/Branches/BranchesView.tsx +3 -4
- package/src/components/JSEditor/JSEditorSidebar.tsx +3 -3
- package/src/components/JSEditor/JSMonacoEditor.tsx +3 -3
- package/src/components/LogsExplorer/LogsExplorer.tsx +46 -84
- package/src/components/RLSEditor/RLSEditor.tsx +354 -117
- package/src/components/RebaseStudio.tsx +10 -1
- package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
- package/src/components/StorageView/StorageView.tsx +43 -4
- package/src/components/StudioHomePage.tsx +144 -85
- package/src/utils/pgColumnToProperty.ts +2 -2
- package/dist/JSEditor-BhAbEjCP.js.map +0 -1
- package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
- package/dist/RLSEditor-DpF1u9EC.js.map +0 -1
- package/dist/StorageView-CvrnHmDG.js.map +0 -1
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
|
-
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/core"), require("react"), require("@rebasepro/ui"), require("react/jsx-runtime"), require("react-router-dom"), require("react-dom"), require("@monaco-editor/react"), require("pgsql-ast-parser"), require("@rebasepro/utils"), require("prism-react-renderer"), require("@rebasepro/client"), require("@rebasepro/types"), require("react-dropzone"), require("@xyflow/react"), require("@xyflow/react/dist/style.css"), require("
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/core"), require("react"), require("@rebasepro/ui"), require("react/jsx-runtime"), require("react-router-dom"), require("react-dom"), require("@monaco-editor/react"), require("pgsql-ast-parser"), require("@rebasepro/utils"), require("prism-react-renderer"), require("@rebasepro/client"), require("@rebasepro/types"), require("@rebasepro/common"), require("react-dropzone"), require("@xyflow/react"), require("@xyflow/react/dist/style.css"), require("dagre")) : typeof define === "function" && define.amd ? define([
|
|
3
3
|
"exports",
|
|
4
4
|
"@rebasepro/core",
|
|
5
5
|
"react",
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"prism-react-renderer",
|
|
14
14
|
"@rebasepro/client",
|
|
15
15
|
"@rebasepro/types",
|
|
16
|
+
"@rebasepro/common",
|
|
16
17
|
"react-dropzone",
|
|
17
18
|
"@xyflow/react",
|
|
18
19
|
"@xyflow/react/dist/style.css",
|
|
19
|
-
"@rebasepro/common",
|
|
20
20
|
"dagre"
|
|
21
|
-
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase collection editor"] = {}, global._rebasepro_core, global.react, global._rebasepro_ui, global.react_jsx_runtime, global.react_router_dom, global.react_dom, global._monaco_editor_react, global.pgsql_ast_parser, global._rebasepro_utils, global.prism_react_renderer, global._rebasepro_client, global._rebasepro_types, global.
|
|
22
|
-
})(this, function(exports, _rebasepro_core, react, _rebasepro_ui, react_jsx_runtime, react_router_dom, react_dom, _monaco_editor_react, pgsql_ast_parser, _rebasepro_utils, prism_react_renderer, _rebasepro_client, _rebasepro_types, react_dropzone, _xyflow_react, _xyflow_react_dist_style_css,
|
|
21
|
+
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase collection editor"] = {}, global._rebasepro_core, global.react, global._rebasepro_ui, global.react_jsx_runtime, global.react_router_dom, global.react_dom, global._monaco_editor_react, global.pgsql_ast_parser, global._rebasepro_utils, global.prism_react_renderer, global._rebasepro_client, global._rebasepro_types, global._rebasepro_common, global.react_dropzone, global._xyflow_react, global._xyflow_react_dist_style_css, global.dagre));
|
|
22
|
+
})(this, function(exports, _rebasepro_core, react, _rebasepro_ui, react_jsx_runtime, react_router_dom, react_dom, _monaco_editor_react, pgsql_ast_parser, _rebasepro_utils, prism_react_renderer, _rebasepro_client, _rebasepro_types, _rebasepro_common, react_dropzone, _xyflow_react, _xyflow_react_dist_style_css, dagre) {
|
|
23
23
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
24
24
|
//#region \0rolldown/runtime.js
|
|
25
25
|
var __create = Object.create;
|
|
@@ -60,8 +60,6 @@
|
|
|
60
60
|
var SECTIONS = [
|
|
61
61
|
{
|
|
62
62
|
label: "Database",
|
|
63
|
-
dotColor: "bg-emerald-400",
|
|
64
|
-
iconColor: "text-emerald-400",
|
|
65
63
|
tools: [
|
|
66
64
|
{
|
|
67
65
|
path: "/schema",
|
|
@@ -103,8 +101,6 @@
|
|
|
103
101
|
},
|
|
104
102
|
{
|
|
105
103
|
label: "Compute",
|
|
106
|
-
dotColor: "bg-blue-400",
|
|
107
|
-
iconColor: "text-blue-400",
|
|
108
104
|
tools: [{
|
|
109
105
|
path: "/js",
|
|
110
106
|
name: "JS Console",
|
|
@@ -119,8 +115,6 @@
|
|
|
119
115
|
},
|
|
120
116
|
{
|
|
121
117
|
label: "API",
|
|
122
|
-
dotColor: "bg-violet-400",
|
|
123
|
-
iconColor: "text-violet-400",
|
|
124
118
|
tools: [{
|
|
125
119
|
path: "/api",
|
|
126
120
|
name: "API Explorer",
|
|
@@ -130,8 +124,6 @@
|
|
|
130
124
|
},
|
|
131
125
|
{
|
|
132
126
|
label: "Storage",
|
|
133
|
-
dotColor: "bg-amber-400",
|
|
134
|
-
iconColor: "text-amber-400",
|
|
135
127
|
tools: [{
|
|
136
128
|
path: "/storage",
|
|
137
129
|
name: "Storage",
|
|
@@ -141,21 +133,55 @@
|
|
|
141
133
|
},
|
|
142
134
|
{
|
|
143
135
|
label: "Access Control",
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
136
|
+
tools: [
|
|
137
|
+
{
|
|
138
|
+
path: "/users",
|
|
139
|
+
name: "Users",
|
|
140
|
+
description: "Manage developers and assign roles in your workspace",
|
|
141
|
+
icon: "group"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
path: "/roles",
|
|
145
|
+
name: "Roles",
|
|
146
|
+
description: "Create and configure fine-grained access permissions",
|
|
147
|
+
icon: "ShieldEllipsis"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
path: "/api-keys",
|
|
151
|
+
name: "API Keys",
|
|
152
|
+
description: "Create and manage scoped keys for machine-to-machine access",
|
|
153
|
+
icon: "KeyRound"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
157
156
|
}
|
|
158
157
|
];
|
|
158
|
+
var COLLAPSED_STORAGE_KEY = "rebase-studio-home-collapsed";
|
|
159
|
+
function useStudioCollapsedGroups(groupNames) {
|
|
160
|
+
const [collapsed, setCollapsed] = (0, react.useState)(() => {
|
|
161
|
+
try {
|
|
162
|
+
const stored = localStorage.getItem(COLLAPSED_STORAGE_KEY);
|
|
163
|
+
return stored ? new Set(JSON.parse(stored)) : /* @__PURE__ */ new Set();
|
|
164
|
+
} catch {
|
|
165
|
+
return /* @__PURE__ */ new Set();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
const isGroupCollapsed = (name) => collapsed.has(name);
|
|
169
|
+
const toggleGroupCollapsed = (name) => {
|
|
170
|
+
setCollapsed((prev) => {
|
|
171
|
+
const next = new Set(prev);
|
|
172
|
+
if (next.has(name)) next.delete(name);
|
|
173
|
+
else next.add(name);
|
|
174
|
+
try {
|
|
175
|
+
localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...next]));
|
|
176
|
+
} catch {}
|
|
177
|
+
return next;
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
isGroupCollapsed,
|
|
182
|
+
toggleGroupCollapsed
|
|
183
|
+
};
|
|
184
|
+
}
|
|
159
185
|
function StudioHomePage({ additionalActions, additionalChildrenStart, additionalChildrenEnd, sections, hiddenGroups }) {
|
|
160
186
|
const context = (0, _rebasepro_core.useRebaseContext)();
|
|
161
187
|
const breadcrumbs = (0, _rebasepro_core.useStudioBreadcrumbs)();
|
|
@@ -165,9 +191,11 @@
|
|
|
165
191
|
}, [breadcrumbs.set]);
|
|
166
192
|
const { containerRef } = (0, _rebasepro_core.useRestoreScroll)();
|
|
167
193
|
const pluginActions = (0, _rebasepro_core.useSlot)("home.actions", { context });
|
|
194
|
+
const filteredSections = (0, react.useMemo)(() => SECTIONS.filter((s) => !hiddenGroups?.includes(s.label)), [hiddenGroups]);
|
|
195
|
+
const { isGroupCollapsed, toggleGroupCollapsed } = useStudioCollapsedGroups((0, react.useMemo)(() => filteredSections.map((s) => s.label), [filteredSections]));
|
|
168
196
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
169
197
|
ref: containerRef,
|
|
170
|
-
className: "py-2 overflow-auto h-full w-full",
|
|
198
|
+
className: "py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800",
|
|
171
199
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Container, {
|
|
172
200
|
maxWidth: "6xl",
|
|
173
201
|
children: [
|
|
@@ -180,68 +208,77 @@
|
|
|
180
208
|
children: [additionalActions, pluginActions]
|
|
181
209
|
}),
|
|
182
210
|
additionalChildrenStart,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"
|
|
187
|
-
children:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
className: (0, _rebasepro_ui.cls)("
|
|
201
|
-
children:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
211
|
+
filteredSections.map((section) => {
|
|
212
|
+
const sectionCollapsed = isGroupCollapsed(section.label);
|
|
213
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
214
|
+
className: "my-10",
|
|
215
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.ExpandablePanel, {
|
|
216
|
+
invisible: true,
|
|
217
|
+
expanded: !sectionCollapsed,
|
|
218
|
+
onExpandedChange: (open) => {
|
|
219
|
+
if (open !== !sectionCollapsed) toggleGroupCollapsed(section.label);
|
|
220
|
+
},
|
|
221
|
+
className: "mt-6",
|
|
222
|
+
titleClassName: (0, _rebasepro_ui.cls)("min-h-0 p-0 border-none", "rounded flex items-center justify-between w-full", "hover:bg-transparent", "cursor-pointer select-none", sectionCollapsed && "bg-surface-100 dark:bg-surface-900/50"),
|
|
223
|
+
innerClassName: "mt-4 pt-0",
|
|
224
|
+
title: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
225
|
+
variant: "caption",
|
|
226
|
+
component: "h2",
|
|
227
|
+
color: "secondary",
|
|
228
|
+
className: (0, _rebasepro_ui.cls)("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
229
|
+
children: section.label
|
|
230
|
+
}),
|
|
231
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
232
|
+
className: "mt-4 pt-0",
|
|
233
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
234
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4",
|
|
235
|
+
children: section.tools.map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Card, {
|
|
236
|
+
onClick: () => {
|
|
237
|
+
navigate(tool.path);
|
|
238
|
+
context.analyticsController?.onAnalyticsEvent?.("home_navigate_to_view", { path: tool.path });
|
|
239
|
+
},
|
|
240
|
+
className: (0, _rebasepro_ui.cls)("group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out", "hover:bg-primary/5 dark:hover:bg-primary/5"),
|
|
241
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
242
|
+
className: "flex flex-col h-full",
|
|
243
|
+
children: [
|
|
244
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
245
|
+
className: "flex items-center w-full justify-between mb-1",
|
|
246
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
247
|
+
className: "flex items-center gap-3",
|
|
248
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
249
|
+
className: "flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary",
|
|
250
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_core.IconForView, {
|
|
251
|
+
collectionOrView: {
|
|
252
|
+
slug: tool.path,
|
|
253
|
+
name: tool.name,
|
|
254
|
+
icon: tool.icon
|
|
255
|
+
},
|
|
256
|
+
size: "small"
|
|
257
|
+
})
|
|
258
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
259
|
+
variant: "subtitle1",
|
|
260
|
+
component: "h2",
|
|
261
|
+
children: tool.name
|
|
262
|
+
})]
|
|
263
|
+
})
|
|
264
|
+
}),
|
|
265
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
266
|
+
className: "pl-8",
|
|
267
|
+
children: tool.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
268
|
+
variant: "caption",
|
|
269
|
+
color: "secondary",
|
|
270
|
+
component: "div",
|
|
271
|
+
children: tool.description
|
|
272
|
+
})
|
|
273
|
+
}),
|
|
274
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "grow" })
|
|
275
|
+
]
|
|
239
276
|
})
|
|
240
|
-
})
|
|
277
|
+
}, tool.path))
|
|
241
278
|
})
|
|
242
|
-
}
|
|
243
|
-
})
|
|
244
|
-
}, section.label)
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
}, section.label);
|
|
245
282
|
}),
|
|
246
283
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
247
284
|
className: "mt-10 mb-6",
|
|
@@ -252,7 +289,7 @@
|
|
|
252
289
|
variant: "caption",
|
|
253
290
|
component: "h2",
|
|
254
291
|
color: "secondary",
|
|
255
|
-
className: "py-
|
|
292
|
+
className: (0, _rebasepro_ui.cls)("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
256
293
|
children: "Quick Start"
|
|
257
294
|
})
|
|
258
295
|
}),
|
|
@@ -305,7 +342,7 @@
|
|
|
305
342
|
variant: "caption",
|
|
306
343
|
component: "h2",
|
|
307
344
|
color: "secondary",
|
|
308
|
-
className: "
|
|
345
|
+
className: (0, _rebasepro_ui.cls)("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
309
346
|
children: s.title
|
|
310
347
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
311
348
|
className: "mt-4",
|
|
@@ -896,7 +933,7 @@
|
|
|
896
933
|
}) : Object.entries(schemas).map(([schemaName, tables]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
897
934
|
className: "mb-2",
|
|
898
935
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
899
|
-
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-
|
|
936
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
|
|
900
937
|
onClick: () => setExpandedSchemas((prev) => ({
|
|
901
938
|
...prev,
|
|
902
939
|
[schemaName]: !prev[schemaName]
|
|
@@ -914,7 +951,7 @@
|
|
|
914
951
|
}), expandedSchemas[schemaName] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
915
952
|
className: "ml-3 mt-1 space-y-1",
|
|
916
953
|
children: tables.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
917
|
-
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-
|
|
954
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors group relative",
|
|
918
955
|
onClick: () => setExpandedTables((prev) => ({
|
|
919
956
|
...prev,
|
|
920
957
|
[`${schemaName}.${table.tableName}`]: !prev[`${schemaName}.${table.tableName}`]
|
|
@@ -1017,7 +1054,7 @@
|
|
|
1017
1054
|
}), expandedTables[`${schemaName}.${table.tableName}`] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1018
1055
|
className: (0, _rebasepro_ui.cls)("ml-5 mt-1 space-y-0.5 border-l", _rebasepro_ui.defaultBorderMixin),
|
|
1019
1056
|
children: table.columns.map((col) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1020
|
-
className: "flex items-center p-1 group pl-2 hover:bg-surface-50 dark:hover:bg-surface-
|
|
1057
|
+
className: "flex items-center p-1 group pl-2 hover:bg-surface-50 dark:hover:bg-surface-900 rounded-r relative min-h-[28px]",
|
|
1021
1058
|
children: [
|
|
1022
1059
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1023
1060
|
className: "w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0",
|
|
@@ -2856,10 +2893,10 @@ interface FindResponse<M extends Record<string, any> = any> {
|
|
|
2856
2893
|
};
|
|
2857
2894
|
}
|
|
2858
2895
|
|
|
2859
|
-
type
|
|
2896
|
+
type WhereFilterOp = "<" | "<=" | "==" | "!=" | ">=" | ">" | "in" | "not-in" | "array-contains" | "array-contains-any";
|
|
2860
2897
|
|
|
2861
2898
|
interface QueryBuilder<M extends Record<string, any> = any> {
|
|
2862
|
-
where(column: keyof M & string, operator:
|
|
2899
|
+
where(column: keyof M & string, operator: WhereFilterOp, value: any): QueryBuilder<M>;
|
|
2863
2900
|
orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilder<M>;
|
|
2864
2901
|
limit(count: number): QueryBuilder<M>;
|
|
2865
2902
|
offset(count: number): QueryBuilder<M>;
|
|
@@ -2875,7 +2912,7 @@ interface CollectionClient<M extends Record<string, any> = any> {
|
|
|
2875
2912
|
create(data: Partial<M>, id?: string | number): Promise<Entity<M>>;
|
|
2876
2913
|
update(id: string | number, data: Partial<M>): Promise<Entity<M>>;
|
|
2877
2914
|
delete(id: string | number): Promise<void>;
|
|
2878
|
-
where(column: keyof M & string, operator:
|
|
2915
|
+
where(column: keyof M & string, operator: WhereFilterOp, value: any): QueryBuilder<M>;
|
|
2879
2916
|
orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilder<M>;
|
|
2880
2917
|
limit(count: number): QueryBuilder<M>;
|
|
2881
2918
|
offset(count: number): QueryBuilder<M>;
|
|
@@ -3130,7 +3167,7 @@ declare const context: JSEditorContext;
|
|
|
3130
3167
|
function CollectionItem({ collection, onInsertCode }) {
|
|
3131
3168
|
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
3132
3169
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3133
|
-
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-
|
|
3170
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors group relative",
|
|
3134
3171
|
onClick: () => setExpanded(!expanded),
|
|
3135
3172
|
children: [
|
|
3136
3173
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
@@ -3172,7 +3209,7 @@ declare const context: JSEditorContext;
|
|
|
3172
3209
|
}), expanded && collection.properties.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3173
3210
|
className: (0, _rebasepro_ui.cls)("ml-5 mt-1 space-y-0.5 border-l", _rebasepro_ui.defaultBorderMixin),
|
|
3174
3211
|
children: collection.properties.map((prop) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3175
|
-
className: "flex items-center p-1 pl-2 hover:bg-surface-50 dark:hover:bg-surface-
|
|
3212
|
+
className: "flex items-center p-1 pl-2 hover:bg-surface-50 dark:hover:bg-surface-900 rounded-r cursor-pointer transition-colors group/prop relative min-h-[28px]",
|
|
3176
3213
|
onClick: () => onInsertCode(`"${prop}"`),
|
|
3177
3214
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3178
3215
|
className: "w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0",
|
|
@@ -3455,7 +3492,7 @@ declare const context: JSEditorContext;
|
|
|
3455
3492
|
children: "No execution history yet"
|
|
3456
3493
|
})
|
|
3457
3494
|
}) : [...history].reverse().map((code, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3458
|
-
className: "p-2 py-1.5 rounded hover:bg-surface-100 dark:hover:bg-surface-
|
|
3495
|
+
className: "p-2 py-1.5 rounded hover:bg-surface-100 dark:hover:bg-surface-900 cursor-pointer group transition-colors flex items-start",
|
|
3459
3496
|
onClick: () => onSelectSnippet(code),
|
|
3460
3497
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3461
3498
|
className: "w-3 h-3 mt-1 mr-2 text-text-disabled dark:text-text-disabled-dark group-hover:text-primary transition-colors flex-shrink-0",
|
|
@@ -5021,6 +5058,12 @@ return result;
|
|
|
5021
5058
|
//#endregion
|
|
5022
5059
|
//#region src/components/RLSEditor/RLSEditor.tsx
|
|
5023
5060
|
var RLSEditor_exports = /* @__PURE__ */ __exportAll({ RLSEditor: () => RLSEditor$1 });
|
|
5061
|
+
function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
|
|
5062
|
+
if (_rebasepro_common.REBASE_INTERNAL_SCHEMAS.includes(schemaName) || _rebasepro_common.REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
|
|
5063
|
+
if (isMappedToCollection) return "collection";
|
|
5064
|
+
if (junctionTableNames.has(tableName)) return "junction";
|
|
5065
|
+
return "other";
|
|
5066
|
+
}
|
|
5024
5067
|
/**
|
|
5025
5068
|
* Validates and double-quotes a SQL identifier to prevent injection.
|
|
5026
5069
|
* Only allows safe Postgres identifiers (letters, digits, underscores).
|
|
@@ -5030,6 +5073,81 @@ return result;
|
|
|
5030
5073
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`Invalid SQL identifier: "${name}". Only letters, digits, and underscores are allowed.`);
|
|
5031
5074
|
return `"${name}"`;
|
|
5032
5075
|
}
|
|
5076
|
+
function SidebarSection({ title, icon, expanded, onToggle, count, children }) {
|
|
5077
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5078
|
+
className: "mb-2",
|
|
5079
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5080
|
+
className: "flex items-center p-1.5 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
|
|
5081
|
+
onClick: onToggle,
|
|
5082
|
+
children: [
|
|
5083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5084
|
+
className: (0, _rebasepro_ui.cls)("w-3 h-3 mr-1.5 transition-transform text-text-disabled dark:text-text-disabled-dark", expanded ? "rotate-90" : ""),
|
|
5085
|
+
fill: "currentColor",
|
|
5086
|
+
viewBox: "0 0 20 20",
|
|
5087
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" })
|
|
5088
|
+
}),
|
|
5089
|
+
icon,
|
|
5090
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5091
|
+
variant: "body2",
|
|
5092
|
+
className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow ml-1.5",
|
|
5093
|
+
children: title
|
|
5094
|
+
}),
|
|
5095
|
+
count !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5096
|
+
className: "text-[10px] text-text-disabled dark:text-text-disabled-dark font-medium tabular-nums mr-1",
|
|
5097
|
+
children: count
|
|
5098
|
+
})
|
|
5099
|
+
]
|
|
5100
|
+
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5101
|
+
className: "ml-3 mt-0.5 space-y-0.5",
|
|
5102
|
+
children
|
|
5103
|
+
})]
|
|
5104
|
+
});
|
|
5105
|
+
}
|
|
5106
|
+
function SidebarTableRow({ table, isSelected, onSelect, badge, dimmed, t }) {
|
|
5107
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5108
|
+
onClick: onSelect,
|
|
5109
|
+
className: (0, _rebasepro_ui.cls)("flex items-center p-1 cursor-pointer rounded transition-colors group relative", isSelected ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light" : "hover:bg-surface-100 dark:hover:bg-surface-900 text-text-secondary dark:text-text-secondary-dark", dimmed && !isSelected && "opacity-60"),
|
|
5110
|
+
children: [
|
|
5111
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5112
|
+
className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
|
|
5113
|
+
fill: "none",
|
|
5114
|
+
stroke: "currentColor",
|
|
5115
|
+
viewBox: "0 0 24 24",
|
|
5116
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
5117
|
+
strokeLinecap: "round",
|
|
5118
|
+
strokeLinejoin: "round",
|
|
5119
|
+
strokeWidth: 2,
|
|
5120
|
+
d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
5121
|
+
})
|
|
5122
|
+
}),
|
|
5123
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5124
|
+
variant: "body2",
|
|
5125
|
+
className: "text-xs truncate flex-1 min-w-0",
|
|
5126
|
+
children: table.tableName
|
|
5127
|
+
}),
|
|
5128
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5129
|
+
className: "flex items-center gap-1.5 shrink-0 ml-2",
|
|
5130
|
+
children: [
|
|
5131
|
+
badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5132
|
+
className: "text-[9px] uppercase tracking-wider font-semibold text-text-disabled dark:text-text-disabled-dark bg-surface-200 dark:bg-surface-800 rounded px-1 py-px",
|
|
5133
|
+
children: badge
|
|
5134
|
+
}),
|
|
5135
|
+
table.rlsEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
5136
|
+
title: t("studio_rls_enabled"),
|
|
5137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
|
|
5138
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
5139
|
+
title: t("studio_rls_disabled"),
|
|
5140
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
|
|
5141
|
+
}),
|
|
5142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5143
|
+
className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
|
|
5144
|
+
children: table.policies.length
|
|
5145
|
+
})
|
|
5146
|
+
]
|
|
5147
|
+
})
|
|
5148
|
+
]
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
5033
5151
|
var RLSEditor$1;
|
|
5034
5152
|
var init_RLSEditor = __esmMin((() => {
|
|
5035
5153
|
init_PolicyEditor();
|
|
@@ -5043,6 +5161,7 @@ return result;
|
|
|
5043
5161
|
const [tables, setTables] = (0, react.useState)([]);
|
|
5044
5162
|
const [selectedTable, setSelectedTable] = (0, react.useState)(null);
|
|
5045
5163
|
const [activeTab, setActiveTab] = (0, react.useState)(0);
|
|
5164
|
+
const [junctionTableNames, setJunctionTableNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5046
5165
|
const [editingPolicy, setEditingPolicy] = (0, react.useState)(null);
|
|
5047
5166
|
const [sidebarSize, setSidebarSize] = (0, react.useState)(() => {
|
|
5048
5167
|
try {
|
|
@@ -5052,7 +5171,11 @@ return result;
|
|
|
5052
5171
|
return 20;
|
|
5053
5172
|
}
|
|
5054
5173
|
});
|
|
5055
|
-
const [
|
|
5174
|
+
const [expandedSections, setExpandedSections] = (0, react.useState)({
|
|
5175
|
+
collection: true,
|
|
5176
|
+
other: true,
|
|
5177
|
+
internal: false
|
|
5178
|
+
});
|
|
5056
5179
|
const [sidebarTab, setSidebarTab] = (0, react.useState)("tables");
|
|
5057
5180
|
(0, react.useEffect)(() => {
|
|
5058
5181
|
try {
|
|
@@ -5134,7 +5257,15 @@ return result;
|
|
|
5134
5257
|
});
|
|
5135
5258
|
const sortedTables = Object.values(tableMap).sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
5136
5259
|
setTables(sortedTables);
|
|
5137
|
-
|
|
5260
|
+
try {
|
|
5261
|
+
const jRows = extractRows(await databaseAdmin.executeSql(_rebasepro_common.JUNCTION_TABLES_SQL));
|
|
5262
|
+
setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
|
|
5263
|
+
} catch {}
|
|
5264
|
+
if (sortedTables.length > 0 && !selectedTable) {
|
|
5265
|
+
const firstUserTable = sortedTables.find((t) => !_rebasepro_common.REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
|
|
5266
|
+
if (firstUserTable) setSelectedTable(`${firstUserTable.schemaName}.${firstUserTable.tableName}`);
|
|
5267
|
+
else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
|
|
5268
|
+
}
|
|
5138
5269
|
} catch (e) {
|
|
5139
5270
|
console.error("RLS fetch error:", e);
|
|
5140
5271
|
setError("Failed to fetch RLS policies: " + (e instanceof Error ? e.message : String(e)));
|
|
@@ -5152,18 +5283,38 @@ return result;
|
|
|
5152
5283
|
if (!selectedTable) return null;
|
|
5153
5284
|
return tables.find((t) => `${t.schemaName}.${t.tableName}` === selectedTable) || null;
|
|
5154
5285
|
}, [selectedTable, tables]);
|
|
5155
|
-
|
|
5156
|
-
|
|
5286
|
+
/** Categorize tables into 4 buckets for the sidebar. */
|
|
5287
|
+
const categorizedTables = (0, react.useMemo)(() => {
|
|
5288
|
+
const groups = {
|
|
5289
|
+
collection: [],
|
|
5290
|
+
junction: [],
|
|
5291
|
+
internal: [],
|
|
5292
|
+
other: []
|
|
5293
|
+
};
|
|
5157
5294
|
tables.forEach((table) => {
|
|
5158
|
-
|
|
5159
|
-
groups[table.schemaName].push(table);
|
|
5295
|
+
const isMapped = !!collectionRegistry.collections?.find((c) => c.id === table.tableName || c.path === table.tableName || c.table === table.tableName || c.slug === table.tableName || c.collectionId === table.tableName);
|
|
5296
|
+
groups[classifyTableClient(table.tableName, table.schemaName, junctionTableNames, isMapped)].push(table);
|
|
5160
5297
|
});
|
|
5161
5298
|
return groups;
|
|
5162
|
-
}, [
|
|
5299
|
+
}, [
|
|
5300
|
+
tables,
|
|
5301
|
+
junctionTableNames,
|
|
5302
|
+
collectionRegistry.collections
|
|
5303
|
+
]);
|
|
5163
5304
|
const activeCollection = (0, react.useMemo)(() => {
|
|
5164
5305
|
if (!activeTableData) return null;
|
|
5165
5306
|
return collectionRegistry.collections?.find((c) => c.id === activeTableData.tableName || c.path === activeTableData.tableName || c.table === activeTableData.tableName || c.slug === activeTableData.tableName || c.collectionId === activeTableData.tableName) || null;
|
|
5166
5307
|
}, [activeTableData, collectionRegistry.collections]);
|
|
5308
|
+
/** The category of the currently selected table. */
|
|
5309
|
+
const activeTableCategory = (0, react.useMemo)(() => {
|
|
5310
|
+
if (!activeTableData) return null;
|
|
5311
|
+
const isMapped = !!activeCollection;
|
|
5312
|
+
return classifyTableClient(activeTableData.tableName, activeTableData.schemaName, junctionTableNames, isMapped);
|
|
5313
|
+
}, [
|
|
5314
|
+
activeTableData,
|
|
5315
|
+
activeCollection,
|
|
5316
|
+
junctionTableNames
|
|
5317
|
+
]);
|
|
5167
5318
|
const mergedPolicies = (0, react.useMemo)(() => {
|
|
5168
5319
|
if (!activeTableData) return [];
|
|
5169
5320
|
const policiesMap = {};
|
|
@@ -5261,74 +5412,78 @@ return result;
|
|
|
5261
5412
|
children: isLoading && tables.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5262
5413
|
className: "flex justify-center p-4",
|
|
5263
5414
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, { size: "small" })
|
|
5264
|
-
}) :
|
|
5415
|
+
}) : tables.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5265
5416
|
className: "p-4 text-center",
|
|
5266
5417
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5267
5418
|
variant: "caption",
|
|
5268
5419
|
className: "text-text-disabled dark:text-text-disabled-dark italic",
|
|
5269
5420
|
children: t("studio_rls_no_tables")
|
|
5270
5421
|
})
|
|
5271
|
-
}) :
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5422
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5423
|
+
categorizedTables.collection.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSection, {
|
|
5424
|
+
title: "Schema Collections",
|
|
5425
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DatabaseIcon, {
|
|
5426
|
+
size: 12,
|
|
5427
|
+
className: "text-primary dark:text-primary-light"
|
|
5428
|
+
}),
|
|
5429
|
+
expanded: expandedSections.collection ?? true,
|
|
5430
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5276
5431
|
...prev,
|
|
5277
|
-
|
|
5432
|
+
collection: !(prev.collection ?? true)
|
|
5278
5433
|
})),
|
|
5279
|
-
children:
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
}
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
})
|
|
5330
|
-
})
|
|
5331
|
-
}
|
|
5434
|
+
children: categorizedTables.collection.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5435
|
+
table,
|
|
5436
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5437
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5438
|
+
t
|
|
5439
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
5440
|
+
}),
|
|
5441
|
+
(categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SidebarSection, {
|
|
5442
|
+
title: "Other Tables",
|
|
5443
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertTriangleIcon, {
|
|
5444
|
+
size: 12,
|
|
5445
|
+
className: "text-yellow-500 dark:text-yellow-400"
|
|
5446
|
+
}),
|
|
5447
|
+
expanded: expandedSections.other ?? true,
|
|
5448
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5449
|
+
...prev,
|
|
5450
|
+
other: !(prev.other ?? true)
|
|
5451
|
+
})),
|
|
5452
|
+
children: [categorizedTables.other.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5453
|
+
table,
|
|
5454
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5455
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5456
|
+
t
|
|
5457
|
+
}, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5458
|
+
table,
|
|
5459
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5460
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5461
|
+
badge: "Junction",
|
|
5462
|
+
dimmed: true,
|
|
5463
|
+
t
|
|
5464
|
+
}, `${table.schemaName}.${table.tableName}`))]
|
|
5465
|
+
}),
|
|
5466
|
+
categorizedTables.internal.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSection, {
|
|
5467
|
+
title: "Rebase Internal",
|
|
5468
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.LockIcon, {
|
|
5469
|
+
size: 12,
|
|
5470
|
+
className: "text-text-disabled dark:text-text-disabled-dark"
|
|
5471
|
+
}),
|
|
5472
|
+
expanded: expandedSections.internal ?? false,
|
|
5473
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5474
|
+
...prev,
|
|
5475
|
+
internal: !(prev.internal ?? false)
|
|
5476
|
+
})),
|
|
5477
|
+
count: categorizedTables.internal.length,
|
|
5478
|
+
children: categorizedTables.internal.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5479
|
+
table,
|
|
5480
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5481
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5482
|
+
dimmed: true,
|
|
5483
|
+
t
|
|
5484
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
5485
|
+
})
|
|
5486
|
+
] })
|
|
5332
5487
|
})]
|
|
5333
5488
|
}), sidebarTab === "info" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5334
5489
|
className: "flex flex-col h-full",
|
|
@@ -5491,10 +5646,11 @@ return result;
|
|
|
5491
5646
|
size: "small",
|
|
5492
5647
|
onClick: async () => {
|
|
5493
5648
|
const table = activeTableData.tableName;
|
|
5649
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
5494
5650
|
const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
|
|
5495
5651
|
if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
|
|
5496
5652
|
try {
|
|
5497
|
-
await databaseAdmin.executeSql(`ALTER TABLE ${
|
|
5653
|
+
await databaseAdmin.executeSql(`ALTER TABLE ${qualifiedTable} ${action} ROW LEVEL SECURITY`);
|
|
5498
5654
|
snackbarController.open({
|
|
5499
5655
|
type: "success",
|
|
5500
5656
|
message: `RLS ${action.toLowerCase()}d on ${table}`
|
|
@@ -5521,7 +5677,6 @@ return result;
|
|
|
5521
5677
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
5522
5678
|
size: "small",
|
|
5523
5679
|
color: "primary",
|
|
5524
|
-
disabled: !activeCollection,
|
|
5525
5680
|
onClick: () => setEditingPolicy("new"),
|
|
5526
5681
|
children: t("studio_rls_create_policy")
|
|
5527
5682
|
})
|
|
@@ -5562,31 +5717,57 @@ return result;
|
|
|
5562
5717
|
schema: activeTableData.schemaName,
|
|
5563
5718
|
table: activeTableData.tableName,
|
|
5564
5719
|
onSave: async (newPolicy) => {
|
|
5565
|
-
if (
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5720
|
+
if (activeCollection) {
|
|
5721
|
+
const rule = {
|
|
5722
|
+
name: newPolicy.policyname,
|
|
5723
|
+
operation: newPolicy.cmd?.toLowerCase(),
|
|
5724
|
+
mode: newPolicy.permissive?.toLowerCase(),
|
|
5725
|
+
using: newPolicy.qual || void 0,
|
|
5726
|
+
withCheck: newPolicy.with_check || void 0,
|
|
5727
|
+
roles: newPolicy.roles
|
|
5728
|
+
};
|
|
5729
|
+
const existingRules = ((0, _rebasepro_types.isPostgresCollection)(activeCollection) ? activeCollection.securityRules : void 0) || [];
|
|
5730
|
+
let newRules;
|
|
5731
|
+
if (editingPolicy === "new") newRules = [...existingRules, rule];
|
|
5732
|
+
else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
|
|
5733
|
+
try {
|
|
5734
|
+
if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
5735
|
+
method: "POST",
|
|
5736
|
+
headers: { "Content-Type": "application/json" },
|
|
5737
|
+
body: JSON.stringify({
|
|
5738
|
+
collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
|
|
5739
|
+
collectionData: { securityRules: newRules }
|
|
5740
|
+
})
|
|
5741
|
+
})).ok) throw new Error("Failed to save policy");
|
|
5742
|
+
snackbarController.open({
|
|
5743
|
+
type: "success",
|
|
5744
|
+
message: "Policy saved successfully"
|
|
5745
|
+
});
|
|
5746
|
+
setEditingPolicy(null);
|
|
5747
|
+
fetchRLSData();
|
|
5748
|
+
} catch (e) {
|
|
5749
|
+
snackbarController.open({
|
|
5750
|
+
type: "error",
|
|
5751
|
+
message: e instanceof Error ? e.message : String(e)
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5754
|
+
} else try {
|
|
5755
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
|
|
5756
|
+
const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
|
|
5757
|
+
const cmd = newPolicy.cmd || "ALL";
|
|
5758
|
+
const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
|
|
5759
|
+
const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
|
|
5760
|
+
if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
|
|
5761
|
+
let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
|
|
5762
|
+
sql += ` AS ${permissive}`;
|
|
5763
|
+
sql += ` FOR ${cmd}`;
|
|
5764
|
+
sql += ` TO ${roles}`;
|
|
5765
|
+
if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
|
|
5766
|
+
if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
|
|
5767
|
+
await databaseAdmin.executeSql(sql);
|
|
5587
5768
|
snackbarController.open({
|
|
5588
5769
|
type: "success",
|
|
5589
|
-
message:
|
|
5770
|
+
message: `Policy "${newPolicy.policyname}" applied directly via SQL`
|
|
5590
5771
|
});
|
|
5591
5772
|
setEditingPolicy(null);
|
|
5592
5773
|
fetchRLSData();
|
|
@@ -5605,17 +5786,59 @@ return result;
|
|
|
5605
5786
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5606
5787
|
className: "max-w-4xl mx-auto flex flex-col gap-6",
|
|
5607
5788
|
children: [
|
|
5608
|
-
activeTableData &&
|
|
5789
|
+
activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5790
|
+
color: "info",
|
|
5791
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5792
|
+
className: "flex items-start gap-2",
|
|
5793
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.LockIcon, {
|
|
5794
|
+
size: 16,
|
|
5795
|
+
className: "shrink-0 mt-0.5"
|
|
5796
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5797
|
+
variant: "body2",
|
|
5798
|
+
className: "mb-1 font-semibold",
|
|
5799
|
+
children: "Rebase System Table"
|
|
5800
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5801
|
+
variant: "caption",
|
|
5802
|
+
className: "opacity-80",
|
|
5803
|
+
children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
|
|
5804
|
+
})] })]
|
|
5805
|
+
})
|
|
5806
|
+
}),
|
|
5807
|
+
activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5808
|
+
color: "info",
|
|
5809
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5810
|
+
className: "flex items-start gap-2",
|
|
5811
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Link2Icon, {
|
|
5812
|
+
size: 16,
|
|
5813
|
+
className: "shrink-0 mt-0.5"
|
|
5814
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5815
|
+
variant: "body2",
|
|
5816
|
+
className: "mb-1 font-semibold",
|
|
5817
|
+
children: "Junction Table"
|
|
5818
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5819
|
+
variant: "caption",
|
|
5820
|
+
className: "opacity-80",
|
|
5821
|
+
children: "This is an auto-generated junction table for a many-to-many relation. Its access is typically managed through the related collections' policies. You can still add RLS policies directly if needed."
|
|
5822
|
+
})] })]
|
|
5823
|
+
})
|
|
5824
|
+
}),
|
|
5825
|
+
activeTableData && activeTableCategory === "other" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5609
5826
|
color: "warning",
|
|
5610
|
-
children:
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5827
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5828
|
+
className: "flex items-start gap-2",
|
|
5829
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertTriangleIcon, {
|
|
5830
|
+
size: 16,
|
|
5831
|
+
className: "shrink-0 mt-0.5"
|
|
5832
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5833
|
+
variant: "body2",
|
|
5834
|
+
className: "mb-1 font-semibold",
|
|
5835
|
+
children: "Unmapped Table"
|
|
5836
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5837
|
+
variant: "caption",
|
|
5838
|
+
className: "opacity-80",
|
|
5839
|
+
children: "This table exists in the database but isn't mapped to a collection definition. Import it into a Schema configuration file to manage security policies visually."
|
|
5840
|
+
})] })]
|
|
5841
|
+
})
|
|
5619
5842
|
}),
|
|
5620
5843
|
activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5621
5844
|
className: (0, _rebasepro_ui.cls)("p-4 sm:p-5 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-900/50 rounded-lg flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between", _rebasepro_ui.defaultBorderMixin),
|
|
@@ -5729,7 +5952,6 @@ return result;
|
|
|
5729
5952
|
variant: "text",
|
|
5730
5953
|
color: "primary",
|
|
5731
5954
|
onClick: () => setEditingPolicy(policy),
|
|
5732
|
-
disabled: !activeCollection,
|
|
5733
5955
|
children: t("studio_rls_edit")
|
|
5734
5956
|
}),
|
|
5735
5957
|
policy.status !== "code_only" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
@@ -5739,9 +5961,10 @@ return result;
|
|
|
5739
5961
|
size: "small",
|
|
5740
5962
|
onClick: async () => {
|
|
5741
5963
|
const table = activeTableData.tableName;
|
|
5964
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
5742
5965
|
if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
|
|
5743
5966
|
try {
|
|
5744
|
-
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${
|
|
5967
|
+
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${qualifiedTable}`);
|
|
5745
5968
|
snackbarController.open({
|
|
5746
5969
|
type: "success",
|
|
5747
5970
|
message: `Policy "${policy.policyname}" dropped`
|
|
@@ -5778,7 +6001,7 @@ return result;
|
|
|
5778
6001
|
className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
|
|
5779
6002
|
children: "RLS is enabled on this table but no policies exist. All access is denied by default (Postgres deny-all). Create a policy to allow specific access."
|
|
5780
6003
|
}),
|
|
5781
|
-
|
|
6004
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
5782
6005
|
size: "small",
|
|
5783
6006
|
variant: "filled",
|
|
5784
6007
|
color: "primary",
|
|
@@ -6223,8 +6446,16 @@ return result;
|
|
|
6223
6446
|
var StorageView$1;
|
|
6224
6447
|
var init_StorageView = __esmMin((() => {
|
|
6225
6448
|
StorageView$1 = () => {
|
|
6226
|
-
const
|
|
6449
|
+
const defaultStorageSource = (0, _rebasepro_core.useStorageSource)();
|
|
6450
|
+
const storageSources = (0, _rebasepro_core.useStorageSources)();
|
|
6227
6451
|
const snackbarController = (0, _rebasepro_core.useSnackbarController)();
|
|
6452
|
+
const sourceKeys = (0, react.useMemo)(() => {
|
|
6453
|
+
const keys = Object.keys(storageSources.sources);
|
|
6454
|
+
if (!keys.includes(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY)) keys.unshift(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
|
|
6455
|
+
return keys;
|
|
6456
|
+
}, [storageSources.sources]);
|
|
6457
|
+
const [selectedSourceKey, setSelectedSourceKey] = (0, react.useState)(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
|
|
6458
|
+
const storageSource = storageSources.sources[selectedSourceKey] ?? defaultStorageSource;
|
|
6228
6459
|
const [searchParams, setSearchParams] = (0, react_router_dom.useSearchParams)();
|
|
6229
6460
|
const currentPath = searchParams.get("path") || "";
|
|
6230
6461
|
const [loading, setLoading] = (0, react.useState)(true);
|
|
@@ -6288,7 +6519,11 @@ return result;
|
|
|
6288
6519
|
}, []);
|
|
6289
6520
|
(0, react.useEffect)(() => {
|
|
6290
6521
|
fetchContents(currentPath);
|
|
6291
|
-
}, [
|
|
6522
|
+
}, [
|
|
6523
|
+
currentPath,
|
|
6524
|
+
fetchContents,
|
|
6525
|
+
selectedSourceKey
|
|
6526
|
+
]);
|
|
6292
6527
|
const handleNavigate = (0, react.useCallback)((path) => {
|
|
6293
6528
|
if (!path) setSearchParams({});
|
|
6294
6529
|
else setSearchParams({ path });
|
|
@@ -6968,6 +7203,21 @@ return result;
|
|
|
6968
7203
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6969
7204
|
className: "flex shrink-0 items-center justify-end gap-1.5 pr-1",
|
|
6970
7205
|
children: [
|
|
7206
|
+
sourceKeys.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Select, {
|
|
7207
|
+
size: "small",
|
|
7208
|
+
position: "item-aligned",
|
|
7209
|
+
value: selectedSourceKey,
|
|
7210
|
+
onValueChange: (value) => {
|
|
7211
|
+
if (value) setSelectedSourceKey(value);
|
|
7212
|
+
},
|
|
7213
|
+
renderValue: (key) => {
|
|
7214
|
+
return storageSources.registry[key]?.label ?? (key === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? "Default" : key);
|
|
7215
|
+
},
|
|
7216
|
+
children: sourceKeys.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
7217
|
+
value: key,
|
|
7218
|
+
children: storageSources.registry[key]?.label ?? (key === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? "Default" : key)
|
|
7219
|
+
}, key))
|
|
7220
|
+
}),
|
|
6971
7221
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
6972
7222
|
title: "Grid view",
|
|
6973
7223
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
@@ -7213,7 +7463,7 @@ return result;
|
|
|
7213
7463
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
7214
7464
|
return `${(ms / 6e4).toFixed(1)}m`;
|
|
7215
7465
|
}
|
|
7216
|
-
function formatRelative$
|
|
7466
|
+
function formatRelative$2(iso) {
|
|
7217
7467
|
if (!iso) return "—";
|
|
7218
7468
|
const d = new Date(iso);
|
|
7219
7469
|
const now = Date.now();
|
|
@@ -7501,20 +7751,20 @@ return result;
|
|
|
7501
7751
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7502
7752
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
7503
7753
|
children: [
|
|
7504
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7754
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7505
7755
|
label: "Schedule",
|
|
7506
7756
|
value: selectedJob.schedule,
|
|
7507
7757
|
mono: true
|
|
7508
7758
|
}),
|
|
7509
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7759
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7510
7760
|
label: "Last Run",
|
|
7511
|
-
value: formatRelative$
|
|
7761
|
+
value: formatRelative$2(selectedJob.lastRunAt)
|
|
7512
7762
|
}),
|
|
7513
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7763
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7514
7764
|
label: "Next Run",
|
|
7515
|
-
value: selectedJob.enabled ? formatRelative$
|
|
7765
|
+
value: selectedJob.enabled ? formatRelative$2(selectedJob.nextRunAt) : "Paused"
|
|
7516
7766
|
}),
|
|
7517
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7518
7768
|
label: "Duration",
|
|
7519
7769
|
value: selectedJob.lastDurationMs !== void 0 ? formatDuration(selectedJob.lastDurationMs) : "—"
|
|
7520
7770
|
})
|
|
@@ -7523,16 +7773,16 @@ return result;
|
|
|
7523
7773
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7524
7774
|
className: "grid grid-cols-3 gap-3 mt-3",
|
|
7525
7775
|
children: [
|
|
7526
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7527
7777
|
label: "Status",
|
|
7528
7778
|
value: selectedJob.state.toUpperCase(),
|
|
7529
7779
|
chipColor: selectedJob.state === "error" ? "red" : selectedJob.state === "disabled" ? "gray" : "green"
|
|
7530
7780
|
}),
|
|
7531
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7532
7782
|
label: "Total Runs",
|
|
7533
7783
|
value: String(selectedJob.totalRuns)
|
|
7534
7784
|
}),
|
|
7535
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7536
7786
|
label: "Failures",
|
|
7537
7787
|
value: String(selectedJob.totalFailures),
|
|
7538
7788
|
highlight: selectedJob.totalFailures > 0
|
|
@@ -7599,7 +7849,7 @@ return result;
|
|
|
7599
7849
|
})]
|
|
7600
7850
|
});
|
|
7601
7851
|
}
|
|
7602
|
-
function StatCard$
|
|
7852
|
+
function StatCard$2({ label, value, mono, chipColor, highlight }) {
|
|
7603
7853
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7604
7854
|
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
7605
7855
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
@@ -8756,7 +9006,7 @@ return result;
|
|
|
8756
9006
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8757
9007
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
8758
9008
|
}
|
|
8759
|
-
function formatRelative(date) {
|
|
9009
|
+
function formatRelative$1(date) {
|
|
8760
9010
|
if (!date) return "—";
|
|
8761
9011
|
const d = date instanceof Date ? date : new Date(date);
|
|
8762
9012
|
if (isNaN(d.getTime())) return "—";
|
|
@@ -8958,7 +9208,7 @@ return result;
|
|
|
8958
9208
|
"from ",
|
|
8959
9209
|
branch.parentDatabase,
|
|
8960
9210
|
" · ",
|
|
8961
|
-
formatRelative(branch.createdAt)
|
|
9211
|
+
formatRelative$1(branch.createdAt)
|
|
8962
9212
|
]
|
|
8963
9213
|
})]
|
|
8964
9214
|
}),
|
|
@@ -9022,21 +9272,21 @@ return result;
|
|
|
9022
9272
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9023
9273
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
9024
9274
|
children: [
|
|
9025
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9026
9276
|
label: "Branch Name",
|
|
9027
9277
|
value: selected.name,
|
|
9028
9278
|
mono: true
|
|
9029
9279
|
}),
|
|
9030
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9280
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9031
9281
|
label: "Source Database",
|
|
9032
9282
|
value: selected.parentDatabase,
|
|
9033
9283
|
mono: true
|
|
9034
9284
|
}),
|
|
9035
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9036
9286
|
label: "Created",
|
|
9037
|
-
value: formatRelative(selected.createdAt)
|
|
9287
|
+
value: formatRelative$1(selected.createdAt)
|
|
9038
9288
|
}),
|
|
9039
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9289
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9040
9290
|
label: "Size",
|
|
9041
9291
|
value: formatSize(selected.sizeBytes)
|
|
9042
9292
|
})
|
|
@@ -9138,13 +9388,12 @@ return result;
|
|
|
9138
9388
|
}) }),
|
|
9139
9389
|
branches.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
9140
9390
|
label: "Source Database",
|
|
9141
|
-
value: sourceBranch ?? "",
|
|
9142
|
-
onValueChange: (v) => setSourceBranch(v
|
|
9391
|
+
value: sourceBranch ?? "__main__",
|
|
9392
|
+
onValueChange: (v) => setSourceBranch(v === "__main__" ? void 0 : v),
|
|
9143
9393
|
placeholder: "Default (main database)",
|
|
9144
9394
|
size: "small",
|
|
9145
|
-
renderValue: (v) => v || "Default (main database)",
|
|
9146
9395
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
9147
|
-
value: "",
|
|
9396
|
+
value: "__main__",
|
|
9148
9397
|
children: "Default (main database)"
|
|
9149
9398
|
}), branches.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
9150
9399
|
value: b.name,
|
|
@@ -9185,7 +9434,7 @@ return result;
|
|
|
9185
9434
|
]
|
|
9186
9435
|
});
|
|
9187
9436
|
}
|
|
9188
|
-
function StatCard({ label, value, mono }) {
|
|
9437
|
+
function StatCard$1({ label, value, mono }) {
|
|
9189
9438
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9190
9439
|
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
9191
9440
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
@@ -10174,16 +10423,16 @@ return result;
|
|
|
10174
10423
|
var LogsExplorer_exports = /* @__PURE__ */ __exportAll({ LogsExplorer: () => LogsExplorer$1 });
|
|
10175
10424
|
function LogsExplorer$1() {
|
|
10176
10425
|
const [logs, setLogs] = (0, react.useState)([]);
|
|
10177
|
-
const [level, setLevel] = (0, react.useState)("");
|
|
10178
|
-
const [source, setSource] = (0, react.useState)("");
|
|
10426
|
+
const [level, setLevel] = (0, react.useState)("all");
|
|
10427
|
+
const [source, setSource] = (0, react.useState)("all");
|
|
10179
10428
|
const [search, setSearch] = (0, react.useState)("");
|
|
10180
10429
|
const [autoScroll, setAutoScroll] = (0, react.useState)(true);
|
|
10181
10430
|
const containerRef = (0, react.useRef)(null);
|
|
10182
10431
|
const fetchLogs = (0, react.useCallback)(async () => {
|
|
10183
10432
|
try {
|
|
10184
10433
|
const params = new URLSearchParams();
|
|
10185
|
-
if (level) params.set("level", level);
|
|
10186
|
-
if (source) params.set("source", source);
|
|
10434
|
+
if (level && level !== "all") params.set("level", level);
|
|
10435
|
+
if (source && source !== "all") params.set("source", source);
|
|
10187
10436
|
if (search) params.set("search", search);
|
|
10188
10437
|
params.set("limit", "200");
|
|
10189
10438
|
const resp = await fetch(`/api/logs?${params}`);
|
|
@@ -10220,22 +10469,9 @@ return result;
|
|
|
10220
10469
|
if (autoScroll && containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
10221
10470
|
}, [logs, autoScroll]);
|
|
10222
10471
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10223
|
-
|
|
10224
|
-
display: "flex",
|
|
10225
|
-
flexDirection: "column",
|
|
10226
|
-
height: "calc(100vh - 64px)",
|
|
10227
|
-
background: "#1e1e2e",
|
|
10228
|
-
color: "#cdd6f4"
|
|
10229
|
-
},
|
|
10472
|
+
className: "flex flex-col h-[calc(100vh-64px)] w-full bg-surface-50 dark:bg-surface-800",
|
|
10230
10473
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10231
|
-
|
|
10232
|
-
display: "flex",
|
|
10233
|
-
gap: 8,
|
|
10234
|
-
padding: "8px 16px",
|
|
10235
|
-
borderBottom: "1px solid #313244",
|
|
10236
|
-
alignItems: "center",
|
|
10237
|
-
flexWrap: "wrap"
|
|
10238
|
-
},
|
|
10474
|
+
className: (0, _rebasepro_ui.cls)("flex gap-2 p-3 border-b items-center flex-wrap shrink-0", _rebasepro_ui.defaultBorderMixin),
|
|
10239
10475
|
children: [
|
|
10240
10476
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
10241
10477
|
value: level,
|
|
@@ -10244,7 +10480,7 @@ return result;
|
|
|
10244
10480
|
placeholder: "All Levels",
|
|
10245
10481
|
children: [
|
|
10246
10482
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
10247
|
-
value: "",
|
|
10483
|
+
value: "all",
|
|
10248
10484
|
children: "All Levels"
|
|
10249
10485
|
}),
|
|
10250
10486
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
@@ -10272,7 +10508,7 @@ return result;
|
|
|
10272
10508
|
placeholder: "All Sources",
|
|
10273
10509
|
children: [
|
|
10274
10510
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
10275
|
-
value: "",
|
|
10511
|
+
value: "all",
|
|
10276
10512
|
children: "All Sources"
|
|
10277
10513
|
}),
|
|
10278
10514
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
@@ -10305,7 +10541,7 @@ return result;
|
|
|
10305
10541
|
className: "flex-1 min-w-[200px]"
|
|
10306
10542
|
}),
|
|
10307
10543
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10308
|
-
className: "flex items-center gap-1.5 cursor-pointer",
|
|
10544
|
+
className: "flex items-center gap-1.5 cursor-pointer ml-2",
|
|
10309
10545
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
10310
10546
|
id: "auto-scroll",
|
|
10311
10547
|
checked: autoScroll,
|
|
@@ -10314,79 +10550,58 @@ return result;
|
|
|
10314
10550
|
padding: false
|
|
10315
10551
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Label, {
|
|
10316
10552
|
htmlFor: "auto-scroll",
|
|
10317
|
-
className: "text-xs select-none cursor-pointer",
|
|
10553
|
+
className: "text-xs select-none cursor-pointer text-surface-600 dark:text-surface-400",
|
|
10318
10554
|
children: "Auto-scroll"
|
|
10319
10555
|
})]
|
|
10320
10556
|
}),
|
|
10321
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10557
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10558
|
+
className: "ml-auto pl-4",
|
|
10559
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10560
|
+
variant: "caption",
|
|
10561
|
+
color: "secondary",
|
|
10562
|
+
children: [logs.length, " entries"]
|
|
10563
|
+
})
|
|
10327
10564
|
})
|
|
10328
10565
|
]
|
|
10329
10566
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10330
10567
|
ref: containerRef,
|
|
10331
|
-
|
|
10332
|
-
flex: 1,
|
|
10333
|
-
overflow: "auto",
|
|
10334
|
-
fontFamily: "monospace",
|
|
10335
|
-
fontSize: 12,
|
|
10336
|
-
padding: "8px 0"
|
|
10337
|
-
},
|
|
10568
|
+
className: "flex-1 overflow-auto py-2",
|
|
10338
10569
|
children: [logs.map((log) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10339
|
-
|
|
10340
|
-
padding: "2px 16px",
|
|
10341
|
-
display: "flex",
|
|
10342
|
-
gap: 8,
|
|
10343
|
-
borderBottom: "1px solid #181825"
|
|
10344
|
-
},
|
|
10570
|
+
className: (0, _rebasepro_ui.cls)("flex gap-4 px-4 py-[6px] border-b hover:bg-surface-100 dark:hover:bg-surface-900 transition-colors", _rebasepro_ui.defaultBorderMixin),
|
|
10345
10571
|
children: [
|
|
10346
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
},
|
|
10572
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10573
|
+
variant: "body2",
|
|
10574
|
+
color: "secondary",
|
|
10575
|
+
className: "w-[72px] shrink-0 font-mono",
|
|
10351
10576
|
children: new Date(log.timestamp).toLocaleTimeString()
|
|
10352
10577
|
}),
|
|
10353
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
width: 40,
|
|
10357
|
-
flexShrink: 0,
|
|
10358
|
-
textTransform: "uppercase",
|
|
10359
|
-
fontWeight: 600
|
|
10360
|
-
},
|
|
10578
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10579
|
+
variant: "body2",
|
|
10580
|
+
className: (0, _rebasepro_ui.cls)("w-[48px] shrink-0 uppercase font-semibold font-mono", LEVEL_COLORS[log.level] || "text-surface-500"),
|
|
10361
10581
|
children: log.level
|
|
10362
10582
|
}),
|
|
10363
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
width: 64,
|
|
10367
|
-
flexShrink: 0
|
|
10368
|
-
},
|
|
10583
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10584
|
+
variant: "body2",
|
|
10585
|
+
className: (0, _rebasepro_ui.cls)("w-[80px] shrink-0 font-mono", SOURCE_COLORS[log.source] || "text-surface-500"),
|
|
10369
10586
|
children: [
|
|
10370
10587
|
"[",
|
|
10371
10588
|
log.source,
|
|
10372
10589
|
"]"
|
|
10373
10590
|
]
|
|
10374
10591
|
}),
|
|
10375
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
flex: 1
|
|
10379
|
-
},
|
|
10592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10593
|
+
variant: "body2",
|
|
10594
|
+
className: "flex-1 font-mono break-all whitespace-pre-wrap text-surface-900 dark:text-surface-100",
|
|
10380
10595
|
children: log.message
|
|
10381
10596
|
})
|
|
10382
10597
|
]
|
|
10383
10598
|
}, log.id)), logs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
color: "
|
|
10388
|
-
|
|
10389
|
-
|
|
10599
|
+
className: "p-8 text-center",
|
|
10600
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10601
|
+
variant: "body2",
|
|
10602
|
+
color: "secondary",
|
|
10603
|
+
children: "No log entries yet. Logs will appear here as requests come in."
|
|
10604
|
+
})
|
|
10390
10605
|
})]
|
|
10391
10606
|
})]
|
|
10392
10607
|
});
|
|
@@ -10394,30 +10609,710 @@ return result;
|
|
|
10394
10609
|
var LEVEL_COLORS, SOURCE_COLORS;
|
|
10395
10610
|
var init_LogsExplorer = __esmMin((() => {
|
|
10396
10611
|
LEVEL_COLORS = {
|
|
10397
|
-
debug: "
|
|
10398
|
-
info: "
|
|
10399
|
-
warn: "
|
|
10400
|
-
error: "
|
|
10612
|
+
debug: "text-surface-500",
|
|
10613
|
+
info: "text-blue-600 dark:text-blue-500",
|
|
10614
|
+
warn: "text-amber-600 dark:text-amber-500",
|
|
10615
|
+
error: "text-red-600 dark:text-red-500"
|
|
10401
10616
|
};
|
|
10402
10617
|
SOURCE_COLORS = {
|
|
10403
|
-
api: "
|
|
10404
|
-
auth: "
|
|
10405
|
-
storage: "
|
|
10406
|
-
realtime: "
|
|
10407
|
-
system: "
|
|
10618
|
+
api: "text-sky-600 dark:text-sky-400",
|
|
10619
|
+
auth: "text-purple-600 dark:text-purple-400",
|
|
10620
|
+
storage: "text-green-600 dark:text-green-500",
|
|
10621
|
+
realtime: "text-orange-600 dark:text-orange-400",
|
|
10622
|
+
system: "text-surface-600 dark:text-surface-400"
|
|
10408
10623
|
};
|
|
10409
10624
|
}));
|
|
10410
10625
|
//#endregion
|
|
10411
|
-
//#region src/components/
|
|
10412
|
-
var
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10626
|
+
//#region src/components/ApiKeys/ApiKeysView.tsx
|
|
10627
|
+
var ApiKeysView_exports = /* @__PURE__ */ __exportAll({ ApiKeysView: () => ApiKeysView$1 });
|
|
10628
|
+
function formatRelative(iso) {
|
|
10629
|
+
if (!iso) return "—";
|
|
10630
|
+
const d = new Date(iso);
|
|
10631
|
+
const now = Date.now();
|
|
10632
|
+
const diff = d.getTime() - now;
|
|
10633
|
+
const abs = Math.abs(diff);
|
|
10634
|
+
if (abs < 6e4) return diff > 0 ? "in <1m" : "<1m ago";
|
|
10635
|
+
if (abs < 36e5) {
|
|
10636
|
+
const m = Math.round(abs / 6e4);
|
|
10637
|
+
return diff > 0 ? `in ${m}m` : `${m}m ago`;
|
|
10638
|
+
}
|
|
10639
|
+
if (abs < 864e5) {
|
|
10640
|
+
const h = Math.round(abs / 36e5);
|
|
10641
|
+
return diff > 0 ? `in ${h}h` : `${h}h ago`;
|
|
10642
|
+
}
|
|
10643
|
+
return d.toLocaleDateString();
|
|
10644
|
+
}
|
|
10645
|
+
function permissionSummary(perms) {
|
|
10646
|
+
if (perms.length === 0) return "No permissions";
|
|
10647
|
+
const wildcard = perms.find((p) => p.collection === "*");
|
|
10648
|
+
if (wildcard) return `All collections (${wildcard.operations.join(", ")})`;
|
|
10649
|
+
if (perms.length === 1) return `${perms[0].collection} (${perms[0].operations.join(", ")})`;
|
|
10650
|
+
return `${perms.length} collections`;
|
|
10651
|
+
}
|
|
10652
|
+
function isExpired(key) {
|
|
10653
|
+
return !!(key.expires_at && new Date(key.expires_at) < /* @__PURE__ */ new Date());
|
|
10654
|
+
}
|
|
10655
|
+
function keyStatus(key) {
|
|
10656
|
+
if (key.revoked_at) return {
|
|
10657
|
+
label: "Revoked",
|
|
10658
|
+
color: "text-red-500"
|
|
10659
|
+
};
|
|
10660
|
+
if (isExpired(key)) return {
|
|
10661
|
+
label: "Expired",
|
|
10662
|
+
color: "text-amber-500"
|
|
10663
|
+
};
|
|
10664
|
+
return {
|
|
10665
|
+
label: "Active",
|
|
10666
|
+
color: "text-emerald-500"
|
|
10667
|
+
};
|
|
10668
|
+
}
|
|
10669
|
+
function ApiKeysView$1() {
|
|
10670
|
+
const client = (0, _rebasepro_core.useRebaseClient)();
|
|
10671
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
10672
|
+
const [keys, setKeys] = (0, react.useState)([]);
|
|
10673
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
10674
|
+
const [selectedId, setSelectedId] = (0, react.useState)(null);
|
|
10675
|
+
const [showCreate, setShowCreate] = (0, react.useState)(false);
|
|
10676
|
+
const [showSecret, setShowSecret] = (0, react.useState)(null);
|
|
10677
|
+
const [revoking, setRevoking] = (0, react.useState)(null);
|
|
10678
|
+
const clientRef = (0, react.useRef)(client);
|
|
10679
|
+
clientRef.current = client;
|
|
10680
|
+
const snackbarRef = (0, react.useRef)(snackbar);
|
|
10681
|
+
snackbarRef.current = snackbar;
|
|
10682
|
+
const loadKeys = (0, react.useCallback)(async () => {
|
|
10683
|
+
const c = clientRef.current;
|
|
10684
|
+
if (!c?.apiKeys) {
|
|
10685
|
+
setLoading(false);
|
|
10686
|
+
return;
|
|
10687
|
+
}
|
|
10688
|
+
try {
|
|
10689
|
+
setKeys((await c.apiKeys.listKeys()).keys);
|
|
10690
|
+
} catch (e) {
|
|
10691
|
+
snackbarRef.current.open({
|
|
10692
|
+
type: "error",
|
|
10693
|
+
message: e instanceof Error ? e.message : String(e)
|
|
10694
|
+
});
|
|
10695
|
+
} finally {
|
|
10696
|
+
setLoading(false);
|
|
10697
|
+
}
|
|
10698
|
+
}, []);
|
|
10699
|
+
(0, react.useEffect)(() => {
|
|
10700
|
+
loadKeys();
|
|
10701
|
+
}, [loadKeys]);
|
|
10702
|
+
const handleRevoke = async (id) => {
|
|
10703
|
+
const c = clientRef.current;
|
|
10704
|
+
if (!c?.apiKeys) return;
|
|
10705
|
+
setRevoking(id);
|
|
10706
|
+
try {
|
|
10707
|
+
await c.apiKeys.revokeKey(id);
|
|
10708
|
+
snackbarRef.current.open({
|
|
10709
|
+
type: "success",
|
|
10710
|
+
message: "API key revoked"
|
|
10711
|
+
});
|
|
10712
|
+
await loadKeys();
|
|
10713
|
+
if (selectedId === id) setSelectedId(null);
|
|
10714
|
+
} catch (e) {
|
|
10715
|
+
snackbarRef.current.open({
|
|
10716
|
+
type: "error",
|
|
10717
|
+
message: e instanceof Error ? e.message : String(e)
|
|
10718
|
+
});
|
|
10719
|
+
} finally {
|
|
10720
|
+
setRevoking(null);
|
|
10721
|
+
}
|
|
10722
|
+
};
|
|
10723
|
+
const handleCreated = (keyWithSecret) => {
|
|
10724
|
+
setShowCreate(false);
|
|
10725
|
+
setShowSecret(keyWithSecret);
|
|
10726
|
+
loadKeys();
|
|
10727
|
+
};
|
|
10728
|
+
const selectedKey = keys.find((k) => k.id === selectedId);
|
|
10729
|
+
const activeKeys = keys.filter((k) => !k.revoked_at && !isExpired(k));
|
|
10730
|
+
const inactiveKeys = keys.filter((k) => k.revoked_at || isExpired(k));
|
|
10731
|
+
if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10732
|
+
className: "flex items-center justify-center h-full",
|
|
10733
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, {})
|
|
10734
|
+
});
|
|
10735
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10737
|
+
className: "flex h-full w-full overflow-hidden bg-white dark:bg-surface-950",
|
|
10738
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10739
|
+
className: (0, _rebasepro_ui.cls)("flex flex-col w-[340px] min-w-[280px] border-r h-full", _rebasepro_ui.defaultBorderMixin),
|
|
10740
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10741
|
+
className: (0, _rebasepro_ui.cls)("flex items-center justify-between px-4 py-2.5 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", _rebasepro_ui.defaultBorderMixin),
|
|
10742
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10743
|
+
className: "flex items-center gap-2",
|
|
10744
|
+
children: [
|
|
10745
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10746
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
10747
|
+
className: "text-primary"
|
|
10748
|
+
}),
|
|
10749
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10750
|
+
variant: "subtitle2",
|
|
10751
|
+
className: "font-semibold",
|
|
10752
|
+
children: "API Keys"
|
|
10753
|
+
}),
|
|
10754
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10755
|
+
size: "smallest",
|
|
10756
|
+
className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
|
|
10757
|
+
children: activeKeys.length
|
|
10758
|
+
})
|
|
10759
|
+
]
|
|
10760
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10761
|
+
className: "flex items-center gap-1",
|
|
10762
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
10763
|
+
size: "small",
|
|
10764
|
+
onClick: loadKeys,
|
|
10765
|
+
title: "Refresh",
|
|
10766
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.RefreshCwIcon, { size: _rebasepro_ui.iconSize.smallest })
|
|
10767
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
10768
|
+
size: "small",
|
|
10769
|
+
color: "primary",
|
|
10770
|
+
onClick: () => setShowCreate(true),
|
|
10771
|
+
startIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.PlusIcon, { size: _rebasepro_ui.iconSize.smallest }),
|
|
10772
|
+
children: "New"
|
|
10773
|
+
})]
|
|
10774
|
+
})]
|
|
10775
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10776
|
+
className: "flex-1 overflow-y-auto p-2 space-y-1",
|
|
10777
|
+
children: [
|
|
10778
|
+
activeKeys.length === 0 && inactiveKeys.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10779
|
+
className: "flex flex-col items-center justify-center h-full gap-3 text-center p-6",
|
|
10780
|
+
children: [
|
|
10781
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10782
|
+
size: _rebasepro_ui.iconSize.medium,
|
|
10783
|
+
className: "text-surface-300 dark:text-surface-600"
|
|
10784
|
+
}),
|
|
10785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10786
|
+
variant: "body2",
|
|
10787
|
+
color: "secondary",
|
|
10788
|
+
children: "No API keys yet"
|
|
10789
|
+
}),
|
|
10790
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10791
|
+
variant: "caption",
|
|
10792
|
+
color: "disabled",
|
|
10793
|
+
children: "Create a key to enable machine-to-machine authentication"
|
|
10794
|
+
})
|
|
10795
|
+
]
|
|
10796
|
+
}),
|
|
10797
|
+
activeKeys.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyListItem, {
|
|
10798
|
+
apiKey: key,
|
|
10799
|
+
selected: selectedId === key.id,
|
|
10800
|
+
onClick: () => setSelectedId(key.id)
|
|
10801
|
+
}, key.id)),
|
|
10802
|
+
inactiveKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10803
|
+
className: "px-2 pt-3 pb-1",
|
|
10804
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10805
|
+
variant: "caption",
|
|
10806
|
+
color: "disabled",
|
|
10807
|
+
className: "text-[10px] uppercase tracking-wider font-medium",
|
|
10808
|
+
children: "Revoked / Expired"
|
|
10809
|
+
})
|
|
10810
|
+
}), inactiveKeys.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyListItem, {
|
|
10811
|
+
apiKey: key,
|
|
10812
|
+
selected: selectedId === key.id,
|
|
10813
|
+
onClick: () => setSelectedId(key.id)
|
|
10814
|
+
}, key.id))] })
|
|
10815
|
+
]
|
|
10816
|
+
})]
|
|
10817
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10818
|
+
className: "flex-1 flex flex-col min-w-0 h-full overflow-hidden",
|
|
10819
|
+
children: !selectedKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10820
|
+
className: "flex items-center justify-center h-full",
|
|
10821
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10822
|
+
variant: "body2",
|
|
10823
|
+
color: "disabled",
|
|
10824
|
+
children: "Select an API key to view details"
|
|
10825
|
+
})
|
|
10826
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10827
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10828
|
+
className: (0, _rebasepro_ui.cls)("flex items-center justify-between px-5 py-3 border-b bg-white dark:bg-surface-950 min-h-[56px]", _rebasepro_ui.defaultBorderMixin),
|
|
10829
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10830
|
+
className: "flex items-center gap-3 min-w-0",
|
|
10831
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10832
|
+
size: _rebasepro_ui.iconSize.small,
|
|
10833
|
+
className: "text-primary shrink-0"
|
|
10834
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10835
|
+
className: "min-w-0",
|
|
10836
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10837
|
+
variant: "subtitle1",
|
|
10838
|
+
className: "font-semibold truncate",
|
|
10839
|
+
children: selectedKey.name
|
|
10840
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10841
|
+
variant: "caption",
|
|
10842
|
+
color: "secondary",
|
|
10843
|
+
className: "font-mono text-[11px]",
|
|
10844
|
+
children: [selectedKey.key_prefix, "•••"]
|
|
10845
|
+
})]
|
|
10846
|
+
})]
|
|
10847
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10848
|
+
className: "flex items-center gap-2 shrink-0",
|
|
10849
|
+
children: !selectedKey.revoked_at && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
10850
|
+
size: "small",
|
|
10851
|
+
color: "error",
|
|
10852
|
+
variant: "outlined",
|
|
10853
|
+
onClick: () => handleRevoke(selectedKey.id),
|
|
10854
|
+
disabled: revoking === selectedKey.id,
|
|
10855
|
+
startIcon: revoking === selectedKey.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, { size: "smallest" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Trash2Icon, { size: _rebasepro_ui.iconSize.smallest }),
|
|
10856
|
+
children: "Revoke"
|
|
10857
|
+
})
|
|
10858
|
+
})]
|
|
10859
|
+
}),
|
|
10860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10861
|
+
className: "px-5 py-4 bg-surface-50 dark:bg-surface-900/50",
|
|
10862
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10863
|
+
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
10864
|
+
children: [
|
|
10865
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10866
|
+
label: "Status",
|
|
10867
|
+
value: keyStatus(selectedKey).label,
|
|
10868
|
+
className: keyStatus(selectedKey).color
|
|
10869
|
+
}),
|
|
10870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10871
|
+
label: "Created",
|
|
10872
|
+
value: formatRelative(selectedKey.created_at)
|
|
10873
|
+
}),
|
|
10874
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10875
|
+
label: "Last Used",
|
|
10876
|
+
value: formatRelative(selectedKey.last_used_at)
|
|
10877
|
+
}),
|
|
10878
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10879
|
+
label: "Expires",
|
|
10880
|
+
value: selectedKey.expires_at ? formatRelative(selectedKey.expires_at) : "Never"
|
|
10881
|
+
})
|
|
10882
|
+
]
|
|
10883
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10884
|
+
className: "grid grid-cols-2 gap-3 mt-3",
|
|
10885
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10886
|
+
label: "Rate Limit",
|
|
10887
|
+
value: selectedKey.rate_limit ? `${selectedKey.rate_limit}/15min` : "Default (1000/15min)"
|
|
10888
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10889
|
+
label: "Created By",
|
|
10890
|
+
value: selectedKey.created_by,
|
|
10891
|
+
mono: true
|
|
10892
|
+
})]
|
|
10893
|
+
})]
|
|
10894
|
+
}),
|
|
10895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10896
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-2 px-5 py-2 border-y bg-white dark:bg-surface-950", _rebasepro_ui.defaultBorderMixin),
|
|
10897
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10898
|
+
variant: "subtitle2",
|
|
10899
|
+
className: "font-semibold text-[13px]",
|
|
10900
|
+
children: "Permissions"
|
|
10901
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10902
|
+
size: "smallest",
|
|
10903
|
+
className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
|
|
10904
|
+
children: selectedKey.permissions.length
|
|
10905
|
+
})]
|
|
10906
|
+
}),
|
|
10907
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10908
|
+
className: "flex-1 overflow-y-auto px-5 py-3",
|
|
10909
|
+
children: selectedKey.permissions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10910
|
+
variant: "body2",
|
|
10911
|
+
color: "disabled",
|
|
10912
|
+
children: "No permissions configured"
|
|
10913
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10914
|
+
className: "space-y-2",
|
|
10915
|
+
children: selectedKey.permissions.map((perm, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10916
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-3 px-3 py-2 rounded-lg border", _rebasepro_ui.defaultBorderMixin),
|
|
10917
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10918
|
+
variant: "body2",
|
|
10919
|
+
className: "font-mono text-[13px] font-medium flex-1",
|
|
10920
|
+
children: perm.collection === "*" ? "* (all collections)" : perm.collection
|
|
10921
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10922
|
+
className: "flex items-center gap-1",
|
|
10923
|
+
children: perm.operations.map((op) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10924
|
+
size: "smallest",
|
|
10925
|
+
className: (0, _rebasepro_ui.cls)(op === "read" && "bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300", op === "write" && "bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300", op === "delete" && "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300"),
|
|
10926
|
+
children: op
|
|
10927
|
+
}, op))
|
|
10928
|
+
})]
|
|
10929
|
+
}, idx))
|
|
10930
|
+
})
|
|
10931
|
+
})
|
|
10932
|
+
] })
|
|
10933
|
+
})]
|
|
10934
|
+
}),
|
|
10935
|
+
showCreate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateApiKeyDialog, {
|
|
10936
|
+
onClose: () => setShowCreate(false),
|
|
10937
|
+
onCreated: handleCreated
|
|
10938
|
+
}),
|
|
10939
|
+
showSecret && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretDisplayDialog, {
|
|
10940
|
+
keyWithSecret: showSecret,
|
|
10941
|
+
onClose: () => setShowSecret(null)
|
|
10942
|
+
})
|
|
10943
|
+
] });
|
|
10944
|
+
}
|
|
10945
|
+
function KeyListItem({ apiKey, selected, onClick }) {
|
|
10946
|
+
const status = keyStatus(apiKey);
|
|
10947
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10948
|
+
onClick,
|
|
10949
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all", selected ? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/30" : "hover:bg-surface-100 dark:hover:bg-surface-950"),
|
|
10950
|
+
children: [
|
|
10951
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, _rebasepro_ui.cls)("w-2 h-2 rounded-full shrink-0", status.label === "Active" ? "bg-emerald-400" : status.label === "Expired" ? "bg-amber-400" : "bg-red-400") }),
|
|
10952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10953
|
+
className: "flex-1 min-w-0",
|
|
10954
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10955
|
+
variant: "body2",
|
|
10956
|
+
className: "truncate font-medium text-[13px]",
|
|
10957
|
+
children: apiKey.name
|
|
10958
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10959
|
+
variant: "caption",
|
|
10960
|
+
color: "secondary",
|
|
10961
|
+
className: "truncate text-[11px] font-mono",
|
|
10962
|
+
children: [apiKey.key_prefix, "•••"]
|
|
10963
|
+
})]
|
|
10964
|
+
}),
|
|
10965
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10966
|
+
className: "shrink-0",
|
|
10967
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10968
|
+
variant: "caption",
|
|
10969
|
+
color: "disabled",
|
|
10970
|
+
className: "text-[10px]",
|
|
10971
|
+
children: permissionSummary(apiKey.permissions)
|
|
10972
|
+
})
|
|
10973
|
+
})
|
|
10974
|
+
]
|
|
10975
|
+
});
|
|
10976
|
+
}
|
|
10977
|
+
function StatCard({ label, value, mono, className }) {
|
|
10978
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10979
|
+
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
10980
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10981
|
+
variant: "caption",
|
|
10982
|
+
color: "secondary",
|
|
10983
|
+
className: "text-[10px] uppercase tracking-wider font-medium",
|
|
10984
|
+
children: label
|
|
10985
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10986
|
+
variant: "body2",
|
|
10987
|
+
className: (0, _rebasepro_ui.cls)("mt-0.5 font-semibold text-[13px]", mono && "font-mono", className),
|
|
10988
|
+
children: value
|
|
10989
|
+
})]
|
|
10990
|
+
});
|
|
10991
|
+
}
|
|
10992
|
+
function CreateApiKeyDialog({ onClose, onCreated }) {
|
|
10993
|
+
const client = (0, _rebasepro_core.useRebaseClient)();
|
|
10994
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
10995
|
+
const [name, setName] = (0, react.useState)("");
|
|
10996
|
+
const [permissions, setPermissions] = (0, react.useState)([{
|
|
10997
|
+
collection: "*",
|
|
10998
|
+
read: true,
|
|
10999
|
+
write: false,
|
|
11000
|
+
delete: false
|
|
11001
|
+
}]);
|
|
11002
|
+
const [rateLimit, setRateLimit] = (0, react.useState)("");
|
|
11003
|
+
const [expiresIn, setExpiresIn] = (0, react.useState)("never");
|
|
11004
|
+
const [creating, setCreating] = (0, react.useState)(false);
|
|
11005
|
+
const addRow = () => setPermissions([...permissions, {
|
|
11006
|
+
collection: "",
|
|
11007
|
+
read: true,
|
|
11008
|
+
write: false,
|
|
11009
|
+
delete: false
|
|
11010
|
+
}]);
|
|
11011
|
+
const removeRow = (idx) => setPermissions(permissions.filter((_, i) => i !== idx));
|
|
11012
|
+
const updateRow = (idx, field, value) => {
|
|
11013
|
+
setPermissions(permissions.map((row, i) => i === idx ? {
|
|
11014
|
+
...row,
|
|
11015
|
+
[field]: value
|
|
11016
|
+
} : row));
|
|
11017
|
+
};
|
|
11018
|
+
const handleCreate = async () => {
|
|
11019
|
+
if (!client?.apiKeys || !name.trim()) return;
|
|
11020
|
+
const apiPerms = permissions.filter((r) => r.collection.trim()).map((r) => ({
|
|
11021
|
+
collection: r.collection.trim(),
|
|
11022
|
+
operations: [
|
|
11023
|
+
...r.read ? ["read"] : [],
|
|
11024
|
+
...r.write ? ["write"] : [],
|
|
11025
|
+
...r.delete ? ["delete"] : []
|
|
11026
|
+
]
|
|
11027
|
+
})).filter((p) => p.operations.length > 0);
|
|
11028
|
+
if (apiPerms.length === 0) {
|
|
11029
|
+
snackbar.open({
|
|
11030
|
+
type: "error",
|
|
11031
|
+
message: "At least one permission is required"
|
|
11032
|
+
});
|
|
11033
|
+
return;
|
|
11034
|
+
}
|
|
11035
|
+
let expires_at = null;
|
|
11036
|
+
if (expiresIn === "7d") expires_at = new Date(Date.now() + 7 * 864e5).toISOString();
|
|
11037
|
+
else if (expiresIn === "30d") expires_at = new Date(Date.now() + 30 * 864e5).toISOString();
|
|
11038
|
+
else if (expiresIn === "90d") expires_at = new Date(Date.now() + 90 * 864e5).toISOString();
|
|
11039
|
+
else if (expiresIn === "1y") expires_at = new Date(Date.now() + 365 * 864e5).toISOString();
|
|
11040
|
+
setCreating(true);
|
|
11041
|
+
try {
|
|
11042
|
+
onCreated((await client.apiKeys.createKey({
|
|
11043
|
+
name: name.trim(),
|
|
11044
|
+
permissions: apiPerms,
|
|
11045
|
+
rate_limit: rateLimit ? parseInt(rateLimit, 10) : null,
|
|
11046
|
+
expires_at
|
|
11047
|
+
})).key);
|
|
11048
|
+
} catch (e) {
|
|
11049
|
+
snackbar.open({
|
|
11050
|
+
type: "error",
|
|
11051
|
+
message: e instanceof Error ? e.message : String(e)
|
|
11052
|
+
});
|
|
11053
|
+
} finally {
|
|
11054
|
+
setCreating(false);
|
|
11055
|
+
}
|
|
11056
|
+
};
|
|
11057
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Dialog, {
|
|
11058
|
+
open: true,
|
|
11059
|
+
onOpenChange: (open) => {
|
|
11060
|
+
if (!open) onClose();
|
|
11061
|
+
},
|
|
11062
|
+
maxWidth: "lg",
|
|
11063
|
+
children: [
|
|
11064
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogTitle, { children: "Create API Key" }),
|
|
11065
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogContent, {
|
|
11066
|
+
className: "space-y-4",
|
|
11067
|
+
children: [
|
|
11068
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11069
|
+
label: "Name",
|
|
11070
|
+
value: name,
|
|
11071
|
+
onChange: (e) => setName(e.target.value),
|
|
11072
|
+
placeholder: "e.g. Analytics Pipeline",
|
|
11073
|
+
size: "small",
|
|
11074
|
+
autoFocus: true
|
|
11075
|
+
}),
|
|
11076
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
11077
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11078
|
+
variant: "caption",
|
|
11079
|
+
color: "secondary",
|
|
11080
|
+
className: "text-[11px] uppercase tracking-wider font-medium mb-2 block",
|
|
11081
|
+
children: "Permissions"
|
|
11082
|
+
}),
|
|
11083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11084
|
+
className: "space-y-2",
|
|
11085
|
+
children: permissions.map((row, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11086
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-2 p-2 rounded-lg border", _rebasepro_ui.defaultBorderMixin),
|
|
11087
|
+
children: [
|
|
11088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11089
|
+
size: "small",
|
|
11090
|
+
value: row.collection,
|
|
11091
|
+
onChange: (e) => updateRow(idx, "collection", e.target.value),
|
|
11092
|
+
placeholder: "Collection slug or *",
|
|
11093
|
+
className: "flex-1"
|
|
11094
|
+
}),
|
|
11095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11096
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11098
|
+
size: "small",
|
|
11099
|
+
checked: row.read,
|
|
11100
|
+
onCheckedChange: (v) => updateRow(idx, "read", !!v)
|
|
11101
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11102
|
+
className: "text-emerald-600 dark:text-emerald-400",
|
|
11103
|
+
children: "read"
|
|
11104
|
+
})]
|
|
11105
|
+
}),
|
|
11106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11107
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11108
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11109
|
+
size: "small",
|
|
11110
|
+
checked: row.write,
|
|
11111
|
+
onCheckedChange: (v) => updateRow(idx, "write", !!v)
|
|
11112
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11113
|
+
className: "text-blue-600 dark:text-blue-400",
|
|
11114
|
+
children: "write"
|
|
11115
|
+
})]
|
|
11116
|
+
}),
|
|
11117
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11118
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11119
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11120
|
+
size: "small",
|
|
11121
|
+
checked: row.delete,
|
|
11122
|
+
onCheckedChange: (v) => updateRow(idx, "delete", !!v)
|
|
11123
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11124
|
+
className: "text-red-600 dark:text-red-400",
|
|
11125
|
+
children: "delete"
|
|
11126
|
+
})]
|
|
11127
|
+
}),
|
|
11128
|
+
permissions.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
11129
|
+
size: "small",
|
|
11130
|
+
onClick: () => removeRow(idx),
|
|
11131
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Trash2Icon, { size: _rebasepro_ui.iconSize.smallest })
|
|
11132
|
+
})
|
|
11133
|
+
]
|
|
11134
|
+
}, idx))
|
|
11135
|
+
}),
|
|
11136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11137
|
+
size: "small",
|
|
11138
|
+
variant: "text",
|
|
11139
|
+
onClick: addRow,
|
|
11140
|
+
className: "mt-1",
|
|
11141
|
+
startIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.PlusIcon, { size: _rebasepro_ui.iconSize.smallest }),
|
|
11142
|
+
children: "Add collection"
|
|
11143
|
+
})
|
|
11144
|
+
] }),
|
|
11145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11146
|
+
className: "flex gap-4",
|
|
11147
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11148
|
+
className: "flex-1",
|
|
11149
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
11150
|
+
label: "Expires",
|
|
11151
|
+
value: expiresIn,
|
|
11152
|
+
onValueChange: setExpiresIn,
|
|
11153
|
+
size: "small",
|
|
11154
|
+
renderValue: (v) => v === "never" ? "Never" : v === "7d" ? "7 days" : v === "30d" ? "30 days" : v === "90d" ? "90 days" : "1 year",
|
|
11155
|
+
children: [
|
|
11156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11157
|
+
value: "never",
|
|
11158
|
+
children: "Never"
|
|
11159
|
+
}),
|
|
11160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11161
|
+
value: "7d",
|
|
11162
|
+
children: "7 days"
|
|
11163
|
+
}),
|
|
11164
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11165
|
+
value: "30d",
|
|
11166
|
+
children: "30 days"
|
|
11167
|
+
}),
|
|
11168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11169
|
+
value: "90d",
|
|
11170
|
+
children: "90 days"
|
|
11171
|
+
}),
|
|
11172
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11173
|
+
value: "1y",
|
|
11174
|
+
children: "1 year"
|
|
11175
|
+
})
|
|
11176
|
+
]
|
|
11177
|
+
})
|
|
11178
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11179
|
+
className: "flex-1",
|
|
11180
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11181
|
+
label: "Rate Limit (per 15 min)",
|
|
11182
|
+
value: rateLimit,
|
|
11183
|
+
onChange: (e) => setRateLimit(e.target.value.replace(/\D/g, "")),
|
|
11184
|
+
placeholder: "Default: 1000",
|
|
11185
|
+
size: "small"
|
|
11186
|
+
})
|
|
11187
|
+
})]
|
|
11188
|
+
})
|
|
11189
|
+
]
|
|
11190
|
+
}),
|
|
11191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogActions, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11192
|
+
variant: "text",
|
|
11193
|
+
onClick: onClose,
|
|
11194
|
+
children: "Cancel"
|
|
11195
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11196
|
+
color: "primary",
|
|
11197
|
+
onClick: handleCreate,
|
|
11198
|
+
disabled: creating || !name.trim(),
|
|
11199
|
+
startIcon: creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, { size: "smallest" }) : void 0,
|
|
11200
|
+
children: "Create Key"
|
|
11201
|
+
})] })
|
|
11202
|
+
]
|
|
11203
|
+
});
|
|
11204
|
+
}
|
|
11205
|
+
function SecretDisplayDialog({ keyWithSecret, onClose }) {
|
|
11206
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
11207
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
11208
|
+
const handleCopy = async () => {
|
|
11209
|
+
try {
|
|
11210
|
+
await navigator.clipboard.writeText(keyWithSecret.key);
|
|
11211
|
+
setCopied(true);
|
|
11212
|
+
snackbar.open({
|
|
11213
|
+
type: "success",
|
|
11214
|
+
message: "API key copied to clipboard"
|
|
11215
|
+
});
|
|
11216
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
11217
|
+
} catch {
|
|
11218
|
+
snackbar.open({
|
|
11219
|
+
type: "error",
|
|
11220
|
+
message: "Failed to copy"
|
|
11221
|
+
});
|
|
11222
|
+
}
|
|
11223
|
+
};
|
|
11224
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Dialog, {
|
|
11225
|
+
open: true,
|
|
11226
|
+
onOpenChange: (open) => {
|
|
11227
|
+
if (!open) onClose();
|
|
11228
|
+
},
|
|
11229
|
+
maxWidth: "md",
|
|
11230
|
+
children: [
|
|
11231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogTitle, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11232
|
+
className: "flex items-center gap-2",
|
|
11233
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CheckCircleIcon, {
|
|
11234
|
+
size: _rebasepro_ui.iconSize.small,
|
|
11235
|
+
className: "text-emerald-500"
|
|
11236
|
+
}), "API Key Created"]
|
|
11237
|
+
}) }),
|
|
11238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogContent, { children: [
|
|
11239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11240
|
+
className: "p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 mb-4",
|
|
11241
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11242
|
+
className: "flex items-center gap-2 mb-1",
|
|
11243
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertCircleIcon, {
|
|
11244
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
11245
|
+
className: "text-amber-600 dark:text-amber-400"
|
|
11246
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11247
|
+
variant: "caption",
|
|
11248
|
+
className: "font-semibold text-amber-700 dark:text-amber-400",
|
|
11249
|
+
children: "Copy your key now — it won't be shown again"
|
|
11250
|
+
})]
|
|
11251
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11252
|
+
variant: "caption",
|
|
11253
|
+
className: "text-amber-600 dark:text-amber-300",
|
|
11254
|
+
children: "This is the only time the full API key will be displayed. Store it securely."
|
|
11255
|
+
})]
|
|
11256
|
+
}),
|
|
11257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11258
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-2 p-3 rounded-lg border bg-surface-50 dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
11259
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
11260
|
+
className: "flex-1 text-[12px] font-mono break-all text-surface-700 dark:text-surface-300 select-all",
|
|
11261
|
+
children: keyWithSecret.key
|
|
11262
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
11263
|
+
title: copied ? "Copied!" : "Copy",
|
|
11264
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
11265
|
+
size: "small",
|
|
11266
|
+
onClick: handleCopy,
|
|
11267
|
+
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CheckCircleIcon, {
|
|
11268
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
11269
|
+
className: "text-emerald-500"
|
|
11270
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CopyIcon, { size: _rebasepro_ui.iconSize.smallest })
|
|
11271
|
+
})
|
|
11272
|
+
})]
|
|
11273
|
+
}),
|
|
11274
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11275
|
+
className: "mt-4 space-y-1",
|
|
11276
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
11277
|
+
variant: "caption",
|
|
11278
|
+
color: "secondary",
|
|
11279
|
+
children: [
|
|
11280
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Name:" }),
|
|
11281
|
+
" ",
|
|
11282
|
+
keyWithSecret.name
|
|
11283
|
+
]
|
|
11284
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
11285
|
+
variant: "caption",
|
|
11286
|
+
color: "secondary",
|
|
11287
|
+
children: [
|
|
11288
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Permissions:" }),
|
|
11289
|
+
" ",
|
|
11290
|
+
permissionSummary(keyWithSecret.permissions)
|
|
11291
|
+
]
|
|
11292
|
+
})]
|
|
11293
|
+
})
|
|
11294
|
+
] }),
|
|
11295
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogActions, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11296
|
+
color: "primary",
|
|
11297
|
+
onClick: onClose,
|
|
11298
|
+
children: "Done"
|
|
11299
|
+
}) })
|
|
11300
|
+
]
|
|
11301
|
+
});
|
|
11302
|
+
}
|
|
11303
|
+
var init_ApiKeysView = __esmMin((() => {}));
|
|
11304
|
+
//#endregion
|
|
11305
|
+
//#region src/components/RebaseStudio.tsx
|
|
11306
|
+
var SQLEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_SQLEditor(), SQLEditor_exports)).then((m) => ({ default: m.SQLEditor })));
|
|
11307
|
+
var JSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_JSEditor(), JSEditor_exports)).then((m) => ({ default: m.JSEditor })));
|
|
11308
|
+
var RLSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_RLSEditor(), RLSEditor_exports)).then((m) => ({ default: m.RLSEditor })));
|
|
11309
|
+
var StorageView = (0, react.lazy)(() => Promise.resolve().then(() => (init_StorageView(), StorageView_exports)).then((m) => ({ default: m.StorageView })));
|
|
11310
|
+
var CronJobsView = (0, react.lazy)(() => Promise.resolve().then(() => (init_CronJobsView(), CronJobsView_exports)).then((m) => ({ default: m.CronJobsView })));
|
|
11311
|
+
var SchemaVisualizer = (0, react.lazy)(() => Promise.resolve().then(() => (init_SchemaVisualizer(), SchemaVisualizer_exports)).then((m) => ({ default: m.SchemaVisualizer })));
|
|
11312
|
+
var BranchesView = (0, react.lazy)(() => Promise.resolve().then(() => (init_BranchesView(), BranchesView_exports)).then((m) => ({ default: m.BranchesView })));
|
|
10419
11313
|
var ApiExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_ApiExplorer(), ApiExplorer_exports)).then((m) => ({ default: m.ApiExplorer })));
|
|
10420
11314
|
var LogsExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_LogsExplorer(), LogsExplorer_exports)).then((m) => ({ default: m.LogsExplorer })));
|
|
11315
|
+
var ApiKeysView = (0, react.lazy)(() => Promise.resolve().then(() => (init_ApiKeysView(), ApiKeysView_exports)).then((m) => ({ default: m.ApiKeysView })));
|
|
10421
11316
|
/**
|
|
10422
11317
|
* Declarative component to configure the Studio in Rebase.
|
|
10423
11318
|
* Renders nothing — purely registers config into the RebaseRegistry.
|
|
@@ -10441,7 +11336,8 @@ return result;
|
|
|
10441
11336
|
"schema-visualizer",
|
|
10442
11337
|
"branches",
|
|
10443
11338
|
"api",
|
|
10444
|
-
"logs"
|
|
11339
|
+
"logs",
|
|
11340
|
+
"api-keys"
|
|
10445
11341
|
];
|
|
10446
11342
|
const suspense = (el) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
10447
11343
|
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgressCenter, {}),
|
|
@@ -10519,6 +11415,14 @@ return result;
|
|
|
10519
11415
|
description: "Real-time system and query logs",
|
|
10520
11416
|
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogsExplorer, {}))
|
|
10521
11417
|
});
|
|
11418
|
+
if (activeTools.includes("api-keys")) views.push({
|
|
11419
|
+
slug: "api-keys",
|
|
11420
|
+
name: "API Keys",
|
|
11421
|
+
group: "Access Control",
|
|
11422
|
+
icon: "KeyRound",
|
|
11423
|
+
description: "Create and manage scoped API keys",
|
|
11424
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApiKeysView, {}))
|
|
11425
|
+
});
|
|
10522
11426
|
return views;
|
|
10523
11427
|
}, [tools]);
|
|
10524
11428
|
const homePageRef = react.default.useRef(resolvedHomePage);
|