@rebasepro/studio 0.6.1 → 0.7.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-DSucz6wV.js} +4 -4
- package/dist/{JSEditor-BhAbEjCP.js.map → JSEditor-DSucz6wV.js.map} +1 -1
- 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/components/ApiKeys/ApiKeysView.d.ts +2 -0
- package/dist/index.es.js +37 -18
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1154 -305
- package/dist/index.umd.js.map +1 -1
- package/package.json +17 -17
- 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/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/StudioHomePage.tsx +5 -1
- package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
- package/dist/RLSEditor-DpF1u9EC.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;
|
|
@@ -143,17 +143,26 @@
|
|
|
143
143
|
label: "Access Control",
|
|
144
144
|
dotColor: "bg-rose-400",
|
|
145
145
|
iconColor: "text-rose-400",
|
|
146
|
-
tools: [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
146
|
+
tools: [
|
|
147
|
+
{
|
|
148
|
+
path: "/users",
|
|
149
|
+
name: "Users",
|
|
150
|
+
description: "Manage developers and assign roles in your workspace",
|
|
151
|
+
icon: "group"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
path: "/roles",
|
|
155
|
+
name: "Roles",
|
|
156
|
+
description: "Create and configure fine-grained access permissions",
|
|
157
|
+
icon: "ShieldEllipsis"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
path: "/api-keys",
|
|
161
|
+
name: "API Keys",
|
|
162
|
+
description: "Create and manage scoped keys for machine-to-machine access",
|
|
163
|
+
icon: "KeyRound"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
157
166
|
}
|
|
158
167
|
];
|
|
159
168
|
function StudioHomePage({ additionalActions, additionalChildrenStart, additionalChildrenEnd, sections, hiddenGroups }) {
|
|
@@ -896,7 +905,7 @@
|
|
|
896
905
|
}) : Object.entries(schemas).map(([schemaName, tables]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
897
906
|
className: "mb-2",
|
|
898
907
|
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-
|
|
908
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
|
|
900
909
|
onClick: () => setExpandedSchemas((prev) => ({
|
|
901
910
|
...prev,
|
|
902
911
|
[schemaName]: !prev[schemaName]
|
|
@@ -914,7 +923,7 @@
|
|
|
914
923
|
}), expandedSchemas[schemaName] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
915
924
|
className: "ml-3 mt-1 space-y-1",
|
|
916
925
|
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-
|
|
926
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors group relative",
|
|
918
927
|
onClick: () => setExpandedTables((prev) => ({
|
|
919
928
|
...prev,
|
|
920
929
|
[`${schemaName}.${table.tableName}`]: !prev[`${schemaName}.${table.tableName}`]
|
|
@@ -1017,7 +1026,7 @@
|
|
|
1017
1026
|
}), expandedTables[`${schemaName}.${table.tableName}`] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1018
1027
|
className: (0, _rebasepro_ui.cls)("ml-5 mt-1 space-y-0.5 border-l", _rebasepro_ui.defaultBorderMixin),
|
|
1019
1028
|
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-
|
|
1029
|
+
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
1030
|
children: [
|
|
1022
1031
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1023
1032
|
className: "w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0",
|
|
@@ -3130,7 +3139,7 @@ declare const context: JSEditorContext;
|
|
|
3130
3139
|
function CollectionItem({ collection, onInsertCode }) {
|
|
3131
3140
|
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
3132
3141
|
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-
|
|
3142
|
+
className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors group relative",
|
|
3134
3143
|
onClick: () => setExpanded(!expanded),
|
|
3135
3144
|
children: [
|
|
3136
3145
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
@@ -3172,7 +3181,7 @@ declare const context: JSEditorContext;
|
|
|
3172
3181
|
}), expanded && collection.properties.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3173
3182
|
className: (0, _rebasepro_ui.cls)("ml-5 mt-1 space-y-0.5 border-l", _rebasepro_ui.defaultBorderMixin),
|
|
3174
3183
|
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-
|
|
3184
|
+
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
3185
|
onClick: () => onInsertCode(`"${prop}"`),
|
|
3177
3186
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3178
3187
|
className: "w-3 h-3 mr-1.5 text-text-disabled dark:text-text-disabled-dark shrink-0",
|
|
@@ -3455,7 +3464,7 @@ declare const context: JSEditorContext;
|
|
|
3455
3464
|
children: "No execution history yet"
|
|
3456
3465
|
})
|
|
3457
3466
|
}) : [...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-
|
|
3467
|
+
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
3468
|
onClick: () => onSelectSnippet(code),
|
|
3460
3469
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
3461
3470
|
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 +5030,12 @@ return result;
|
|
|
5021
5030
|
//#endregion
|
|
5022
5031
|
//#region src/components/RLSEditor/RLSEditor.tsx
|
|
5023
5032
|
var RLSEditor_exports = /* @__PURE__ */ __exportAll({ RLSEditor: () => RLSEditor$1 });
|
|
5033
|
+
function classifyTableClient(tableName, schemaName, junctionTableNames, isMappedToCollection) {
|
|
5034
|
+
if (_rebasepro_common.REBASE_INTERNAL_SCHEMAS.includes(schemaName) || _rebasepro_common.REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "internal";
|
|
5035
|
+
if (isMappedToCollection) return "collection";
|
|
5036
|
+
if (junctionTableNames.has(tableName)) return "junction";
|
|
5037
|
+
return "other";
|
|
5038
|
+
}
|
|
5024
5039
|
/**
|
|
5025
5040
|
* Validates and double-quotes a SQL identifier to prevent injection.
|
|
5026
5041
|
* Only allows safe Postgres identifiers (letters, digits, underscores).
|
|
@@ -5030,6 +5045,81 @@ return result;
|
|
|
5030
5045
|
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
5046
|
return `"${name}"`;
|
|
5032
5047
|
}
|
|
5048
|
+
function SidebarSection({ title, icon, expanded, onToggle, count, children }) {
|
|
5049
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5050
|
+
className: "mb-2",
|
|
5051
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5052
|
+
className: "flex items-center p-1.5 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-900 rounded transition-colors",
|
|
5053
|
+
onClick: onToggle,
|
|
5054
|
+
children: [
|
|
5055
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5056
|
+
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" : ""),
|
|
5057
|
+
fill: "currentColor",
|
|
5058
|
+
viewBox: "0 0 20 20",
|
|
5059
|
+
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" })
|
|
5060
|
+
}),
|
|
5061
|
+
icon,
|
|
5062
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5063
|
+
variant: "body2",
|
|
5064
|
+
className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow ml-1.5",
|
|
5065
|
+
children: title
|
|
5066
|
+
}),
|
|
5067
|
+
count !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5068
|
+
className: "text-[10px] text-text-disabled dark:text-text-disabled-dark font-medium tabular-nums mr-1",
|
|
5069
|
+
children: count
|
|
5070
|
+
})
|
|
5071
|
+
]
|
|
5072
|
+
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5073
|
+
className: "ml-3 mt-0.5 space-y-0.5",
|
|
5074
|
+
children
|
|
5075
|
+
})]
|
|
5076
|
+
});
|
|
5077
|
+
}
|
|
5078
|
+
function SidebarTableRow({ table, isSelected, onSelect, badge, dimmed, t }) {
|
|
5079
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5080
|
+
onClick: onSelect,
|
|
5081
|
+
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"),
|
|
5082
|
+
children: [
|
|
5083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
5084
|
+
className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
|
|
5085
|
+
fill: "none",
|
|
5086
|
+
stroke: "currentColor",
|
|
5087
|
+
viewBox: "0 0 24 24",
|
|
5088
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
5089
|
+
strokeLinecap: "round",
|
|
5090
|
+
strokeLinejoin: "round",
|
|
5091
|
+
strokeWidth: 2,
|
|
5092
|
+
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"
|
|
5093
|
+
})
|
|
5094
|
+
}),
|
|
5095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5096
|
+
variant: "body2",
|
|
5097
|
+
className: "text-xs truncate flex-1 min-w-0",
|
|
5098
|
+
children: table.tableName
|
|
5099
|
+
}),
|
|
5100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5101
|
+
className: "flex items-center gap-1.5 shrink-0 ml-2",
|
|
5102
|
+
children: [
|
|
5103
|
+
badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5104
|
+
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",
|
|
5105
|
+
children: badge
|
|
5106
|
+
}),
|
|
5107
|
+
table.rlsEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
5108
|
+
title: t("studio_rls_enabled"),
|
|
5109
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
|
|
5110
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
5111
|
+
title: t("studio_rls_disabled"),
|
|
5112
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
|
|
5113
|
+
}),
|
|
5114
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5115
|
+
className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
|
|
5116
|
+
children: table.policies.length
|
|
5117
|
+
})
|
|
5118
|
+
]
|
|
5119
|
+
})
|
|
5120
|
+
]
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5033
5123
|
var RLSEditor$1;
|
|
5034
5124
|
var init_RLSEditor = __esmMin((() => {
|
|
5035
5125
|
init_PolicyEditor();
|
|
@@ -5043,6 +5133,7 @@ return result;
|
|
|
5043
5133
|
const [tables, setTables] = (0, react.useState)([]);
|
|
5044
5134
|
const [selectedTable, setSelectedTable] = (0, react.useState)(null);
|
|
5045
5135
|
const [activeTab, setActiveTab] = (0, react.useState)(0);
|
|
5136
|
+
const [junctionTableNames, setJunctionTableNames] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
5046
5137
|
const [editingPolicy, setEditingPolicy] = (0, react.useState)(null);
|
|
5047
5138
|
const [sidebarSize, setSidebarSize] = (0, react.useState)(() => {
|
|
5048
5139
|
try {
|
|
@@ -5052,7 +5143,11 @@ return result;
|
|
|
5052
5143
|
return 20;
|
|
5053
5144
|
}
|
|
5054
5145
|
});
|
|
5055
|
-
const [
|
|
5146
|
+
const [expandedSections, setExpandedSections] = (0, react.useState)({
|
|
5147
|
+
collection: true,
|
|
5148
|
+
other: true,
|
|
5149
|
+
internal: false
|
|
5150
|
+
});
|
|
5056
5151
|
const [sidebarTab, setSidebarTab] = (0, react.useState)("tables");
|
|
5057
5152
|
(0, react.useEffect)(() => {
|
|
5058
5153
|
try {
|
|
@@ -5134,7 +5229,15 @@ return result;
|
|
|
5134
5229
|
});
|
|
5135
5230
|
const sortedTables = Object.values(tableMap).sort((a, b) => a.tableName.localeCompare(b.tableName));
|
|
5136
5231
|
setTables(sortedTables);
|
|
5137
|
-
|
|
5232
|
+
try {
|
|
5233
|
+
const jRows = extractRows(await databaseAdmin.executeSql(_rebasepro_common.JUNCTION_TABLES_SQL));
|
|
5234
|
+
setJunctionTableNames(new Set(jRows.map((r) => r.table_name).filter(Boolean)));
|
|
5235
|
+
} catch {}
|
|
5236
|
+
if (sortedTables.length > 0 && !selectedTable) {
|
|
5237
|
+
const firstUserTable = sortedTables.find((t) => !_rebasepro_common.REBASE_INTERNAL_SCHEMAS.includes(t.schemaName));
|
|
5238
|
+
if (firstUserTable) setSelectedTable(`${firstUserTable.schemaName}.${firstUserTable.tableName}`);
|
|
5239
|
+
else setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
|
|
5240
|
+
}
|
|
5138
5241
|
} catch (e) {
|
|
5139
5242
|
console.error("RLS fetch error:", e);
|
|
5140
5243
|
setError("Failed to fetch RLS policies: " + (e instanceof Error ? e.message : String(e)));
|
|
@@ -5152,18 +5255,38 @@ return result;
|
|
|
5152
5255
|
if (!selectedTable) return null;
|
|
5153
5256
|
return tables.find((t) => `${t.schemaName}.${t.tableName}` === selectedTable) || null;
|
|
5154
5257
|
}, [selectedTable, tables]);
|
|
5155
|
-
|
|
5156
|
-
|
|
5258
|
+
/** Categorize tables into 4 buckets for the sidebar. */
|
|
5259
|
+
const categorizedTables = (0, react.useMemo)(() => {
|
|
5260
|
+
const groups = {
|
|
5261
|
+
collection: [],
|
|
5262
|
+
junction: [],
|
|
5263
|
+
internal: [],
|
|
5264
|
+
other: []
|
|
5265
|
+
};
|
|
5157
5266
|
tables.forEach((table) => {
|
|
5158
|
-
|
|
5159
|
-
groups[table.schemaName].push(table);
|
|
5267
|
+
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);
|
|
5268
|
+
groups[classifyTableClient(table.tableName, table.schemaName, junctionTableNames, isMapped)].push(table);
|
|
5160
5269
|
});
|
|
5161
5270
|
return groups;
|
|
5162
|
-
}, [
|
|
5271
|
+
}, [
|
|
5272
|
+
tables,
|
|
5273
|
+
junctionTableNames,
|
|
5274
|
+
collectionRegistry.collections
|
|
5275
|
+
]);
|
|
5163
5276
|
const activeCollection = (0, react.useMemo)(() => {
|
|
5164
5277
|
if (!activeTableData) return null;
|
|
5165
5278
|
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
5279
|
}, [activeTableData, collectionRegistry.collections]);
|
|
5280
|
+
/** The category of the currently selected table. */
|
|
5281
|
+
const activeTableCategory = (0, react.useMemo)(() => {
|
|
5282
|
+
if (!activeTableData) return null;
|
|
5283
|
+
const isMapped = !!activeCollection;
|
|
5284
|
+
return classifyTableClient(activeTableData.tableName, activeTableData.schemaName, junctionTableNames, isMapped);
|
|
5285
|
+
}, [
|
|
5286
|
+
activeTableData,
|
|
5287
|
+
activeCollection,
|
|
5288
|
+
junctionTableNames
|
|
5289
|
+
]);
|
|
5167
5290
|
const mergedPolicies = (0, react.useMemo)(() => {
|
|
5168
5291
|
if (!activeTableData) return [];
|
|
5169
5292
|
const policiesMap = {};
|
|
@@ -5261,74 +5384,78 @@ return result;
|
|
|
5261
5384
|
children: isLoading && tables.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5262
5385
|
className: "flex justify-center p-4",
|
|
5263
5386
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, { size: "small" })
|
|
5264
|
-
}) :
|
|
5387
|
+
}) : tables.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5265
5388
|
className: "p-4 text-center",
|
|
5266
5389
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5267
5390
|
variant: "caption",
|
|
5268
5391
|
className: "text-text-disabled dark:text-text-disabled-dark italic",
|
|
5269
5392
|
children: t("studio_rls_no_tables")
|
|
5270
5393
|
})
|
|
5271
|
-
}) :
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5394
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
5395
|
+
categorizedTables.collection.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSection, {
|
|
5396
|
+
title: "Schema Collections",
|
|
5397
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DatabaseIcon, {
|
|
5398
|
+
size: 12,
|
|
5399
|
+
className: "text-primary dark:text-primary-light"
|
|
5400
|
+
}),
|
|
5401
|
+
expanded: expandedSections.collection ?? true,
|
|
5402
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5276
5403
|
...prev,
|
|
5277
|
-
|
|
5404
|
+
collection: !(prev.collection ?? true)
|
|
5278
5405
|
})),
|
|
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
|
-
}
|
|
5406
|
+
children: categorizedTables.collection.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5407
|
+
table,
|
|
5408
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5409
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5410
|
+
t
|
|
5411
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
5412
|
+
}),
|
|
5413
|
+
(categorizedTables.other.length > 0 || categorizedTables.junction.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(SidebarSection, {
|
|
5414
|
+
title: "Other Tables",
|
|
5415
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertTriangleIcon, {
|
|
5416
|
+
size: 12,
|
|
5417
|
+
className: "text-yellow-500 dark:text-yellow-400"
|
|
5418
|
+
}),
|
|
5419
|
+
expanded: expandedSections.other ?? true,
|
|
5420
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5421
|
+
...prev,
|
|
5422
|
+
other: !(prev.other ?? true)
|
|
5423
|
+
})),
|
|
5424
|
+
children: [categorizedTables.other.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5425
|
+
table,
|
|
5426
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5427
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5428
|
+
t
|
|
5429
|
+
}, `${table.schemaName}.${table.tableName}`)), categorizedTables.junction.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5430
|
+
table,
|
|
5431
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5432
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5433
|
+
badge: "Junction",
|
|
5434
|
+
dimmed: true,
|
|
5435
|
+
t
|
|
5436
|
+
}, `${table.schemaName}.${table.tableName}`))]
|
|
5437
|
+
}),
|
|
5438
|
+
categorizedTables.internal.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarSection, {
|
|
5439
|
+
title: "Rebase Internal",
|
|
5440
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.LockIcon, {
|
|
5441
|
+
size: 12,
|
|
5442
|
+
className: "text-text-disabled dark:text-text-disabled-dark"
|
|
5443
|
+
}),
|
|
5444
|
+
expanded: expandedSections.internal ?? false,
|
|
5445
|
+
onToggle: () => setExpandedSections((prev) => ({
|
|
5446
|
+
...prev,
|
|
5447
|
+
internal: !(prev.internal ?? false)
|
|
5448
|
+
})),
|
|
5449
|
+
count: categorizedTables.internal.length,
|
|
5450
|
+
children: categorizedTables.internal.map((table) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SidebarTableRow, {
|
|
5451
|
+
table,
|
|
5452
|
+
isSelected: selectedTable === `${table.schemaName}.${table.tableName}`,
|
|
5453
|
+
onSelect: () => setSelectedTable(`${table.schemaName}.${table.tableName}`),
|
|
5454
|
+
dimmed: true,
|
|
5455
|
+
t
|
|
5456
|
+
}, `${table.schemaName}.${table.tableName}`))
|
|
5457
|
+
})
|
|
5458
|
+
] })
|
|
5332
5459
|
})]
|
|
5333
5460
|
}), sidebarTab === "info" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5334
5461
|
className: "flex flex-col h-full",
|
|
@@ -5491,10 +5618,11 @@ return result;
|
|
|
5491
5618
|
size: "small",
|
|
5492
5619
|
onClick: async () => {
|
|
5493
5620
|
const table = activeTableData.tableName;
|
|
5621
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
5494
5622
|
const action = activeTableData.rlsEnabled ? "DISABLE" : "ENABLE";
|
|
5495
5623
|
if (!confirm(`Are you sure you want to ${action.toLowerCase()} Row Level Security on "${table}"?`)) return;
|
|
5496
5624
|
try {
|
|
5497
|
-
await databaseAdmin.executeSql(`ALTER TABLE ${
|
|
5625
|
+
await databaseAdmin.executeSql(`ALTER TABLE ${qualifiedTable} ${action} ROW LEVEL SECURITY`);
|
|
5498
5626
|
snackbarController.open({
|
|
5499
5627
|
type: "success",
|
|
5500
5628
|
message: `RLS ${action.toLowerCase()}d on ${table}`
|
|
@@ -5521,7 +5649,6 @@ return result;
|
|
|
5521
5649
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
5522
5650
|
size: "small",
|
|
5523
5651
|
color: "primary",
|
|
5524
|
-
disabled: !activeCollection,
|
|
5525
5652
|
onClick: () => setEditingPolicy("new"),
|
|
5526
5653
|
children: t("studio_rls_create_policy")
|
|
5527
5654
|
})
|
|
@@ -5562,31 +5689,57 @@ return result;
|
|
|
5562
5689
|
schema: activeTableData.schemaName,
|
|
5563
5690
|
table: activeTableData.tableName,
|
|
5564
5691
|
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
|
-
|
|
5692
|
+
if (activeCollection) {
|
|
5693
|
+
const rule = {
|
|
5694
|
+
name: newPolicy.policyname,
|
|
5695
|
+
operation: newPolicy.cmd?.toLowerCase(),
|
|
5696
|
+
mode: newPolicy.permissive?.toLowerCase(),
|
|
5697
|
+
using: newPolicy.qual || void 0,
|
|
5698
|
+
withCheck: newPolicy.with_check || void 0,
|
|
5699
|
+
roles: newPolicy.roles
|
|
5700
|
+
};
|
|
5701
|
+
const existingRules = ((0, _rebasepro_types.isPostgresCollection)(activeCollection) ? activeCollection.securityRules : void 0) || [];
|
|
5702
|
+
let newRules;
|
|
5703
|
+
if (editingPolicy === "new") newRules = [...existingRules, rule];
|
|
5704
|
+
else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
|
|
5705
|
+
try {
|
|
5706
|
+
if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
|
|
5707
|
+
method: "POST",
|
|
5708
|
+
headers: { "Content-Type": "application/json" },
|
|
5709
|
+
body: JSON.stringify({
|
|
5710
|
+
collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
|
|
5711
|
+
collectionData: { securityRules: newRules }
|
|
5712
|
+
})
|
|
5713
|
+
})).ok) throw new Error("Failed to save policy");
|
|
5714
|
+
snackbarController.open({
|
|
5715
|
+
type: "success",
|
|
5716
|
+
message: "Policy saved successfully"
|
|
5717
|
+
});
|
|
5718
|
+
setEditingPolicy(null);
|
|
5719
|
+
fetchRLSData();
|
|
5720
|
+
} catch (e) {
|
|
5721
|
+
snackbarController.open({
|
|
5722
|
+
type: "error",
|
|
5723
|
+
message: e instanceof Error ? e.message : String(e)
|
|
5724
|
+
});
|
|
5725
|
+
}
|
|
5726
|
+
} else try {
|
|
5727
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(activeTableData.tableName)}`;
|
|
5728
|
+
const policyName = sanitizeSqlIdentifier(newPolicy.policyname || "unnamed_policy");
|
|
5729
|
+
const cmd = newPolicy.cmd || "ALL";
|
|
5730
|
+
const permissive = (newPolicy.permissive || "PERMISSIVE") === "PERMISSIVE" ? "PERMISSIVE" : "RESTRICTIVE";
|
|
5731
|
+
const roles = newPolicy.roles && newPolicy.roles.length > 0 ? newPolicy.roles.map((r) => sanitizeSqlIdentifier(r)).join(", ") : "public";
|
|
5732
|
+
if (editingPolicy !== "new") await databaseAdmin.executeSql(`DROP POLICY IF EXISTS ${policyName} ON ${qualifiedTable}`);
|
|
5733
|
+
let sql = `CREATE POLICY ${policyName} ON ${qualifiedTable}`;
|
|
5734
|
+
sql += ` AS ${permissive}`;
|
|
5735
|
+
sql += ` FOR ${cmd}`;
|
|
5736
|
+
sql += ` TO ${roles}`;
|
|
5737
|
+
if (newPolicy.qual) sql += ` USING (${newPolicy.qual})`;
|
|
5738
|
+
if (newPolicy.with_check) sql += ` WITH CHECK (${newPolicy.with_check})`;
|
|
5739
|
+
await databaseAdmin.executeSql(sql);
|
|
5587
5740
|
snackbarController.open({
|
|
5588
5741
|
type: "success",
|
|
5589
|
-
message:
|
|
5742
|
+
message: `Policy "${newPolicy.policyname}" applied directly via SQL`
|
|
5590
5743
|
});
|
|
5591
5744
|
setEditingPolicy(null);
|
|
5592
5745
|
fetchRLSData();
|
|
@@ -5605,17 +5758,59 @@ return result;
|
|
|
5605
5758
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5606
5759
|
className: "max-w-4xl mx-auto flex flex-col gap-6",
|
|
5607
5760
|
children: [
|
|
5608
|
-
activeTableData &&
|
|
5761
|
+
activeTableData && activeTableCategory === "internal" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5762
|
+
color: "info",
|
|
5763
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5764
|
+
className: "flex items-start gap-2",
|
|
5765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.LockIcon, {
|
|
5766
|
+
size: 16,
|
|
5767
|
+
className: "shrink-0 mt-0.5"
|
|
5768
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5769
|
+
variant: "body2",
|
|
5770
|
+
className: "mb-1 font-semibold",
|
|
5771
|
+
children: "Rebase System Table"
|
|
5772
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5773
|
+
variant: "caption",
|
|
5774
|
+
className: "opacity-80",
|
|
5775
|
+
children: "This table is managed internally by Rebase. Its security policies are configured automatically. Editing policies on system tables is an advanced operation."
|
|
5776
|
+
})] })]
|
|
5777
|
+
})
|
|
5778
|
+
}),
|
|
5779
|
+
activeTableData && activeTableCategory === "junction" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5780
|
+
color: "info",
|
|
5781
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5782
|
+
className: "flex items-start gap-2",
|
|
5783
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Link2Icon, {
|
|
5784
|
+
size: 16,
|
|
5785
|
+
className: "shrink-0 mt-0.5"
|
|
5786
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5787
|
+
variant: "body2",
|
|
5788
|
+
className: "mb-1 font-semibold",
|
|
5789
|
+
children: "Junction Table"
|
|
5790
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5791
|
+
variant: "caption",
|
|
5792
|
+
className: "opacity-80",
|
|
5793
|
+
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."
|
|
5794
|
+
})] })]
|
|
5795
|
+
})
|
|
5796
|
+
}),
|
|
5797
|
+
activeTableData && activeTableCategory === "other" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Alert, {
|
|
5609
5798
|
color: "warning",
|
|
5610
|
-
children:
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5799
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5800
|
+
className: "flex items-start gap-2",
|
|
5801
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertTriangleIcon, {
|
|
5802
|
+
size: 16,
|
|
5803
|
+
className: "shrink-0 mt-0.5"
|
|
5804
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5805
|
+
variant: "body2",
|
|
5806
|
+
className: "mb-1 font-semibold",
|
|
5807
|
+
children: "Unmapped Table"
|
|
5808
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
5809
|
+
variant: "caption",
|
|
5810
|
+
className: "opacity-80",
|
|
5811
|
+
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."
|
|
5812
|
+
})] })]
|
|
5813
|
+
})
|
|
5619
5814
|
}),
|
|
5620
5815
|
activeTableData && !activeTableData.rlsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5621
5816
|
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 +5924,6 @@ return result;
|
|
|
5729
5924
|
variant: "text",
|
|
5730
5925
|
color: "primary",
|
|
5731
5926
|
onClick: () => setEditingPolicy(policy),
|
|
5732
|
-
disabled: !activeCollection,
|
|
5733
5927
|
children: t("studio_rls_edit")
|
|
5734
5928
|
}),
|
|
5735
5929
|
policy.status !== "code_only" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
@@ -5739,9 +5933,10 @@ return result;
|
|
|
5739
5933
|
size: "small",
|
|
5740
5934
|
onClick: async () => {
|
|
5741
5935
|
const table = activeTableData.tableName;
|
|
5936
|
+
const qualifiedTable = `${sanitizeSqlIdentifier(activeTableData.schemaName)}.${sanitizeSqlIdentifier(table)}`;
|
|
5742
5937
|
if (!confirm(`Drop policy "${policy.policyname}" from table "${table}"?`)) return;
|
|
5743
5938
|
try {
|
|
5744
|
-
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${
|
|
5939
|
+
await databaseAdmin.executeSql(`DROP POLICY ${sanitizeSqlIdentifier(policy.policyname)} ON ${qualifiedTable}`);
|
|
5745
5940
|
snackbarController.open({
|
|
5746
5941
|
type: "success",
|
|
5747
5942
|
message: `Policy "${policy.policyname}" dropped`
|
|
@@ -5778,7 +5973,7 @@ return result;
|
|
|
5778
5973
|
className: "text-text-disabled dark:text-text-disabled-dark max-w-sm mb-4",
|
|
5779
5974
|
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
5975
|
}),
|
|
5781
|
-
|
|
5976
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
5782
5977
|
size: "small",
|
|
5783
5978
|
variant: "filled",
|
|
5784
5979
|
color: "primary",
|
|
@@ -7213,7 +7408,7 @@ return result;
|
|
|
7213
7408
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
7214
7409
|
return `${(ms / 6e4).toFixed(1)}m`;
|
|
7215
7410
|
}
|
|
7216
|
-
function formatRelative$
|
|
7411
|
+
function formatRelative$2(iso) {
|
|
7217
7412
|
if (!iso) return "—";
|
|
7218
7413
|
const d = new Date(iso);
|
|
7219
7414
|
const now = Date.now();
|
|
@@ -7501,20 +7696,20 @@ return result;
|
|
|
7501
7696
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7502
7697
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
7503
7698
|
children: [
|
|
7504
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7505
7700
|
label: "Schedule",
|
|
7506
7701
|
value: selectedJob.schedule,
|
|
7507
7702
|
mono: true
|
|
7508
7703
|
}),
|
|
7509
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7704
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7510
7705
|
label: "Last Run",
|
|
7511
|
-
value: formatRelative$
|
|
7706
|
+
value: formatRelative$2(selectedJob.lastRunAt)
|
|
7512
7707
|
}),
|
|
7513
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7708
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7514
7709
|
label: "Next Run",
|
|
7515
|
-
value: selectedJob.enabled ? formatRelative$
|
|
7710
|
+
value: selectedJob.enabled ? formatRelative$2(selectedJob.nextRunAt) : "Paused"
|
|
7516
7711
|
}),
|
|
7517
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7712
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7518
7713
|
label: "Duration",
|
|
7519
7714
|
value: selectedJob.lastDurationMs !== void 0 ? formatDuration(selectedJob.lastDurationMs) : "—"
|
|
7520
7715
|
})
|
|
@@ -7523,16 +7718,16 @@ return result;
|
|
|
7523
7718
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7524
7719
|
className: "grid grid-cols-3 gap-3 mt-3",
|
|
7525
7720
|
children: [
|
|
7526
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7721
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7527
7722
|
label: "Status",
|
|
7528
7723
|
value: selectedJob.state.toUpperCase(),
|
|
7529
7724
|
chipColor: selectedJob.state === "error" ? "red" : selectedJob.state === "disabled" ? "gray" : "green"
|
|
7530
7725
|
}),
|
|
7531
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7726
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7532
7727
|
label: "Total Runs",
|
|
7533
7728
|
value: String(selectedJob.totalRuns)
|
|
7534
7729
|
}),
|
|
7535
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$
|
|
7730
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
|
|
7536
7731
|
label: "Failures",
|
|
7537
7732
|
value: String(selectedJob.totalFailures),
|
|
7538
7733
|
highlight: selectedJob.totalFailures > 0
|
|
@@ -7599,7 +7794,7 @@ return result;
|
|
|
7599
7794
|
})]
|
|
7600
7795
|
});
|
|
7601
7796
|
}
|
|
7602
|
-
function StatCard$
|
|
7797
|
+
function StatCard$2({ label, value, mono, chipColor, highlight }) {
|
|
7603
7798
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7604
7799
|
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
7605
7800
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
@@ -8756,7 +8951,7 @@ return result;
|
|
|
8756
8951
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8757
8952
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
8758
8953
|
}
|
|
8759
|
-
function formatRelative(date) {
|
|
8954
|
+
function formatRelative$1(date) {
|
|
8760
8955
|
if (!date) return "—";
|
|
8761
8956
|
const d = date instanceof Date ? date : new Date(date);
|
|
8762
8957
|
if (isNaN(d.getTime())) return "—";
|
|
@@ -8958,7 +9153,7 @@ return result;
|
|
|
8958
9153
|
"from ",
|
|
8959
9154
|
branch.parentDatabase,
|
|
8960
9155
|
" · ",
|
|
8961
|
-
formatRelative(branch.createdAt)
|
|
9156
|
+
formatRelative$1(branch.createdAt)
|
|
8962
9157
|
]
|
|
8963
9158
|
})]
|
|
8964
9159
|
}),
|
|
@@ -9022,21 +9217,21 @@ return result;
|
|
|
9022
9217
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9023
9218
|
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
9024
9219
|
children: [
|
|
9025
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9026
9221
|
label: "Branch Name",
|
|
9027
9222
|
value: selected.name,
|
|
9028
9223
|
mono: true
|
|
9029
9224
|
}),
|
|
9030
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9031
9226
|
label: "Source Database",
|
|
9032
9227
|
value: selected.parentDatabase,
|
|
9033
9228
|
mono: true
|
|
9034
9229
|
}),
|
|
9035
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9036
9231
|
label: "Created",
|
|
9037
|
-
value: formatRelative(selected.createdAt)
|
|
9232
|
+
value: formatRelative$1(selected.createdAt)
|
|
9038
9233
|
}),
|
|
9039
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
9234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
|
|
9040
9235
|
label: "Size",
|
|
9041
9236
|
value: formatSize(selected.sizeBytes)
|
|
9042
9237
|
})
|
|
@@ -9138,13 +9333,12 @@ return result;
|
|
|
9138
9333
|
}) }),
|
|
9139
9334
|
branches.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
9140
9335
|
label: "Source Database",
|
|
9141
|
-
value: sourceBranch ?? "",
|
|
9142
|
-
onValueChange: (v) => setSourceBranch(v
|
|
9336
|
+
value: sourceBranch ?? "__main__",
|
|
9337
|
+
onValueChange: (v) => setSourceBranch(v === "__main__" ? void 0 : v),
|
|
9143
9338
|
placeholder: "Default (main database)",
|
|
9144
9339
|
size: "small",
|
|
9145
|
-
renderValue: (v) => v || "Default (main database)",
|
|
9146
9340
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
9147
|
-
value: "",
|
|
9341
|
+
value: "__main__",
|
|
9148
9342
|
children: "Default (main database)"
|
|
9149
9343
|
}), branches.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
9150
9344
|
value: b.name,
|
|
@@ -9185,7 +9379,7 @@ return result;
|
|
|
9185
9379
|
]
|
|
9186
9380
|
});
|
|
9187
9381
|
}
|
|
9188
|
-
function StatCard({ label, value, mono }) {
|
|
9382
|
+
function StatCard$1({ label, value, mono }) {
|
|
9189
9383
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9190
9384
|
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
9191
9385
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
@@ -10174,16 +10368,16 @@ return result;
|
|
|
10174
10368
|
var LogsExplorer_exports = /* @__PURE__ */ __exportAll({ LogsExplorer: () => LogsExplorer$1 });
|
|
10175
10369
|
function LogsExplorer$1() {
|
|
10176
10370
|
const [logs, setLogs] = (0, react.useState)([]);
|
|
10177
|
-
const [level, setLevel] = (0, react.useState)("");
|
|
10178
|
-
const [source, setSource] = (0, react.useState)("");
|
|
10371
|
+
const [level, setLevel] = (0, react.useState)("all");
|
|
10372
|
+
const [source, setSource] = (0, react.useState)("all");
|
|
10179
10373
|
const [search, setSearch] = (0, react.useState)("");
|
|
10180
10374
|
const [autoScroll, setAutoScroll] = (0, react.useState)(true);
|
|
10181
10375
|
const containerRef = (0, react.useRef)(null);
|
|
10182
10376
|
const fetchLogs = (0, react.useCallback)(async () => {
|
|
10183
10377
|
try {
|
|
10184
10378
|
const params = new URLSearchParams();
|
|
10185
|
-
if (level) params.set("level", level);
|
|
10186
|
-
if (source) params.set("source", source);
|
|
10379
|
+
if (level && level !== "all") params.set("level", level);
|
|
10380
|
+
if (source && source !== "all") params.set("source", source);
|
|
10187
10381
|
if (search) params.set("search", search);
|
|
10188
10382
|
params.set("limit", "200");
|
|
10189
10383
|
const resp = await fetch(`/api/logs?${params}`);
|
|
@@ -10220,22 +10414,9 @@ return result;
|
|
|
10220
10414
|
if (autoScroll && containerRef.current) containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
10221
10415
|
}, [logs, autoScroll]);
|
|
10222
10416
|
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
|
-
},
|
|
10417
|
+
className: "flex flex-col h-[calc(100vh-64px)] w-full bg-surface-50 dark:bg-surface-800",
|
|
10230
10418
|
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
|
-
},
|
|
10419
|
+
className: (0, _rebasepro_ui.cls)("flex gap-2 p-3 border-b items-center flex-wrap shrink-0", _rebasepro_ui.defaultBorderMixin),
|
|
10239
10420
|
children: [
|
|
10240
10421
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
10241
10422
|
value: level,
|
|
@@ -10244,7 +10425,7 @@ return result;
|
|
|
10244
10425
|
placeholder: "All Levels",
|
|
10245
10426
|
children: [
|
|
10246
10427
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
10247
|
-
value: "",
|
|
10428
|
+
value: "all",
|
|
10248
10429
|
children: "All Levels"
|
|
10249
10430
|
}),
|
|
10250
10431
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
@@ -10272,7 +10453,7 @@ return result;
|
|
|
10272
10453
|
placeholder: "All Sources",
|
|
10273
10454
|
children: [
|
|
10274
10455
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
10275
|
-
value: "",
|
|
10456
|
+
value: "all",
|
|
10276
10457
|
children: "All Sources"
|
|
10277
10458
|
}),
|
|
10278
10459
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
@@ -10305,7 +10486,7 @@ return result;
|
|
|
10305
10486
|
className: "flex-1 min-w-[200px]"
|
|
10306
10487
|
}),
|
|
10307
10488
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10308
|
-
className: "flex items-center gap-1.5 cursor-pointer",
|
|
10489
|
+
className: "flex items-center gap-1.5 cursor-pointer ml-2",
|
|
10309
10490
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
10310
10491
|
id: "auto-scroll",
|
|
10311
10492
|
checked: autoScroll,
|
|
@@ -10314,79 +10495,58 @@ return result;
|
|
|
10314
10495
|
padding: false
|
|
10315
10496
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Label, {
|
|
10316
10497
|
htmlFor: "auto-scroll",
|
|
10317
|
-
className: "text-xs select-none cursor-pointer",
|
|
10498
|
+
className: "text-xs select-none cursor-pointer text-surface-600 dark:text-surface-400",
|
|
10318
10499
|
children: "Auto-scroll"
|
|
10319
10500
|
})]
|
|
10320
10501
|
}),
|
|
10321
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10502
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10503
|
+
className: "ml-auto pl-4",
|
|
10504
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10505
|
+
variant: "caption",
|
|
10506
|
+
color: "secondary",
|
|
10507
|
+
children: [logs.length, " entries"]
|
|
10508
|
+
})
|
|
10327
10509
|
})
|
|
10328
10510
|
]
|
|
10329
10511
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10330
10512
|
ref: containerRef,
|
|
10331
|
-
|
|
10332
|
-
flex: 1,
|
|
10333
|
-
overflow: "auto",
|
|
10334
|
-
fontFamily: "monospace",
|
|
10335
|
-
fontSize: 12,
|
|
10336
|
-
padding: "8px 0"
|
|
10337
|
-
},
|
|
10513
|
+
className: "flex-1 overflow-auto py-2",
|
|
10338
10514
|
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
|
-
},
|
|
10515
|
+
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
10516
|
children: [
|
|
10346
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
},
|
|
10517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10518
|
+
variant: "body2",
|
|
10519
|
+
color: "secondary",
|
|
10520
|
+
className: "w-[72px] shrink-0 font-mono",
|
|
10351
10521
|
children: new Date(log.timestamp).toLocaleTimeString()
|
|
10352
10522
|
}),
|
|
10353
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
width: 40,
|
|
10357
|
-
flexShrink: 0,
|
|
10358
|
-
textTransform: "uppercase",
|
|
10359
|
-
fontWeight: 600
|
|
10360
|
-
},
|
|
10523
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10524
|
+
variant: "body2",
|
|
10525
|
+
className: (0, _rebasepro_ui.cls)("w-[48px] shrink-0 uppercase font-semibold font-mono", LEVEL_COLORS[log.level] || "text-surface-500"),
|
|
10361
10526
|
children: log.level
|
|
10362
10527
|
}),
|
|
10363
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
width: 64,
|
|
10367
|
-
flexShrink: 0
|
|
10368
|
-
},
|
|
10528
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10529
|
+
variant: "body2",
|
|
10530
|
+
className: (0, _rebasepro_ui.cls)("w-[80px] shrink-0 font-mono", SOURCE_COLORS[log.source] || "text-surface-500"),
|
|
10369
10531
|
children: [
|
|
10370
10532
|
"[",
|
|
10371
10533
|
log.source,
|
|
10372
10534
|
"]"
|
|
10373
10535
|
]
|
|
10374
10536
|
}),
|
|
10375
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
flex: 1
|
|
10379
|
-
},
|
|
10537
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10538
|
+
variant: "body2",
|
|
10539
|
+
className: "flex-1 font-mono break-all whitespace-pre-wrap text-surface-900 dark:text-surface-100",
|
|
10380
10540
|
children: log.message
|
|
10381
10541
|
})
|
|
10382
10542
|
]
|
|
10383
10543
|
}, log.id)), logs.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
color: "
|
|
10388
|
-
|
|
10389
|
-
|
|
10544
|
+
className: "p-8 text-center",
|
|
10545
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10546
|
+
variant: "body2",
|
|
10547
|
+
color: "secondary",
|
|
10548
|
+
children: "No log entries yet. Logs will appear here as requests come in."
|
|
10549
|
+
})
|
|
10390
10550
|
})]
|
|
10391
10551
|
})]
|
|
10392
10552
|
});
|
|
@@ -10394,95 +10554,776 @@ return result;
|
|
|
10394
10554
|
var LEVEL_COLORS, SOURCE_COLORS;
|
|
10395
10555
|
var init_LogsExplorer = __esmMin((() => {
|
|
10396
10556
|
LEVEL_COLORS = {
|
|
10397
|
-
debug: "
|
|
10398
|
-
info: "
|
|
10399
|
-
warn: "
|
|
10400
|
-
error: "
|
|
10557
|
+
debug: "text-surface-500",
|
|
10558
|
+
info: "text-blue-600 dark:text-blue-500",
|
|
10559
|
+
warn: "text-amber-600 dark:text-amber-500",
|
|
10560
|
+
error: "text-red-600 dark:text-red-500"
|
|
10401
10561
|
};
|
|
10402
10562
|
SOURCE_COLORS = {
|
|
10403
|
-
api: "
|
|
10404
|
-
auth: "
|
|
10405
|
-
storage: "
|
|
10406
|
-
realtime: "
|
|
10407
|
-
system: "
|
|
10563
|
+
api: "text-sky-600 dark:text-sky-400",
|
|
10564
|
+
auth: "text-purple-600 dark:text-purple-400",
|
|
10565
|
+
storage: "text-green-600 dark:text-green-500",
|
|
10566
|
+
realtime: "text-orange-600 dark:text-orange-400",
|
|
10567
|
+
system: "text-surface-600 dark:text-surface-400"
|
|
10408
10568
|
};
|
|
10409
10569
|
}));
|
|
10410
10570
|
//#endregion
|
|
10411
|
-
//#region src/components/
|
|
10412
|
-
var
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
function
|
|
10431
|
-
|
|
10432
|
-
const
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10571
|
+
//#region src/components/ApiKeys/ApiKeysView.tsx
|
|
10572
|
+
var ApiKeysView_exports = /* @__PURE__ */ __exportAll({ ApiKeysView: () => ApiKeysView$1 });
|
|
10573
|
+
function formatRelative(iso) {
|
|
10574
|
+
if (!iso) return "—";
|
|
10575
|
+
const d = new Date(iso);
|
|
10576
|
+
const now = Date.now();
|
|
10577
|
+
const diff = d.getTime() - now;
|
|
10578
|
+
const abs = Math.abs(diff);
|
|
10579
|
+
if (abs < 6e4) return diff > 0 ? "in <1m" : "<1m ago";
|
|
10580
|
+
if (abs < 36e5) {
|
|
10581
|
+
const m = Math.round(abs / 6e4);
|
|
10582
|
+
return diff > 0 ? `in ${m}m` : `${m}m ago`;
|
|
10583
|
+
}
|
|
10584
|
+
if (abs < 864e5) {
|
|
10585
|
+
const h = Math.round(abs / 36e5);
|
|
10586
|
+
return diff > 0 ? `in ${h}h` : `${h}h ago`;
|
|
10587
|
+
}
|
|
10588
|
+
return d.toLocaleDateString();
|
|
10589
|
+
}
|
|
10590
|
+
function permissionSummary(perms) {
|
|
10591
|
+
if (perms.length === 0) return "No permissions";
|
|
10592
|
+
const wildcard = perms.find((p) => p.collection === "*");
|
|
10593
|
+
if (wildcard) return `All collections (${wildcard.operations.join(", ")})`;
|
|
10594
|
+
if (perms.length === 1) return `${perms[0].collection} (${perms[0].operations.join(", ")})`;
|
|
10595
|
+
return `${perms.length} collections`;
|
|
10596
|
+
}
|
|
10597
|
+
function isExpired(key) {
|
|
10598
|
+
return !!(key.expires_at && new Date(key.expires_at) < /* @__PURE__ */ new Date());
|
|
10599
|
+
}
|
|
10600
|
+
function keyStatus(key) {
|
|
10601
|
+
if (key.revoked_at) return {
|
|
10602
|
+
label: "Revoked",
|
|
10603
|
+
color: "text-red-500"
|
|
10604
|
+
};
|
|
10605
|
+
if (isExpired(key)) return {
|
|
10606
|
+
label: "Expired",
|
|
10607
|
+
color: "text-amber-500"
|
|
10608
|
+
};
|
|
10609
|
+
return {
|
|
10610
|
+
label: "Active",
|
|
10611
|
+
color: "text-emerald-500"
|
|
10612
|
+
};
|
|
10613
|
+
}
|
|
10614
|
+
function ApiKeysView$1() {
|
|
10615
|
+
const client = (0, _rebasepro_core.useRebaseClient)();
|
|
10616
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
10617
|
+
const [keys, setKeys] = (0, react.useState)([]);
|
|
10618
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
10619
|
+
const [selectedId, setSelectedId] = (0, react.useState)(null);
|
|
10620
|
+
const [showCreate, setShowCreate] = (0, react.useState)(false);
|
|
10621
|
+
const [showSecret, setShowSecret] = (0, react.useState)(null);
|
|
10622
|
+
const [revoking, setRevoking] = (0, react.useState)(null);
|
|
10623
|
+
const clientRef = (0, react.useRef)(client);
|
|
10624
|
+
clientRef.current = client;
|
|
10625
|
+
const snackbarRef = (0, react.useRef)(snackbar);
|
|
10626
|
+
snackbarRef.current = snackbar;
|
|
10627
|
+
const loadKeys = (0, react.useCallback)(async () => {
|
|
10628
|
+
const c = clientRef.current;
|
|
10629
|
+
if (!c?.apiKeys) {
|
|
10630
|
+
setLoading(false);
|
|
10631
|
+
return;
|
|
10632
|
+
}
|
|
10633
|
+
try {
|
|
10634
|
+
setKeys((await c.apiKeys.listKeys()).keys);
|
|
10635
|
+
} catch (e) {
|
|
10636
|
+
snackbarRef.current.open({
|
|
10637
|
+
type: "error",
|
|
10638
|
+
message: e instanceof Error ? e.message : String(e)
|
|
10639
|
+
});
|
|
10640
|
+
} finally {
|
|
10641
|
+
setLoading(false);
|
|
10642
|
+
}
|
|
10643
|
+
}, []);
|
|
10644
|
+
(0, react.useEffect)(() => {
|
|
10645
|
+
loadKeys();
|
|
10646
|
+
}, [loadKeys]);
|
|
10647
|
+
const handleRevoke = async (id) => {
|
|
10648
|
+
const c = clientRef.current;
|
|
10649
|
+
if (!c?.apiKeys) return;
|
|
10650
|
+
setRevoking(id);
|
|
10651
|
+
try {
|
|
10652
|
+
await c.apiKeys.revokeKey(id);
|
|
10653
|
+
snackbarRef.current.open({
|
|
10654
|
+
type: "success",
|
|
10655
|
+
message: "API key revoked"
|
|
10656
|
+
});
|
|
10657
|
+
await loadKeys();
|
|
10658
|
+
if (selectedId === id) setSelectedId(null);
|
|
10659
|
+
} catch (e) {
|
|
10660
|
+
snackbarRef.current.open({
|
|
10661
|
+
type: "error",
|
|
10662
|
+
message: e instanceof Error ? e.message : String(e)
|
|
10663
|
+
});
|
|
10664
|
+
} finally {
|
|
10665
|
+
setRevoking(null);
|
|
10666
|
+
}
|
|
10667
|
+
};
|
|
10668
|
+
const handleCreated = (keyWithSecret) => {
|
|
10669
|
+
setShowCreate(false);
|
|
10670
|
+
setShowSecret(keyWithSecret);
|
|
10671
|
+
loadKeys();
|
|
10672
|
+
};
|
|
10673
|
+
const selectedKey = keys.find((k) => k.id === selectedId);
|
|
10674
|
+
const activeKeys = keys.filter((k) => !k.revoked_at && !isExpired(k));
|
|
10675
|
+
const inactiveKeys = keys.filter((k) => k.revoked_at || isExpired(k));
|
|
10676
|
+
if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10677
|
+
className: "flex items-center justify-center h-full",
|
|
10678
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, {})
|
|
10679
|
+
});
|
|
10680
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10681
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10682
|
+
className: "flex h-full w-full overflow-hidden bg-white dark:bg-surface-950",
|
|
10683
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10684
|
+
className: (0, _rebasepro_ui.cls)("flex flex-col w-[340px] min-w-[280px] border-r h-full", _rebasepro_ui.defaultBorderMixin),
|
|
10685
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10686
|
+
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),
|
|
10687
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10688
|
+
className: "flex items-center gap-2",
|
|
10689
|
+
children: [
|
|
10690
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10691
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
10692
|
+
className: "text-primary"
|
|
10693
|
+
}),
|
|
10694
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10695
|
+
variant: "subtitle2",
|
|
10696
|
+
className: "font-semibold",
|
|
10697
|
+
children: "API Keys"
|
|
10698
|
+
}),
|
|
10699
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10700
|
+
size: "smallest",
|
|
10701
|
+
className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
|
|
10702
|
+
children: activeKeys.length
|
|
10703
|
+
})
|
|
10704
|
+
]
|
|
10705
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10706
|
+
className: "flex items-center gap-1",
|
|
10707
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
10708
|
+
size: "small",
|
|
10709
|
+
onClick: loadKeys,
|
|
10710
|
+
title: "Refresh",
|
|
10711
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.RefreshCwIcon, { size: _rebasepro_ui.iconSize.smallest })
|
|
10712
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
10713
|
+
size: "small",
|
|
10714
|
+
color: "primary",
|
|
10715
|
+
onClick: () => setShowCreate(true),
|
|
10716
|
+
startIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.PlusIcon, { size: _rebasepro_ui.iconSize.smallest }),
|
|
10717
|
+
children: "New"
|
|
10718
|
+
})]
|
|
10719
|
+
})]
|
|
10720
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10721
|
+
className: "flex-1 overflow-y-auto p-2 space-y-1",
|
|
10722
|
+
children: [
|
|
10723
|
+
activeKeys.length === 0 && inactiveKeys.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10724
|
+
className: "flex flex-col items-center justify-center h-full gap-3 text-center p-6",
|
|
10725
|
+
children: [
|
|
10726
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10727
|
+
size: _rebasepro_ui.iconSize.medium,
|
|
10728
|
+
className: "text-surface-300 dark:text-surface-600"
|
|
10729
|
+
}),
|
|
10730
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10731
|
+
variant: "body2",
|
|
10732
|
+
color: "secondary",
|
|
10733
|
+
children: "No API keys yet"
|
|
10734
|
+
}),
|
|
10735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10736
|
+
variant: "caption",
|
|
10737
|
+
color: "disabled",
|
|
10738
|
+
children: "Create a key to enable machine-to-machine authentication"
|
|
10739
|
+
})
|
|
10740
|
+
]
|
|
10741
|
+
}),
|
|
10742
|
+
activeKeys.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyListItem, {
|
|
10743
|
+
apiKey: key,
|
|
10744
|
+
selected: selectedId === key.id,
|
|
10745
|
+
onClick: () => setSelectedId(key.id)
|
|
10746
|
+
}, key.id)),
|
|
10747
|
+
inactiveKeys.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10748
|
+
className: "px-2 pt-3 pb-1",
|
|
10749
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10750
|
+
variant: "caption",
|
|
10751
|
+
color: "disabled",
|
|
10752
|
+
className: "text-[10px] uppercase tracking-wider font-medium",
|
|
10753
|
+
children: "Revoked / Expired"
|
|
10754
|
+
})
|
|
10755
|
+
}), inactiveKeys.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyListItem, {
|
|
10756
|
+
apiKey: key,
|
|
10757
|
+
selected: selectedId === key.id,
|
|
10758
|
+
onClick: () => setSelectedId(key.id)
|
|
10759
|
+
}, key.id))] })
|
|
10760
|
+
]
|
|
10761
|
+
})]
|
|
10762
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10763
|
+
className: "flex-1 flex flex-col min-w-0 h-full overflow-hidden",
|
|
10764
|
+
children: !selectedKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10765
|
+
className: "flex items-center justify-center h-full",
|
|
10766
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10767
|
+
variant: "body2",
|
|
10768
|
+
color: "disabled",
|
|
10769
|
+
children: "Select an API key to view details"
|
|
10770
|
+
})
|
|
10771
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10773
|
+
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),
|
|
10774
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10775
|
+
className: "flex items-center gap-3 min-w-0",
|
|
10776
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.KeyRoundIcon, {
|
|
10777
|
+
size: _rebasepro_ui.iconSize.small,
|
|
10778
|
+
className: "text-primary shrink-0"
|
|
10779
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10780
|
+
className: "min-w-0",
|
|
10781
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10782
|
+
variant: "subtitle1",
|
|
10783
|
+
className: "font-semibold truncate",
|
|
10784
|
+
children: selectedKey.name
|
|
10785
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10786
|
+
variant: "caption",
|
|
10787
|
+
color: "secondary",
|
|
10788
|
+
className: "font-mono text-[11px]",
|
|
10789
|
+
children: [selectedKey.key_prefix, "•••"]
|
|
10790
|
+
})]
|
|
10791
|
+
})]
|
|
10792
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10793
|
+
className: "flex items-center gap-2 shrink-0",
|
|
10794
|
+
children: !selectedKey.revoked_at && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
10795
|
+
size: "small",
|
|
10796
|
+
color: "error",
|
|
10797
|
+
variant: "outlined",
|
|
10798
|
+
onClick: () => handleRevoke(selectedKey.id),
|
|
10799
|
+
disabled: revoking === selectedKey.id,
|
|
10800
|
+
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 }),
|
|
10801
|
+
children: "Revoke"
|
|
10802
|
+
})
|
|
10803
|
+
})]
|
|
10804
|
+
}),
|
|
10805
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10806
|
+
className: "px-5 py-4 bg-surface-50 dark:bg-surface-900/50",
|
|
10807
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10808
|
+
className: "grid grid-cols-2 md:grid-cols-4 gap-3",
|
|
10809
|
+
children: [
|
|
10810
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10811
|
+
label: "Status",
|
|
10812
|
+
value: keyStatus(selectedKey).label,
|
|
10813
|
+
className: keyStatus(selectedKey).color
|
|
10814
|
+
}),
|
|
10815
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10816
|
+
label: "Created",
|
|
10817
|
+
value: formatRelative(selectedKey.created_at)
|
|
10818
|
+
}),
|
|
10819
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10820
|
+
label: "Last Used",
|
|
10821
|
+
value: formatRelative(selectedKey.last_used_at)
|
|
10822
|
+
}),
|
|
10823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10824
|
+
label: "Expires",
|
|
10825
|
+
value: selectedKey.expires_at ? formatRelative(selectedKey.expires_at) : "Never"
|
|
10826
|
+
})
|
|
10827
|
+
]
|
|
10828
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10829
|
+
className: "grid grid-cols-2 gap-3 mt-3",
|
|
10830
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10831
|
+
label: "Rate Limit",
|
|
10832
|
+
value: selectedKey.rate_limit ? `${selectedKey.rate_limit}/15min` : "Default (1000/15min)"
|
|
10833
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
|
|
10834
|
+
label: "Created By",
|
|
10835
|
+
value: selectedKey.created_by,
|
|
10836
|
+
mono: true
|
|
10837
|
+
})]
|
|
10838
|
+
})]
|
|
10839
|
+
}),
|
|
10840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10841
|
+
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),
|
|
10842
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10843
|
+
variant: "subtitle2",
|
|
10844
|
+
className: "font-semibold text-[13px]",
|
|
10845
|
+
children: "Permissions"
|
|
10846
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10847
|
+
size: "smallest",
|
|
10848
|
+
className: "bg-surface-200 dark:bg-surface-700 text-surface-600 dark:text-surface-300",
|
|
10849
|
+
children: selectedKey.permissions.length
|
|
10850
|
+
})]
|
|
10851
|
+
}),
|
|
10852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10853
|
+
className: "flex-1 overflow-y-auto px-5 py-3",
|
|
10854
|
+
children: selectedKey.permissions.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10855
|
+
variant: "body2",
|
|
10856
|
+
color: "disabled",
|
|
10857
|
+
children: "No permissions configured"
|
|
10858
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10859
|
+
className: "space-y-2",
|
|
10860
|
+
children: selectedKey.permissions.map((perm, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10861
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-3 px-3 py-2 rounded-lg border", _rebasepro_ui.defaultBorderMixin),
|
|
10862
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10863
|
+
variant: "body2",
|
|
10864
|
+
className: "font-mono text-[13px] font-medium flex-1",
|
|
10865
|
+
children: perm.collection === "*" ? "* (all collections)" : perm.collection
|
|
10866
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10867
|
+
className: "flex items-center gap-1",
|
|
10868
|
+
children: perm.operations.map((op) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Chip, {
|
|
10869
|
+
size: "smallest",
|
|
10870
|
+
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"),
|
|
10871
|
+
children: op
|
|
10872
|
+
}, op))
|
|
10873
|
+
})]
|
|
10874
|
+
}, idx))
|
|
10875
|
+
})
|
|
10876
|
+
})
|
|
10877
|
+
] })
|
|
10878
|
+
})]
|
|
10879
|
+
}),
|
|
10880
|
+
showCreate && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreateApiKeyDialog, {
|
|
10881
|
+
onClose: () => setShowCreate(false),
|
|
10882
|
+
onCreated: handleCreated
|
|
10883
|
+
}),
|
|
10884
|
+
showSecret && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SecretDisplayDialog, {
|
|
10885
|
+
keyWithSecret: showSecret,
|
|
10886
|
+
onClose: () => setShowSecret(null)
|
|
10887
|
+
})
|
|
10888
|
+
] });
|
|
10889
|
+
}
|
|
10890
|
+
function KeyListItem({ apiKey, selected, onClick }) {
|
|
10891
|
+
const status = keyStatus(apiKey);
|
|
10892
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10893
|
+
onClick,
|
|
10894
|
+
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"),
|
|
10895
|
+
children: [
|
|
10896
|
+
/* @__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") }),
|
|
10897
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10898
|
+
className: "flex-1 min-w-0",
|
|
10899
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10900
|
+
variant: "body2",
|
|
10901
|
+
className: "truncate font-medium text-[13px]",
|
|
10902
|
+
children: apiKey.name
|
|
10903
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
10904
|
+
variant: "caption",
|
|
10905
|
+
color: "secondary",
|
|
10906
|
+
className: "truncate text-[11px] font-mono",
|
|
10907
|
+
children: [apiKey.key_prefix, "•••"]
|
|
10908
|
+
})]
|
|
10909
|
+
}),
|
|
10910
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10911
|
+
className: "shrink-0",
|
|
10912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10913
|
+
variant: "caption",
|
|
10914
|
+
color: "disabled",
|
|
10915
|
+
className: "text-[10px]",
|
|
10916
|
+
children: permissionSummary(apiKey.permissions)
|
|
10917
|
+
})
|
|
10918
|
+
})
|
|
10919
|
+
]
|
|
10920
|
+
});
|
|
10921
|
+
}
|
|
10922
|
+
function StatCard({ label, value, mono, className }) {
|
|
10923
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10924
|
+
className: (0, _rebasepro_ui.cls)("px-3 py-2 rounded-lg border bg-white dark:bg-surface-900", _rebasepro_ui.defaultBorderMixin),
|
|
10925
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10926
|
+
variant: "caption",
|
|
10927
|
+
color: "secondary",
|
|
10928
|
+
className: "text-[10px] uppercase tracking-wider font-medium",
|
|
10929
|
+
children: label
|
|
10930
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
10931
|
+
variant: "body2",
|
|
10932
|
+
className: (0, _rebasepro_ui.cls)("mt-0.5 font-semibold text-[13px]", mono && "font-mono", className),
|
|
10933
|
+
children: value
|
|
10934
|
+
})]
|
|
10935
|
+
});
|
|
10936
|
+
}
|
|
10937
|
+
function CreateApiKeyDialog({ onClose, onCreated }) {
|
|
10938
|
+
const client = (0, _rebasepro_core.useRebaseClient)();
|
|
10939
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
10940
|
+
const [name, setName] = (0, react.useState)("");
|
|
10941
|
+
const [permissions, setPermissions] = (0, react.useState)([{
|
|
10942
|
+
collection: "*",
|
|
10943
|
+
read: true,
|
|
10944
|
+
write: false,
|
|
10945
|
+
delete: false
|
|
10946
|
+
}]);
|
|
10947
|
+
const [rateLimit, setRateLimit] = (0, react.useState)("");
|
|
10948
|
+
const [expiresIn, setExpiresIn] = (0, react.useState)("never");
|
|
10949
|
+
const [creating, setCreating] = (0, react.useState)(false);
|
|
10950
|
+
const addRow = () => setPermissions([...permissions, {
|
|
10951
|
+
collection: "",
|
|
10952
|
+
read: true,
|
|
10953
|
+
write: false,
|
|
10954
|
+
delete: false
|
|
10955
|
+
}]);
|
|
10956
|
+
const removeRow = (idx) => setPermissions(permissions.filter((_, i) => i !== idx));
|
|
10957
|
+
const updateRow = (idx, field, value) => {
|
|
10958
|
+
setPermissions(permissions.map((row, i) => i === idx ? {
|
|
10959
|
+
...row,
|
|
10960
|
+
[field]: value
|
|
10961
|
+
} : row));
|
|
10962
|
+
};
|
|
10963
|
+
const handleCreate = async () => {
|
|
10964
|
+
if (!client?.apiKeys || !name.trim()) return;
|
|
10965
|
+
const apiPerms = permissions.filter((r) => r.collection.trim()).map((r) => ({
|
|
10966
|
+
collection: r.collection.trim(),
|
|
10967
|
+
operations: [
|
|
10968
|
+
...r.read ? ["read"] : [],
|
|
10969
|
+
...r.write ? ["write"] : [],
|
|
10970
|
+
...r.delete ? ["delete"] : []
|
|
10971
|
+
]
|
|
10972
|
+
})).filter((p) => p.operations.length > 0);
|
|
10973
|
+
if (apiPerms.length === 0) {
|
|
10974
|
+
snackbar.open({
|
|
10975
|
+
type: "error",
|
|
10976
|
+
message: "At least one permission is required"
|
|
10977
|
+
});
|
|
10978
|
+
return;
|
|
10979
|
+
}
|
|
10980
|
+
let expires_at = null;
|
|
10981
|
+
if (expiresIn === "7d") expires_at = new Date(Date.now() + 7 * 864e5).toISOString();
|
|
10982
|
+
else if (expiresIn === "30d") expires_at = new Date(Date.now() + 30 * 864e5).toISOString();
|
|
10983
|
+
else if (expiresIn === "90d") expires_at = new Date(Date.now() + 90 * 864e5).toISOString();
|
|
10984
|
+
else if (expiresIn === "1y") expires_at = new Date(Date.now() + 365 * 864e5).toISOString();
|
|
10985
|
+
setCreating(true);
|
|
10986
|
+
try {
|
|
10987
|
+
onCreated((await client.apiKeys.createKey({
|
|
10988
|
+
name: name.trim(),
|
|
10989
|
+
permissions: apiPerms,
|
|
10990
|
+
rate_limit: rateLimit ? parseInt(rateLimit, 10) : null,
|
|
10991
|
+
expires_at
|
|
10992
|
+
})).key);
|
|
10993
|
+
} catch (e) {
|
|
10994
|
+
snackbar.open({
|
|
10995
|
+
type: "error",
|
|
10996
|
+
message: e instanceof Error ? e.message : String(e)
|
|
10997
|
+
});
|
|
10998
|
+
} finally {
|
|
10999
|
+
setCreating(false);
|
|
11000
|
+
}
|
|
11001
|
+
};
|
|
11002
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Dialog, {
|
|
11003
|
+
open: true,
|
|
11004
|
+
onOpenChange: (open) => {
|
|
11005
|
+
if (!open) onClose();
|
|
11006
|
+
},
|
|
11007
|
+
maxWidth: "lg",
|
|
11008
|
+
children: [
|
|
11009
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogTitle, { children: "Create API Key" }),
|
|
11010
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogContent, {
|
|
11011
|
+
className: "space-y-4",
|
|
11012
|
+
children: [
|
|
11013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11014
|
+
label: "Name",
|
|
11015
|
+
value: name,
|
|
11016
|
+
onChange: (e) => setName(e.target.value),
|
|
11017
|
+
placeholder: "e.g. Analytics Pipeline",
|
|
11018
|
+
size: "small",
|
|
11019
|
+
autoFocus: true
|
|
11020
|
+
}),
|
|
11021
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
11022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11023
|
+
variant: "caption",
|
|
11024
|
+
color: "secondary",
|
|
11025
|
+
className: "text-[11px] uppercase tracking-wider font-medium mb-2 block",
|
|
11026
|
+
children: "Permissions"
|
|
11027
|
+
}),
|
|
11028
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11029
|
+
className: "space-y-2",
|
|
11030
|
+
children: permissions.map((row, idx) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11031
|
+
className: (0, _rebasepro_ui.cls)("flex items-center gap-2 p-2 rounded-lg border", _rebasepro_ui.defaultBorderMixin),
|
|
11032
|
+
children: [
|
|
11033
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11034
|
+
size: "small",
|
|
11035
|
+
value: row.collection,
|
|
11036
|
+
onChange: (e) => updateRow(idx, "collection", e.target.value),
|
|
11037
|
+
placeholder: "Collection slug or *",
|
|
11038
|
+
className: "flex-1"
|
|
11039
|
+
}),
|
|
11040
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11041
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11042
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11043
|
+
size: "small",
|
|
11044
|
+
checked: row.read,
|
|
11045
|
+
onCheckedChange: (v) => updateRow(idx, "read", !!v)
|
|
11046
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11047
|
+
className: "text-emerald-600 dark:text-emerald-400",
|
|
11048
|
+
children: "read"
|
|
11049
|
+
})]
|
|
11050
|
+
}),
|
|
11051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11052
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11053
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11054
|
+
size: "small",
|
|
11055
|
+
checked: row.write,
|
|
11056
|
+
onCheckedChange: (v) => updateRow(idx, "write", !!v)
|
|
11057
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11058
|
+
className: "text-blue-600 dark:text-blue-400",
|
|
11059
|
+
children: "write"
|
|
11060
|
+
})]
|
|
11061
|
+
}),
|
|
11062
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
11063
|
+
className: "flex items-center gap-1 text-xs cursor-pointer",
|
|
11064
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Checkbox, {
|
|
11065
|
+
size: "small",
|
|
11066
|
+
checked: row.delete,
|
|
11067
|
+
onCheckedChange: (v) => updateRow(idx, "delete", !!v)
|
|
11068
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11069
|
+
className: "text-red-600 dark:text-red-400",
|
|
11070
|
+
children: "delete"
|
|
11071
|
+
})]
|
|
11072
|
+
}),
|
|
11073
|
+
permissions.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
11074
|
+
size: "small",
|
|
11075
|
+
onClick: () => removeRow(idx),
|
|
11076
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Trash2Icon, { size: _rebasepro_ui.iconSize.smallest })
|
|
11077
|
+
})
|
|
11078
|
+
]
|
|
11079
|
+
}, idx))
|
|
11080
|
+
}),
|
|
11081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11082
|
+
size: "small",
|
|
11083
|
+
variant: "text",
|
|
11084
|
+
onClick: addRow,
|
|
11085
|
+
className: "mt-1",
|
|
11086
|
+
startIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.PlusIcon, { size: _rebasepro_ui.iconSize.smallest }),
|
|
11087
|
+
children: "Add collection"
|
|
11088
|
+
})
|
|
11089
|
+
] }),
|
|
11090
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11091
|
+
className: "flex gap-4",
|
|
11092
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11093
|
+
className: "flex-1",
|
|
11094
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Select, {
|
|
11095
|
+
label: "Expires",
|
|
11096
|
+
value: expiresIn,
|
|
11097
|
+
onValueChange: setExpiresIn,
|
|
11098
|
+
size: "small",
|
|
11099
|
+
renderValue: (v) => v === "never" ? "Never" : v === "7d" ? "7 days" : v === "30d" ? "30 days" : v === "90d" ? "90 days" : "1 year",
|
|
11100
|
+
children: [
|
|
11101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11102
|
+
value: "never",
|
|
11103
|
+
children: "Never"
|
|
11104
|
+
}),
|
|
11105
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11106
|
+
value: "7d",
|
|
11107
|
+
children: "7 days"
|
|
11108
|
+
}),
|
|
11109
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11110
|
+
value: "30d",
|
|
11111
|
+
children: "30 days"
|
|
11112
|
+
}),
|
|
11113
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11114
|
+
value: "90d",
|
|
11115
|
+
children: "90 days"
|
|
11116
|
+
}),
|
|
11117
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.SelectItem, {
|
|
11118
|
+
value: "1y",
|
|
11119
|
+
children: "1 year"
|
|
11120
|
+
})
|
|
11121
|
+
]
|
|
11122
|
+
})
|
|
11123
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11124
|
+
className: "flex-1",
|
|
11125
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.TextField, {
|
|
11126
|
+
label: "Rate Limit (per 15 min)",
|
|
11127
|
+
value: rateLimit,
|
|
11128
|
+
onChange: (e) => setRateLimit(e.target.value.replace(/\D/g, "")),
|
|
11129
|
+
placeholder: "Default: 1000",
|
|
11130
|
+
size: "small"
|
|
11131
|
+
})
|
|
11132
|
+
})]
|
|
11133
|
+
})
|
|
11134
|
+
]
|
|
11135
|
+
}),
|
|
11136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogActions, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11137
|
+
variant: "text",
|
|
11138
|
+
onClick: onClose,
|
|
11139
|
+
children: "Cancel"
|
|
11140
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11141
|
+
color: "primary",
|
|
11142
|
+
onClick: handleCreate,
|
|
11143
|
+
disabled: creating || !name.trim(),
|
|
11144
|
+
startIcon: creating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgress, { size: "smallest" }) : void 0,
|
|
11145
|
+
children: "Create Key"
|
|
11146
|
+
})] })
|
|
11147
|
+
]
|
|
11148
|
+
});
|
|
11149
|
+
}
|
|
11150
|
+
function SecretDisplayDialog({ keyWithSecret, onClose }) {
|
|
11151
|
+
const snackbar = (0, _rebasepro_core.useSnackbarController)();
|
|
11152
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
11153
|
+
const handleCopy = async () => {
|
|
11154
|
+
try {
|
|
11155
|
+
await navigator.clipboard.writeText(keyWithSecret.key);
|
|
11156
|
+
setCopied(true);
|
|
11157
|
+
snackbar.open({
|
|
11158
|
+
type: "success",
|
|
11159
|
+
message: "API key copied to clipboard"
|
|
11160
|
+
});
|
|
11161
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
11162
|
+
} catch {
|
|
11163
|
+
snackbar.open({
|
|
11164
|
+
type: "error",
|
|
11165
|
+
message: "Failed to copy"
|
|
11166
|
+
});
|
|
11167
|
+
}
|
|
11168
|
+
};
|
|
11169
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Dialog, {
|
|
11170
|
+
open: true,
|
|
11171
|
+
onOpenChange: (open) => {
|
|
11172
|
+
if (!open) onClose();
|
|
11173
|
+
},
|
|
11174
|
+
maxWidth: "md",
|
|
11175
|
+
children: [
|
|
11176
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogTitle, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11177
|
+
className: "flex items-center gap-2",
|
|
11178
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CheckCircleIcon, {
|
|
11179
|
+
size: _rebasepro_ui.iconSize.small,
|
|
11180
|
+
className: "text-emerald-500"
|
|
11181
|
+
}), "API Key Created"]
|
|
11182
|
+
}) }),
|
|
11183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.DialogContent, { children: [
|
|
11184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11185
|
+
className: "p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800/50 mb-4",
|
|
11186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11187
|
+
className: "flex items-center gap-2 mb-1",
|
|
11188
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.AlertCircleIcon, {
|
|
11189
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
11190
|
+
className: "text-amber-600 dark:text-amber-400"
|
|
11191
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11192
|
+
variant: "caption",
|
|
11193
|
+
className: "font-semibold text-amber-700 dark:text-amber-400",
|
|
11194
|
+
children: "Copy your key now — it won't be shown again"
|
|
11195
|
+
})]
|
|
11196
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
|
|
11197
|
+
variant: "caption",
|
|
11198
|
+
className: "text-amber-600 dark:text-amber-300",
|
|
11199
|
+
children: "This is the only time the full API key will be displayed. Store it securely."
|
|
11200
|
+
})]
|
|
11201
|
+
}),
|
|
11202
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11203
|
+
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),
|
|
11204
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
11205
|
+
className: "flex-1 text-[12px] font-mono break-all text-surface-700 dark:text-surface-300 select-all",
|
|
11206
|
+
children: keyWithSecret.key
|
|
11207
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
|
|
11208
|
+
title: copied ? "Copied!" : "Copy",
|
|
11209
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.IconButton, {
|
|
11210
|
+
size: "small",
|
|
11211
|
+
onClick: handleCopy,
|
|
11212
|
+
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CheckCircleIcon, {
|
|
11213
|
+
size: _rebasepro_ui.iconSize.smallest,
|
|
11214
|
+
className: "text-emerald-500"
|
|
11215
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CopyIcon, { size: _rebasepro_ui.iconSize.smallest })
|
|
11216
|
+
})
|
|
11217
|
+
})]
|
|
11218
|
+
}),
|
|
11219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11220
|
+
className: "mt-4 space-y-1",
|
|
11221
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
11222
|
+
variant: "caption",
|
|
11223
|
+
color: "secondary",
|
|
11224
|
+
children: [
|
|
11225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Name:" }),
|
|
11226
|
+
" ",
|
|
11227
|
+
keyWithSecret.name
|
|
11228
|
+
]
|
|
11229
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Typography, {
|
|
11230
|
+
variant: "caption",
|
|
11231
|
+
color: "secondary",
|
|
11232
|
+
children: [
|
|
11233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Permissions:" }),
|
|
11234
|
+
" ",
|
|
11235
|
+
permissionSummary(keyWithSecret.permissions)
|
|
11236
|
+
]
|
|
11237
|
+
})]
|
|
11238
|
+
})
|
|
11239
|
+
] }),
|
|
11240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.DialogActions, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
|
|
11241
|
+
color: "primary",
|
|
11242
|
+
onClick: onClose,
|
|
11243
|
+
children: "Done"
|
|
11244
|
+
}) })
|
|
11245
|
+
]
|
|
11246
|
+
});
|
|
11247
|
+
}
|
|
11248
|
+
var init_ApiKeysView = __esmMin((() => {}));
|
|
11249
|
+
//#endregion
|
|
11250
|
+
//#region src/components/RebaseStudio.tsx
|
|
11251
|
+
var SQLEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_SQLEditor(), SQLEditor_exports)).then((m) => ({ default: m.SQLEditor })));
|
|
11252
|
+
var JSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_JSEditor(), JSEditor_exports)).then((m) => ({ default: m.JSEditor })));
|
|
11253
|
+
var RLSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_RLSEditor(), RLSEditor_exports)).then((m) => ({ default: m.RLSEditor })));
|
|
11254
|
+
var StorageView = (0, react.lazy)(() => Promise.resolve().then(() => (init_StorageView(), StorageView_exports)).then((m) => ({ default: m.StorageView })));
|
|
11255
|
+
var CronJobsView = (0, react.lazy)(() => Promise.resolve().then(() => (init_CronJobsView(), CronJobsView_exports)).then((m) => ({ default: m.CronJobsView })));
|
|
11256
|
+
var SchemaVisualizer = (0, react.lazy)(() => Promise.resolve().then(() => (init_SchemaVisualizer(), SchemaVisualizer_exports)).then((m) => ({ default: m.SchemaVisualizer })));
|
|
11257
|
+
var BranchesView = (0, react.lazy)(() => Promise.resolve().then(() => (init_BranchesView(), BranchesView_exports)).then((m) => ({ default: m.BranchesView })));
|
|
11258
|
+
var ApiExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_ApiExplorer(), ApiExplorer_exports)).then((m) => ({ default: m.ApiExplorer })));
|
|
11259
|
+
var LogsExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_LogsExplorer(), LogsExplorer_exports)).then((m) => ({ default: m.LogsExplorer })));
|
|
11260
|
+
var ApiKeysView = (0, react.lazy)(() => Promise.resolve().then(() => (init_ApiKeysView(), ApiKeysView_exports)).then((m) => ({ default: m.ApiKeysView })));
|
|
11261
|
+
/**
|
|
11262
|
+
* Declarative component to configure the Studio in Rebase.
|
|
11263
|
+
* Renders nothing — purely registers config into the RebaseRegistry.
|
|
11264
|
+
*
|
|
11265
|
+
* The "schema" tool (collection editor view) is now a built-in CMS feature.
|
|
11266
|
+
* When `<RebaseCMS collectionEditor={...}>` is used, the schema view is
|
|
11267
|
+
* automatically injected into Studio — no manual wiring needed.
|
|
11268
|
+
*/
|
|
11269
|
+
var DEFAULT_HOME_PAGE = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StudioHomePage, {});
|
|
11270
|
+
function RebaseStudio({ tools, homePage }) {
|
|
11271
|
+
const dispatch = (0, _rebasepro_core.useRebaseRegistryDispatch)();
|
|
11272
|
+
const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;
|
|
11273
|
+
const devViews = (0, react.useMemo)(() => {
|
|
11274
|
+
const views = [];
|
|
11275
|
+
const activeTools = tools ?? [
|
|
11276
|
+
"sql",
|
|
11277
|
+
"js",
|
|
11278
|
+
"rls",
|
|
11279
|
+
"storage",
|
|
11280
|
+
"cron",
|
|
11281
|
+
"schema-visualizer",
|
|
11282
|
+
"branches",
|
|
11283
|
+
"api",
|
|
11284
|
+
"logs",
|
|
11285
|
+
"api-keys"
|
|
11286
|
+
];
|
|
11287
|
+
const suspense = (el) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, {
|
|
11288
|
+
fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgressCenter, {}),
|
|
11289
|
+
children: el
|
|
11290
|
+
});
|
|
11291
|
+
if (activeTools.includes("sql")) views.push({
|
|
11292
|
+
slug: "sql",
|
|
11293
|
+
name: "SQL Console",
|
|
11294
|
+
group: "Database",
|
|
11295
|
+
icon: "terminal",
|
|
11296
|
+
description: "Execute SQL queries",
|
|
11297
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SQLEditor, {}))
|
|
11298
|
+
});
|
|
11299
|
+
if (activeTools.includes("js")) views.push({
|
|
11300
|
+
slug: "js",
|
|
11301
|
+
name: "JS Console",
|
|
11302
|
+
group: "Compute",
|
|
11303
|
+
icon: "code",
|
|
11304
|
+
description: "Execute JavaScript",
|
|
11305
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JSEditor, {}))
|
|
11306
|
+
});
|
|
11307
|
+
if (activeTools.includes("rls")) views.push({
|
|
11308
|
+
slug: "rls",
|
|
11309
|
+
name: "RLS Policies",
|
|
11310
|
+
group: "Database",
|
|
11311
|
+
icon: "ShieldCheck",
|
|
11312
|
+
description: "Row Level Security",
|
|
11313
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RLSEditor, {}))
|
|
11314
|
+
});
|
|
11315
|
+
if (activeTools.includes("storage")) views.push({
|
|
11316
|
+
slug: "storage",
|
|
11317
|
+
name: "Storage",
|
|
11318
|
+
group: "Storage",
|
|
11319
|
+
icon: "HardDrive",
|
|
11320
|
+
description: "Manage storage files",
|
|
11321
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StorageView, {}))
|
|
11322
|
+
});
|
|
11323
|
+
if (activeTools.includes("cron")) views.push({
|
|
11324
|
+
slug: "cron",
|
|
11325
|
+
name: "Cron Jobs",
|
|
11326
|
+
group: "Compute",
|
|
10486
11327
|
icon: "Clock",
|
|
10487
11328
|
description: "Manage scheduled tasks",
|
|
10488
11329
|
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CronJobsView, {}))
|
|
@@ -10519,6 +11360,14 @@ return result;
|
|
|
10519
11360
|
description: "Real-time system and query logs",
|
|
10520
11361
|
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogsExplorer, {}))
|
|
10521
11362
|
});
|
|
11363
|
+
if (activeTools.includes("api-keys")) views.push({
|
|
11364
|
+
slug: "api-keys",
|
|
11365
|
+
name: "API Keys",
|
|
11366
|
+
group: "Access Control",
|
|
11367
|
+
icon: "KeyRound",
|
|
11368
|
+
description: "Create and manage scoped API keys",
|
|
11369
|
+
view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ApiKeysView, {}))
|
|
11370
|
+
});
|
|
10522
11371
|
return views;
|
|
10523
11372
|
}, [tools]);
|
|
10524
11373
|
const homePageRef = react.default.useRef(resolvedHomePage);
|