@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.
Files changed (30) hide show
  1. package/dist/ApiKeysView-D-_FSlNL.js +684 -0
  2. package/dist/ApiKeysView-D-_FSlNL.js.map +1 -0
  3. package/dist/{BranchesView-DncIRcZt.js → BranchesView-Dlg78EQ8.js} +4 -5
  4. package/dist/{BranchesView-DncIRcZt.js.map → BranchesView-Dlg78EQ8.js.map} +1 -1
  5. package/dist/{JSEditor-BhAbEjCP.js → JSEditor-DSucz6wV.js} +4 -4
  6. package/dist/{JSEditor-BhAbEjCP.js.map → JSEditor-DSucz6wV.js.map} +1 -1
  7. package/dist/{LogsExplorer-CqtKILj8.js → LogsExplorer-J4xfsuv3.js} +49 -83
  8. package/dist/LogsExplorer-J4xfsuv3.js.map +1 -0
  9. package/dist/{RLSEditor-DpF1u9EC.js → RLSEditor-BM64laoW.js} +294 -107
  10. package/dist/RLSEditor-BM64laoW.js.map +1 -0
  11. package/dist/{SQLEditor-BLuq_zDM.js → SQLEditor-CuAhR-zr.js} +4 -4
  12. package/dist/{SQLEditor-BLuq_zDM.js.map → SQLEditor-CuAhR-zr.js.map} +1 -1
  13. package/dist/{SchemaVisualizer-BJK2u3C0.js → SchemaVisualizer-OibKoD3g.js} +2 -2
  14. package/dist/{SchemaVisualizer-BJK2u3C0.js.map → SchemaVisualizer-OibKoD3g.js.map} +1 -1
  15. package/dist/components/ApiKeys/ApiKeysView.d.ts +2 -0
  16. package/dist/index.es.js +37 -18
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +1154 -305
  19. package/dist/index.umd.js.map +1 -1
  20. package/package.json +17 -17
  21. package/src/components/ApiKeys/ApiKeysView.tsx +580 -0
  22. package/src/components/Branches/BranchesView.tsx +3 -4
  23. package/src/components/JSEditor/JSEditorSidebar.tsx +3 -3
  24. package/src/components/LogsExplorer/LogsExplorer.tsx +46 -84
  25. package/src/components/RLSEditor/RLSEditor.tsx +354 -117
  26. package/src/components/RebaseStudio.tsx +10 -1
  27. package/src/components/SQLEditor/SchemaBrowser.tsx +3 -3
  28. package/src/components/StudioHomePage.tsx +5 -1
  29. package/dist/LogsExplorer-CqtKILj8.js.map +0 -1
  30. 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("@rebasepro/common"), require("dagre")) : typeof define === "function" && define.amd ? define([
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.react_dropzone, global._xyflow_react, global._xyflow_react_dist_style_css, global._rebasepro_common, 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, react_dropzone, _xyflow_react, _xyflow_react_dist_style_css, _rebasepro_common, 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._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
- path: "/users",
148
- name: "Users",
149
- description: "Manage developers and assign roles in your workspace",
150
- icon: "group"
151
- }, {
152
- path: "/roles",
153
- name: "Roles",
154
- description: "Create and configure fine-grained access permissions",
155
- icon: "ShieldEllipsis"
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-950 rounded transition-colors",
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-950 rounded transition-colors group relative",
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-950 rounded-r relative min-h-[28px]",
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-950 rounded transition-colors group relative",
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-950 rounded-r cursor-pointer transition-colors group/prop relative min-h-[28px]",
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-950 cursor-pointer group transition-colors flex items-start",
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 [expandedSchemas, setExpandedSchemas] = (0, react.useState)({ public: true });
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
- if (sortedTables.length > 0 && !selectedTable) setSelectedTable(`${sortedTables[0].schemaName}.${sortedTables[0].tableName}`);
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
- const groupedTables = (0, react.useMemo)(() => {
5156
- const groups = {};
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
- if (!groups[table.schemaName]) groups[table.schemaName] = [];
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
- }, [tables]);
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
- }) : Object.keys(groupedTables).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
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
- }) : Object.entries(groupedTables).map(([schemaName, schemaTables]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5272
- className: "mb-2",
5273
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5274
- className: "flex items-center p-1 cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-950 rounded transition-colors",
5275
- onClick: () => setExpandedSchemas((prev) => ({
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
- [schemaName]: !prev[schemaName]
5404
+ collection: !(prev.collection ?? true)
5278
5405
  })),
5279
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
5280
- className: (0, _rebasepro_ui.cls)("w-3 h-3 mr-1 transition-transform", expandedSchemas[schemaName] ? "rotate-90" : ""),
5281
- fill: "currentColor",
5282
- viewBox: "0 0 20 20",
5283
- 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" })
5284
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
5285
- variant: "body2",
5286
- className: "text-text-primary dark:text-text-primary-dark font-medium text-xs truncate flex-grow",
5287
- children: schemaName
5288
- })]
5289
- }), expandedSchemas[schemaName] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5290
- className: "ml-3 mt-1 space-y-0.5",
5291
- children: schemaTables.map((table) => {
5292
- const key = `${table.schemaName}.${table.tableName}`;
5293
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5294
- onClick: () => setSelectedTable(key),
5295
- className: (0, _rebasepro_ui.cls)("flex items-center p-1 cursor-pointer rounded transition-colors group relative", selectedTable === key ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light" : "hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark"),
5296
- children: [
5297
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
5298
- className: "w-3.5 h-3.5 mr-1 shrink-0 text-text-disabled dark:text-text-disabled-dark",
5299
- fill: "none",
5300
- stroke: "currentColor",
5301
- viewBox: "0 0 24 24",
5302
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
5303
- strokeLinecap: "round",
5304
- strokeLinejoin: "round",
5305
- strokeWidth: 2,
5306
- 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"
5307
- })
5308
- }),
5309
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
5310
- variant: "body2",
5311
- className: "text-xs truncate flex-1 min-w-0",
5312
- children: table.tableName
5313
- }),
5314
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5315
- className: "flex items-center gap-1.5 shrink-0 ml-2",
5316
- children: [table.rlsEnabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
5317
- title: t("studio_rls_enabled"),
5318
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-green-500" })
5319
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Tooltip, {
5320
- title: t("studio_rls_disabled"),
5321
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "w-1.5 h-1.5 rounded-full bg-orange-400 opacity-50" })
5322
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5323
- className: "text-[10px] opacity-40 group-hover:opacity-100 min-w-[1.2rem] text-right font-medium",
5324
- children: table.policies.length
5325
- })]
5326
- })
5327
- ]
5328
- }, key);
5329
- })
5330
- })]
5331
- }, schemaName))
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 ${sanitizeSqlIdentifier(table)} ${action} ROW LEVEL SECURITY`);
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 (!activeCollection) return;
5566
- const rule = {
5567
- name: newPolicy.policyname,
5568
- operation: newPolicy.cmd?.toLowerCase(),
5569
- mode: newPolicy.permissive?.toLowerCase(),
5570
- using: newPolicy.qual || void 0,
5571
- withCheck: newPolicy.with_check || void 0,
5572
- roles: newPolicy.roles
5573
- };
5574
- const existingRules = ((0, _rebasepro_types.isPostgresCollection)(activeCollection) ? activeCollection.securityRules : void 0) || [];
5575
- let newRules;
5576
- if (editingPolicy === "new") newRules = [...existingRules, rule];
5577
- else newRules = existingRules.map((r) => r.name === editingPolicy.policyname ? rule : r);
5578
- try {
5579
- if (!(await fetch(`${apiUrl}/api/schema-editor/collection/save`, {
5580
- method: "POST",
5581
- headers: { "Content-Type": "application/json" },
5582
- body: JSON.stringify({
5583
- collectionId: activeCollection.id || activeCollection.path || activeCollection.alias || activeTableData.tableName,
5584
- collectionData: { securityRules: newRules }
5585
- })
5586
- })).ok) throw new Error("Failed to save policy");
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: "Policy saved successfully"
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 && !activeCollection && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_rebasepro_ui.Alert, {
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: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
5611
- variant: "body2",
5612
- className: "mb-1",
5613
- children: "Table not managed by Rebase"
5614
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Typography, {
5615
- variant: "caption",
5616
- className: "opacity-80",
5617
- children: "This table is not mapped to a Rebase Schema via code. To edit security policies visually, you must first import this table into a Schema configuration file."
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 ${sanitizeSqlIdentifier(table)}`);
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
- activeCollection && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.Button, {
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$1(iso) {
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$1, {
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$1, {
7704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
7510
7705
  label: "Last Run",
7511
- value: formatRelative$1(selectedJob.lastRunAt)
7706
+ value: formatRelative$2(selectedJob.lastRunAt)
7512
7707
  }),
7513
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
7708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$2, {
7514
7709
  label: "Next Run",
7515
- value: selectedJob.enabled ? formatRelative$1(selectedJob.nextRunAt) : "Paused"
7710
+ value: selectedJob.enabled ? formatRelative$2(selectedJob.nextRunAt) : "Paused"
7516
7711
  }),
7517
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard$1, {
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$1, {
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$1, {
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$1, {
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$1({ label, value, mono, chipColor, highlight }) {
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 || void 0),
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
- style: {
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
- style: {
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.jsxs)("span", {
10322
- style: {
10323
- fontSize: 12,
10324
- color: "#6c7086"
10325
- },
10326
- children: [logs.length, " entries"]
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
- style: {
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
- style: {
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)("span", {
10347
- style: {
10348
- color: "#6c7086",
10349
- flexShrink: 0
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)("span", {
10354
- style: {
10355
- color: LEVEL_COLORS[log.level] || "#cdd6f4",
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)("span", {
10364
- style: {
10365
- color: SOURCE_COLORS[log.source] || "#cdd6f4",
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)("span", {
10376
- style: {
10377
- color: "#cdd6f4",
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
- style: {
10385
- padding: 32,
10386
- textAlign: "center",
10387
- color: "#6c7086"
10388
- },
10389
- children: "No log entries yet. Logs will appear here as requests come in."
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: "#6c7086",
10398
- info: "#89b4fa",
10399
- warn: "#f9e2af",
10400
- error: "#f38ba8"
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: "#74c7ec",
10404
- auth: "#cba6f7",
10405
- storage: "#a6e3a1",
10406
- realtime: "#fab387",
10407
- system: "#6c7086"
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/RebaseStudio.tsx
10412
- var SQLEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_SQLEditor(), SQLEditor_exports)).then((m) => ({ default: m.SQLEditor })));
10413
- var JSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_JSEditor(), JSEditor_exports)).then((m) => ({ default: m.JSEditor })));
10414
- var RLSEditor = (0, react.lazy)(() => Promise.resolve().then(() => (init_RLSEditor(), RLSEditor_exports)).then((m) => ({ default: m.RLSEditor })));
10415
- var StorageView = (0, react.lazy)(() => Promise.resolve().then(() => (init_StorageView(), StorageView_exports)).then((m) => ({ default: m.StorageView })));
10416
- var CronJobsView = (0, react.lazy)(() => Promise.resolve().then(() => (init_CronJobsView(), CronJobsView_exports)).then((m) => ({ default: m.CronJobsView })));
10417
- var SchemaVisualizer = (0, react.lazy)(() => Promise.resolve().then(() => (init_SchemaVisualizer(), SchemaVisualizer_exports)).then((m) => ({ default: m.SchemaVisualizer })));
10418
- var BranchesView = (0, react.lazy)(() => Promise.resolve().then(() => (init_BranchesView(), BranchesView_exports)).then((m) => ({ default: m.BranchesView })));
10419
- var ApiExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_ApiExplorer(), ApiExplorer_exports)).then((m) => ({ default: m.ApiExplorer })));
10420
- var LogsExplorer = (0, react.lazy)(() => Promise.resolve().then(() => (init_LogsExplorer(), LogsExplorer_exports)).then((m) => ({ default: m.LogsExplorer })));
10421
- /**
10422
- * Declarative component to configure the Studio in Rebase.
10423
- * Renders nothing — purely registers config into the RebaseRegistry.
10424
- *
10425
- * The "schema" tool (collection editor view) is now a built-in CMS feature.
10426
- * When `<RebaseCMS collectionEditor={...}>` is used, the schema view is
10427
- * automatically injected into Studio — no manual wiring needed.
10428
- */
10429
- var DEFAULT_HOME_PAGE = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StudioHomePage, {});
10430
- function RebaseStudio({ tools, homePage }) {
10431
- const dispatch = (0, _rebasepro_core.useRebaseRegistryDispatch)();
10432
- const resolvedHomePage = homePage ?? DEFAULT_HOME_PAGE;
10433
- const devViews = (0, react.useMemo)(() => {
10434
- const views = [];
10435
- const activeTools = tools ?? [
10436
- "sql",
10437
- "js",
10438
- "rls",
10439
- "storage",
10440
- "cron",
10441
- "schema-visualizer",
10442
- "branches",
10443
- "api",
10444
- "logs"
10445
- ];
10446
- const suspense = (el) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, {
10447
- fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_rebasepro_ui.CircularProgressCenter, {}),
10448
- children: el
10449
- });
10450
- if (activeTools.includes("sql")) views.push({
10451
- slug: "sql",
10452
- name: "SQL Console",
10453
- group: "Database",
10454
- icon: "terminal",
10455
- description: "Execute SQL queries",
10456
- view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SQLEditor, {}))
10457
- });
10458
- if (activeTools.includes("js")) views.push({
10459
- slug: "js",
10460
- name: "JS Console",
10461
- group: "Compute",
10462
- icon: "code",
10463
- description: "Execute JavaScript",
10464
- view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(JSEditor, {}))
10465
- });
10466
- if (activeTools.includes("rls")) views.push({
10467
- slug: "rls",
10468
- name: "RLS Policies",
10469
- group: "Database",
10470
- icon: "ShieldCheck",
10471
- description: "Row Level Security",
10472
- view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RLSEditor, {}))
10473
- });
10474
- if (activeTools.includes("storage")) views.push({
10475
- slug: "storage",
10476
- name: "Storage",
10477
- group: "Storage",
10478
- icon: "HardDrive",
10479
- description: "Manage storage files",
10480
- view: suspense(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StorageView, {}))
10481
- });
10482
- if (activeTools.includes("cron")) views.push({
10483
- slug: "cron",
10484
- name: "Cron Jobs",
10485
- group: "Compute",
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);