@rebasepro/studio 0.23.0 → 0.24.1-canary.g56af4a4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ApiExplorer-pYMZflL3.js → ApiExplorer-Bzt7Ye49.js} +18 -29
- package/dist/ApiExplorer-Bzt7Ye49.js.map +1 -0
- package/dist/ApiKeysView-Bqf6XYR1.js +1739 -0
- package/dist/ApiKeysView-Bqf6XYR1.js.map +1 -0
- package/dist/BackupsView-BhOHB6iP.js +354 -0
- package/dist/BackupsView-BhOHB6iP.js.map +1 -0
- package/dist/{BranchesView-D6Sz1a5f.js → BranchesView-BHfwGTYc.js} +16 -10
- package/dist/BranchesView-BHfwGTYc.js.map +1 -0
- package/dist/{CronJobsView-7hjgSYId.js → CronJobsView-CMNK7eWO.js} +175 -116
- package/dist/CronJobsView-CMNK7eWO.js.map +1 -0
- package/dist/{JSEditor-qiuwmcvk.js → JSEditor-DIO2Mw8q.js} +230 -165
- package/dist/JSEditor-DIO2Mw8q.js.map +1 -0
- package/dist/{LogsExplorer-Xq8K4oo6.js → LogsExplorer-C-bwDFIl.js} +66 -44
- package/dist/LogsExplorer-C-bwDFIl.js.map +1 -0
- package/dist/{RLSEditor-Djh43lBE.js → RLSEditor-jFD_d06f.js} +246 -84
- package/dist/RLSEditor-jFD_d06f.js.map +1 -0
- package/dist/{SQLEditor-TBw_K6CW.js → SQLEditor-D5Ci4pyW.js} +502 -600
- package/dist/SQLEditor-D5Ci4pyW.js.map +1 -0
- package/dist/{SchemaVisualizer-DtDzIUNy.js → SchemaVisualizer-D-rAqwul.js} +496 -227
- package/dist/SchemaVisualizer-D-rAqwul.js.map +1 -0
- package/dist/{StorageView-CMG1DOVX.js → StorageView-CPkDKbsX.js} +838 -451
- package/dist/StorageView-CPkDKbsX.js.map +1 -0
- package/dist/assets/dagre-layout.worker-BFnhAzuR.js.map +1 -0
- package/dist/components/ApiExplorer/TryItPanel.d.ts +6 -1
- package/dist/components/ApiKeys/CreateApiKeyDialog.d.ts +21 -2
- package/dist/components/ApiKeys/scope-words.d.ts +37 -0
- package/dist/components/ApiKeys/scopes.d.ts +113 -0
- package/dist/components/AuthSimulationSelector.d.ts +5 -1
- package/dist/components/LogsExplorer/useLogTail.d.ts +9 -0
- package/dist/components/RLSEditor/policyListing.d.ts +44 -0
- package/dist/components/SQLEditor/SQLEditor.d.ts +11 -0
- package/dist/components/SchemaVisualizer/dagre-layout.d.ts +32 -0
- package/dist/components/SchemaVisualizer/dagre-layout.worker.d.ts +13 -0
- package/dist/components/SchemaVisualizer/layout-runner.d.ts +23 -0
- package/dist/components/SchemaVisualizer/schema-visualizer.utils.d.ts +21 -11
- package/dist/components/SchemaVisualizer/useSchemaGraph.d.ts +44 -1
- package/dist/components/StorageView/useVisibleFileMetadata.d.ts +46 -0
- package/dist/components/load-failure.d.ts +1 -1
- package/dist/components/useRunAsUsers.d.ts +25 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js +27 -0
- package/dist/dagre-layout.worker-j9NbH9Gb.js.map +1 -0
- package/dist/index.es.js +16 -14
- package/dist/index.es.js.map +1 -1
- package/dist/{load-failure-C6OMJSGC.js → load-failure-Bd8lFIO7.js} +2 -2
- package/dist/load-failure-Bd8lFIO7.js.map +1 -0
- package/dist/sql_utils-BZD31xE3.js +478 -0
- package/dist/sql_utils-BZD31xE3.js.map +1 -0
- package/dist/useRunAsUsers-CLaAtXt6.js +122 -0
- package/dist/useRunAsUsers-CLaAtXt6.js.map +1 -0
- package/dist/utils/sql_utils.d.ts +150 -49
- package/package.json +9 -9
- package/dist/ApiExplorer-pYMZflL3.js.map +0 -1
- package/dist/ApiKeysView-qLSs47tw.js +0 -1239
- package/dist/ApiKeysView-qLSs47tw.js.map +0 -1
- package/dist/AuthSimulationSelector-BewmhMBw.js +0 -56
- package/dist/AuthSimulationSelector-BewmhMBw.js.map +0 -1
- package/dist/BackupsView-DYTAD6aW.js +0 -253
- package/dist/BackupsView-DYTAD6aW.js.map +0 -1
- package/dist/BranchesView-D6Sz1a5f.js.map +0 -1
- package/dist/CronJobsView-7hjgSYId.js.map +0 -1
- package/dist/JSEditor-qiuwmcvk.js.map +0 -1
- package/dist/LogsExplorer-Xq8K4oo6.js.map +0 -1
- package/dist/RLSEditor-Djh43lBE.js.map +0 -1
- package/dist/SQLEditor-TBw_K6CW.js.map +0 -1
- package/dist/SchemaVisualizer-DtDzIUNy.js.map +0 -1
- package/dist/StorageView-CMG1DOVX.js.map +0 -1
- package/dist/components/ApiKeys/permissions.d.ts +0 -66
- package/dist/load-failure-C6OMJSGC.js.map +0 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as AuthSimulationSelector, t as useRunAsUsers } from "./useRunAsUsers-CLaAtXt6.js";
|
|
2
|
+
import { f as toCsv } from "./sql_utils-BZD31xE3.js";
|
|
2
3
|
import { ErrorView, IconForView, useApiConfig, useModeController, useRebaseClient, useRebaseContext, useSnackbarController, useStudioCollectionRegistry, useStudioSidePanelController, useTranslation } from "@rebasepro/app";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
5
|
import { Button, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, IconButton, Menu, MenuItem, MoreVerticalIcon, PencilIcon, PlayIcon, PlusIcon, ResizablePanels, SaveIcon, Tab, Tabs, TerminalIcon, TextField, Tooltip, Trash2Icon, Typography, VirtualTable, XIcon, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
5
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { ALL_WHERE_FILTER_OPS } from "@rebasepro/types";
|
|
7
8
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
8
9
|
import { Highlight, themes } from "prism-react-renderer";
|
|
9
|
-
import { createRebaseClient } from "@rebasepro/client";
|
|
10
|
+
import { createMemoryStorage, createRebaseClient } from "@rebasepro/client";
|
|
10
11
|
import Editor from "@monaco-editor/react";
|
|
11
12
|
//#region src/components/JSEditor/JSMonacoEditor.tsx
|
|
12
13
|
/**
|
|
@@ -138,10 +139,29 @@ interface AdminUser {
|
|
|
138
139
|
updatedAt: string;
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
interface ListUsersOptions {
|
|
143
|
+
search?: string;
|
|
144
|
+
/** Page size. The server's default is 25. */
|
|
145
|
+
limit?: number;
|
|
146
|
+
offset?: number;
|
|
147
|
+
orderBy?: string;
|
|
148
|
+
orderDir?: "asc" | "desc";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** One page of users, and \`total\`: how many match in all. */
|
|
152
|
+
interface UsersPage {
|
|
153
|
+
users: AdminUser[];
|
|
154
|
+
total: number;
|
|
155
|
+
limit: number;
|
|
156
|
+
offset: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
141
159
|
interface RebaseCMS {
|
|
142
|
-
|
|
160
|
+
/** One page of users (25 unless \`limit\` says otherwise) and the total. */
|
|
161
|
+
listUsers(options?: ListUsersOptions): Promise<UsersPage>;
|
|
162
|
+
listUsersPaginated(options?: ListUsersOptions): Promise<UsersPage>;
|
|
143
163
|
getUser(userId: string): Promise<{ user: AdminUser }>;
|
|
144
|
-
createUser(data: { email: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;
|
|
164
|
+
createUser(data: { email: string; displayName?: string; password?: string; roles?: string[]; skipPasswordPolicy?: boolean }): Promise<{ user: AdminUser }>;
|
|
145
165
|
updateUser(userId: string, data: { email?: string; displayName?: string; password?: string; roles?: string[] }): Promise<{ user: AdminUser }>;
|
|
146
166
|
deleteUser(userId: string): Promise<{ success: boolean }>;
|
|
147
167
|
bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
|
|
@@ -200,8 +220,8 @@ type RebaseData = {
|
|
|
200
220
|
* // Auth
|
|
201
221
|
* const session = client.auth.getSession();
|
|
202
222
|
*
|
|
203
|
-
* // Admin
|
|
204
|
-
* const { users } = await client.admin.listUsers();
|
|
223
|
+
* // Admin: one page of users, and how many there are
|
|
224
|
+
* const { users, total } = await client.admin.listUsers({ limit: 50 });
|
|
205
225
|
*
|
|
206
226
|
* // Custom endpoint
|
|
207
227
|
* const result = await client.call("/my-endpoint", { myData: 123 });
|
|
@@ -221,12 +241,16 @@ interface RebaseClient {
|
|
|
221
241
|
[collectionSlug: string]: unknown;
|
|
222
242
|
}
|
|
223
243
|
|
|
224
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* The client for this run: your own, one that runs every request as the user
|
|
246
|
+
* picked in "Run as" (it holds no session, so \`client.auth.getSession()\` is null),
|
|
247
|
+
* or an unauthenticated one under "No Auth".
|
|
248
|
+
*/
|
|
225
249
|
declare const client: RebaseClient;
|
|
226
250
|
|
|
227
251
|
/** Execution context with user and collection information. */
|
|
228
252
|
interface JSEditorContext {
|
|
229
|
-
/** The user the script
|
|
253
|
+
/** The user the script runs as: the one picked in "Run as", or you. \`null\` under "No Auth". */
|
|
230
254
|
user: {
|
|
231
255
|
uid: string;
|
|
232
256
|
displayName: string | null;
|
|
@@ -359,15 +383,20 @@ var QUICK_REFERENCE = [
|
|
|
359
383
|
code: "const result = await client.data.collection(\"COLLECTION\")\n .where(\"field\", \">\", value)\n .orderBy(\"field\", \"desc\")\n .limit(10)\n .find();\nreturn result;",
|
|
360
384
|
description: "Chained query builder"
|
|
361
385
|
},
|
|
386
|
+
{
|
|
387
|
+
label: "Who am I",
|
|
388
|
+
code: "return context.user;",
|
|
389
|
+
description: "The user this script runs as, or null under No Auth"
|
|
390
|
+
},
|
|
362
391
|
{
|
|
363
392
|
label: "Auth: Current session",
|
|
364
393
|
code: "const session = client.auth.getSession();\nreturn session;",
|
|
365
|
-
description: "
|
|
394
|
+
description: "The session this client holds: yours, or none under Run as and No Auth"
|
|
366
395
|
},
|
|
367
396
|
{
|
|
368
397
|
label: "Admin: List users",
|
|
369
|
-
code: "
|
|
370
|
-
description: "
|
|
398
|
+
code: "// One page: raise limit or page with offset while users.length < total\nconst { users, total } = await client.admin.listUsers({ limit: 50, offset: 0 });\nreturn { users, total };",
|
|
399
|
+
description: "A page of users, and how many there are in all"
|
|
371
400
|
},
|
|
372
401
|
{
|
|
373
402
|
label: "Custom endpoint",
|
|
@@ -376,6 +405,7 @@ var QUICK_REFERENCE = [
|
|
|
376
405
|
}
|
|
377
406
|
];
|
|
378
407
|
var JSEditorSidebar = ({ collections, snippets, history, onSelectSnippet, onDeleteSnippet, onInsertCode }) => {
|
|
408
|
+
const { t } = useTranslation();
|
|
379
409
|
const [activeTab, setActiveTab] = useState("collections");
|
|
380
410
|
return /* @__PURE__ */ jsxs("div", {
|
|
381
411
|
className: cls("flex flex-col h-full w-full bg-surface-card border-r", defaultBorderMixin),
|
|
@@ -391,7 +421,7 @@ var JSEditorSidebar = ({ collections, snippets, history, onSelectSnippet, onDele
|
|
|
391
421
|
title: "Browse collections",
|
|
392
422
|
children: /* @__PURE__ */ jsx("span", {
|
|
393
423
|
className: "text-xs",
|
|
394
|
-
children: "
|
|
424
|
+
children: t("studio_js_collections_tab")
|
|
395
425
|
})
|
|
396
426
|
})
|
|
397
427
|
}),
|
|
@@ -430,7 +460,7 @@ var JSEditorSidebar = ({ collections, snippets, history, onSelectSnippet, onDele
|
|
|
430
460
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
431
461
|
variant: "caption",
|
|
432
462
|
className: "font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
433
|
-
children: "
|
|
463
|
+
children: t("studio_js_collections_tab")
|
|
434
464
|
})
|
|
435
465
|
}), /* @__PURE__ */ jsx("div", {
|
|
436
466
|
className: "flex-grow overflow-y-auto p-1 space-y-1 no-scrollbar",
|
|
@@ -618,6 +648,7 @@ function CollectionItem({ collection, onInsertCode }) {
|
|
|
618
648
|
/* @__PURE__ */ jsx(Tooltip, {
|
|
619
649
|
title: "Insert find() snippet",
|
|
620
650
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
651
|
+
"aria-label": "Insert find() snippet",
|
|
621
652
|
size: "smallest",
|
|
622
653
|
className: "opacity-0 group-hover:opacity-100 text-text-disabled hover:text-primary transition-all",
|
|
623
654
|
onClick: (e) => {
|
|
@@ -653,6 +684,7 @@ function CollectionItem({ collection, onInsertCode }) {
|
|
|
653
684
|
})] });
|
|
654
685
|
}
|
|
655
686
|
function SnippetItem({ snippet, onSelect, onDelete }) {
|
|
687
|
+
const { t } = useTranslation();
|
|
656
688
|
return /* @__PURE__ */ jsxs("div", {
|
|
657
689
|
className: cls("group p-2 rounded border hover:border-hairline-strong bg-surface-card transition-all cursor-pointer relative", defaultBorderMixin),
|
|
658
690
|
onClick: () => onSelect(snippet.code),
|
|
@@ -668,6 +700,7 @@ function SnippetItem({ snippet, onSelect, onDelete }) {
|
|
|
668
700
|
children: snippet.code
|
|
669
701
|
}),
|
|
670
702
|
/* @__PURE__ */ jsx(IconButton, {
|
|
703
|
+
"aria-label": t("delete"),
|
|
671
704
|
size: "smallest",
|
|
672
705
|
className: "absolute right-1 top-1 opacity-0 group-hover:opacity-100 text-text-disabled hover:text-red-500 transition-opacity",
|
|
673
706
|
onClick: (e) => {
|
|
@@ -690,12 +723,12 @@ var STORAGE_PREFIX = "rebase_js_";
|
|
|
690
723
|
* new user did be a 404 — a placeholder presented as working code. `slug` is
|
|
691
724
|
* the project's first collection when one is known.
|
|
692
725
|
*/
|
|
693
|
-
var makeDefaultCode = (slug) => `// Available: client (RebaseClient)
|
|
726
|
+
var makeDefaultCode = (slug) => `// Available: client (RebaseClient), context
|
|
694
727
|
// Press Cmd+Enter (Ctrl+Enter) to run
|
|
695
728
|
//
|
|
696
729
|
// Examples:
|
|
697
|
-
// const users = await client.admin.listUsers();
|
|
698
|
-
// const
|
|
730
|
+
// const { users, total } = await client.admin.listUsers({ limit: 50, offset: 0 }); // one page of total
|
|
731
|
+
// const me = context.user; // who this script runs as ("Run as"), null under No Auth
|
|
699
732
|
${slug ? `
|
|
700
733
|
const result = await client.data.collection(${JSON.stringify(slug)}).find({ limit: 10 });
|
|
701
734
|
return result;
|
|
@@ -704,9 +737,24 @@ return result;
|
|
|
704
737
|
// const result = await client.data.collection("posts").find({ limit: 10 });
|
|
705
738
|
// return result;
|
|
706
739
|
|
|
707
|
-
return
|
|
740
|
+
return context.user;
|
|
708
741
|
`}`;
|
|
709
742
|
var DEFAULT_CODE = makeDefaultCode();
|
|
743
|
+
/**
|
|
744
|
+
* The auth of a client built for one run: its own, in memory, and empty.
|
|
745
|
+
*
|
|
746
|
+
* The SDK's default auth restores the session the app saved in `localStorage`,
|
|
747
|
+
* sends its token on every request and keeps refreshing it. A run under "No
|
|
748
|
+
* Auth" or as another user must do none of that: it would go out as the
|
|
749
|
+
* signed-in user, and `client.auth` would describe the wrong person.
|
|
750
|
+
*/
|
|
751
|
+
function runOnlyAuth() {
|
|
752
|
+
return {
|
|
753
|
+
storage: createMemoryStorage(),
|
|
754
|
+
persistSession: false,
|
|
755
|
+
autoRefresh: false
|
|
756
|
+
};
|
|
757
|
+
}
|
|
710
758
|
function loadFromStorage(key, fallback) {
|
|
711
759
|
try {
|
|
712
760
|
const raw = localStorage.getItem(STORAGE_PREFIX + key);
|
|
@@ -727,14 +775,22 @@ function formatJSON(value) {
|
|
|
727
775
|
return String(value);
|
|
728
776
|
}
|
|
729
777
|
}
|
|
778
|
+
/** The script without its comments: a collection named in one is not where rows came from. */
|
|
779
|
+
function withoutComments(code) {
|
|
780
|
+
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
|
|
781
|
+
}
|
|
730
782
|
/**
|
|
731
|
-
* Given the raw SDK result, try to detect which
|
|
783
|
+
* Given the raw SDK result, try to detect which collection its rows belong to.
|
|
732
784
|
* JS SDK results typically come back as `{ data: [{ id, values }] }` or plain arrays.
|
|
733
|
-
* The heuristic:
|
|
734
|
-
*
|
|
785
|
+
* The heuristic: the executed code, outside its comments, names exactly one
|
|
786
|
+
* collection — `collection("<slug>")` or `client.data.<slug>` — and the result has
|
|
787
|
+
* rows with an "id" column. A script naming two could have returned either's
|
|
788
|
+
* rows, and an action on a row would open the other collection's row with the
|
|
789
|
+
* same id, so it gets none.
|
|
735
790
|
*/
|
|
736
|
-
function detectCollectionsInResult(
|
|
791
|
+
function detectCollectionsInResult(executedCode, resultValue, collections) {
|
|
737
792
|
if (!resultValue || !collections?.length) return [];
|
|
793
|
+
const code = withoutComments(executedCode);
|
|
738
794
|
const mentionedSlugs = /* @__PURE__ */ new Set();
|
|
739
795
|
const collectionCallRegex = /\.collection\(["']([^"']+)["']\)/g;
|
|
740
796
|
let m;
|
|
@@ -748,7 +804,7 @@ function detectCollectionsInResult(code, resultValue, collections) {
|
|
|
748
804
|
"update",
|
|
749
805
|
"delete"
|
|
750
806
|
].includes(m[1])) mentionedSlugs.add(m[1]);
|
|
751
|
-
if (mentionedSlugs.size
|
|
807
|
+
if (mentionedSlugs.size !== 1) return [];
|
|
752
808
|
let rows = [];
|
|
753
809
|
const rv = resultValue;
|
|
754
810
|
if (rv?.data && Array.isArray(rv.data)) rows = rv.data;
|
|
@@ -839,27 +895,7 @@ function JSEditor() {
|
|
|
839
895
|
} : tab);
|
|
840
896
|
});
|
|
841
897
|
}, [firstSlug]);
|
|
842
|
-
const users =
|
|
843
|
-
const managed = [];
|
|
844
|
-
if (currentUser) managed.push({
|
|
845
|
-
uid: currentUser.uid,
|
|
846
|
-
displayName: currentUser.displayName,
|
|
847
|
-
email: currentUser.email,
|
|
848
|
-
photoURL: currentUser.photoURL,
|
|
849
|
-
roles: currentUser.roles
|
|
850
|
-
});
|
|
851
|
-
return managed;
|
|
852
|
-
}, [currentUser]);
|
|
853
|
-
const currentSelectableUser = useMemo(() => {
|
|
854
|
-
if (!currentUser) return null;
|
|
855
|
-
return {
|
|
856
|
-
uid: currentUser.uid,
|
|
857
|
-
displayName: currentUser.displayName,
|
|
858
|
-
email: currentUser.email,
|
|
859
|
-
photoURL: currentUser.photoURL,
|
|
860
|
-
roles: currentUser.roles
|
|
861
|
-
};
|
|
862
|
-
}, [currentUser]);
|
|
898
|
+
const { users, currentUser: currentSelectableUser, loading: usersLoading, onSearchTextChange: onUserSearchTextChange, totalCount: userTotalCount } = useRunAsUsers();
|
|
863
899
|
useEffect(() => {
|
|
864
900
|
saveToStorage("tabs", tabs);
|
|
865
901
|
}, [tabs]);
|
|
@@ -918,29 +954,32 @@ function JSEditor() {
|
|
|
918
954
|
const apiUrl = apiConfig?.apiUrl;
|
|
919
955
|
const getAuthToken = apiConfig?.getAuthToken;
|
|
920
956
|
if (!apiUrl) throw new Error("API URL not configured. Make sure apiUrl is set.");
|
|
921
|
-
if (authMode === "none")
|
|
922
|
-
client
|
|
957
|
+
if (authMode === "none") {
|
|
958
|
+
const client = createRebaseClient({
|
|
923
959
|
baseUrl: apiUrl,
|
|
924
|
-
|
|
925
|
-
})
|
|
926
|
-
isScoped: true
|
|
927
|
-
};
|
|
928
|
-
if (!selectedUser || currentUser && selectedUser.uid === currentUser.uid) {
|
|
929
|
-
if (!rebaseClient) throw new Error("Application client is not initialized.");
|
|
960
|
+
auth: runOnlyAuth()
|
|
961
|
+
});
|
|
930
962
|
return {
|
|
931
|
-
client
|
|
932
|
-
|
|
963
|
+
client,
|
|
964
|
+
release: () => client.close()
|
|
933
965
|
};
|
|
934
966
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
967
|
+
if (!selectedUser || currentUser && selectedUser.uid === currentUser.uid) {
|
|
968
|
+
if (!rebaseClient) throw new Error("Application client is not initialized.");
|
|
969
|
+
return { client: rebaseClient };
|
|
970
|
+
}
|
|
971
|
+
const token = getAuthToken ? await getAuthToken() ?? void 0 : void 0;
|
|
972
|
+
if (!getAuthToken || !token) throw new Error("No auth token available. Please sign in first.");
|
|
973
|
+
const client = createRebaseClient({
|
|
974
|
+
baseUrl: apiUrl,
|
|
975
|
+
token,
|
|
976
|
+
impersonate: selectedUser.uid,
|
|
977
|
+
auth: runOnlyAuth()
|
|
978
|
+
});
|
|
979
|
+
client.setAuthTokenGetter(getAuthToken);
|
|
938
980
|
return {
|
|
939
|
-
client
|
|
940
|
-
|
|
941
|
-
token
|
|
942
|
-
}),
|
|
943
|
-
isScoped: true
|
|
981
|
+
client,
|
|
982
|
+
release: () => client.close()
|
|
944
983
|
};
|
|
945
984
|
}, [
|
|
946
985
|
apiConfig,
|
|
@@ -959,7 +998,8 @@ function JSEditor() {
|
|
|
959
998
|
});
|
|
960
999
|
const consoleEntries = [];
|
|
961
1000
|
const startTime = performance.now();
|
|
962
|
-
let
|
|
1001
|
+
let release;
|
|
1002
|
+
const ranAsSignedInUser = authMode !== "none" && (!selectedUser || selectedUser.uid === currentUser?.uid);
|
|
963
1003
|
const originalConsole = {
|
|
964
1004
|
log: console.log,
|
|
965
1005
|
warn: console.warn,
|
|
@@ -979,10 +1019,11 @@ function JSEditor() {
|
|
|
979
1019
|
console.warn = captureConsole("warn");
|
|
980
1020
|
console.error = captureConsole("error");
|
|
981
1021
|
console.info = captureConsole("info");
|
|
982
|
-
const
|
|
983
|
-
|
|
1022
|
+
const built = await buildClient();
|
|
1023
|
+
release = built.release;
|
|
1024
|
+
const client = built.client;
|
|
984
1025
|
const context = {
|
|
985
|
-
user: selectedUser ?? (currentUser ? {
|
|
1026
|
+
user: authMode === "none" ? null : selectedUser ?? (currentUser ? {
|
|
986
1027
|
uid: currentUser.uid,
|
|
987
1028
|
displayName: currentUser.displayName,
|
|
988
1029
|
email: currentUser.email,
|
|
@@ -997,7 +1038,9 @@ function JSEditor() {
|
|
|
997
1038
|
value,
|
|
998
1039
|
console: consoleEntries,
|
|
999
1040
|
duration,
|
|
1000
|
-
timestamp: Date.now()
|
|
1041
|
+
timestamp: Date.now(),
|
|
1042
|
+
code,
|
|
1043
|
+
ranAsSignedInUser
|
|
1001
1044
|
});
|
|
1002
1045
|
const resultObj = value;
|
|
1003
1046
|
if (resultObj?.data && Array.isArray(resultObj.data)) setResultView("table");
|
|
@@ -1010,7 +1053,9 @@ function JSEditor() {
|
|
|
1010
1053
|
console: consoleEntries,
|
|
1011
1054
|
duration,
|
|
1012
1055
|
error: err instanceof Error ? err.message : String(err),
|
|
1013
|
-
timestamp: Date.now()
|
|
1056
|
+
timestamp: Date.now(),
|
|
1057
|
+
code,
|
|
1058
|
+
ranAsSignedInUser
|
|
1014
1059
|
});
|
|
1015
1060
|
setResultView("json");
|
|
1016
1061
|
} finally {
|
|
@@ -1019,11 +1064,12 @@ function JSEditor() {
|
|
|
1019
1064
|
console.error = originalConsole.error;
|
|
1020
1065
|
console.info = originalConsole.info;
|
|
1021
1066
|
setIsRunning(false);
|
|
1022
|
-
|
|
1067
|
+
release?.();
|
|
1023
1068
|
}
|
|
1024
1069
|
}, [
|
|
1025
1070
|
activeTab?.code,
|
|
1026
1071
|
buildClient,
|
|
1072
|
+
authMode,
|
|
1027
1073
|
selectedUser,
|
|
1028
1074
|
currentUser,
|
|
1029
1075
|
collectionInfos
|
|
@@ -1092,13 +1138,9 @@ function JSEditor() {
|
|
|
1092
1138
|
};
|
|
1093
1139
|
}, [result]);
|
|
1094
1140
|
const matchedCollections = useMemo(() => {
|
|
1095
|
-
if (!result?.value || result.error) return [];
|
|
1096
|
-
return detectCollectionsInResult(
|
|
1097
|
-
}, [
|
|
1098
|
-
result,
|
|
1099
|
-
activeTab?.code,
|
|
1100
|
-
collectionRegistry?.collections
|
|
1101
|
-
]);
|
|
1141
|
+
if (!result?.value || result.error || !result.ranAsSignedInUser) return [];
|
|
1142
|
+
return detectCollectionsInResult(result.code, result.value, collectionRegistry?.collections ?? []);
|
|
1143
|
+
}, [result, collectionRegistry?.collections]);
|
|
1102
1144
|
const getRowEntityActions = useCallback((rowData) => {
|
|
1103
1145
|
if (!rowData || matchedCollections.length === 0) return [];
|
|
1104
1146
|
return matchedCollections.filter((mc) => rowData[mc.pkColumn] != null).map((mc) => ({
|
|
@@ -1106,13 +1148,97 @@ function JSEditor() {
|
|
|
1106
1148
|
entityId: rowData[mc.pkColumn]
|
|
1107
1149
|
}));
|
|
1108
1150
|
}, [matchedCollections]);
|
|
1151
|
+
const resultColumns = useMemo(() => matchedCollections.length > 0 ? [{
|
|
1152
|
+
key: "__entity_action__",
|
|
1153
|
+
title: "",
|
|
1154
|
+
width: 36,
|
|
1155
|
+
sortable: false,
|
|
1156
|
+
resizable: false
|
|
1157
|
+
}, ...tableData.columns] : tableData.columns, [matchedCollections.length, tableData.columns]);
|
|
1158
|
+
const renderResultCell = useCallback(({ rowData, column }) => {
|
|
1159
|
+
if (column.key === "__entity_action__") {
|
|
1160
|
+
const rowActions = getRowEntityActions(rowData ?? {});
|
|
1161
|
+
if (rowActions.length === 0) return /* @__PURE__ */ jsx("div", { className: "h-full w-full" });
|
|
1162
|
+
if (rowActions.length === 1) {
|
|
1163
|
+
const ra = rowActions[0];
|
|
1164
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1165
|
+
className: "h-full flex items-center justify-center",
|
|
1166
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
1167
|
+
title: t("studio_sql_edit_entity", {
|
|
1168
|
+
name: ra.collection.collection.name,
|
|
1169
|
+
id: String(ra.entityId)
|
|
1170
|
+
}),
|
|
1171
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1172
|
+
"aria-label": t("studio_sql_edit_entity", {
|
|
1173
|
+
name: ra.collection.collection.name,
|
|
1174
|
+
id: String(ra.entityId)
|
|
1175
|
+
}),
|
|
1176
|
+
size: "small",
|
|
1177
|
+
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300",
|
|
1178
|
+
onClick: (e) => {
|
|
1179
|
+
e.stopPropagation();
|
|
1180
|
+
sidePanelController.open({
|
|
1181
|
+
path: ra.collection.collectionSlug,
|
|
1182
|
+
entityId: ra.entityId,
|
|
1183
|
+
collection: ra.collection.collection,
|
|
1184
|
+
updateUrl: false
|
|
1185
|
+
});
|
|
1186
|
+
},
|
|
1187
|
+
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
1188
|
+
})
|
|
1189
|
+
})
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1193
|
+
className: "h-full flex items-center justify-center",
|
|
1194
|
+
children: /* @__PURE__ */ jsx(Menu, {
|
|
1195
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
1196
|
+
"aria-label": t("more_actions"),
|
|
1197
|
+
size: "small",
|
|
1198
|
+
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300",
|
|
1199
|
+
onClick: (e) => e.stopPropagation(),
|
|
1200
|
+
children: /* @__PURE__ */ jsx(MoreVerticalIcon, { size: iconSize.smallest })
|
|
1201
|
+
}),
|
|
1202
|
+
children: rowActions.map((ra) => /* @__PURE__ */ jsx(MenuItem, {
|
|
1203
|
+
dense: true,
|
|
1204
|
+
onClick: () => {
|
|
1205
|
+
sidePanelController.open({
|
|
1206
|
+
path: ra.collection.collectionSlug,
|
|
1207
|
+
entityId: ra.entityId,
|
|
1208
|
+
collection: ra.collection.collection,
|
|
1209
|
+
updateUrl: false
|
|
1210
|
+
});
|
|
1211
|
+
},
|
|
1212
|
+
children: t("studio_sql_edit_entity", {
|
|
1213
|
+
name: ra.collection.collection.name,
|
|
1214
|
+
id: String(ra.entityId)
|
|
1215
|
+
})
|
|
1216
|
+
}, ra.collection.collectionSlug))
|
|
1217
|
+
})
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (!rowData) return null;
|
|
1221
|
+
const val = rowData[column.key];
|
|
1222
|
+
const displayValue = typeof val === "object" && val !== null ? JSON.stringify(val) : String(val ?? "");
|
|
1223
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1224
|
+
className: "px-4 py-1.5 h-full flex items-center whitespace-nowrap text-[13px] text-text-primary dark:text-text-primary-dark font-mono",
|
|
1225
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1226
|
+
className: "truncate flex-grow",
|
|
1227
|
+
title: displayValue,
|
|
1228
|
+
children: displayValue === "" ? /* @__PURE__ */ jsx("span", {
|
|
1229
|
+
className: "text-text-disabled dark:text-text-disabled-dark italic text-[11px]",
|
|
1230
|
+
children: "NULL"
|
|
1231
|
+
}) : displayValue
|
|
1232
|
+
})
|
|
1233
|
+
});
|
|
1234
|
+
}, [
|
|
1235
|
+
getRowEntityActions,
|
|
1236
|
+
sidePanelController,
|
|
1237
|
+
t
|
|
1238
|
+
]);
|
|
1109
1239
|
const handleExportCSV = useCallback(() => {
|
|
1110
1240
|
if (tableData.data.length === 0) return;
|
|
1111
|
-
const csv =
|
|
1112
|
-
const val = row[c.key];
|
|
1113
|
-
const str = val === null || val === void 0 ? "" : String(val);
|
|
1114
|
-
return str.includes(",") ? `"${str}"` : str;
|
|
1115
|
-
}).join(","))].join("\n");
|
|
1241
|
+
const csv = toCsv(tableData.columns.map((c) => c.key), tableData.data);
|
|
1116
1242
|
const blob = new Blob([csv], { type: "text/csv" });
|
|
1117
1243
|
const url = URL.createObjectURL(blob);
|
|
1118
1244
|
const a = document.createElement("a");
|
|
@@ -1154,6 +1280,7 @@ function JSEditor() {
|
|
|
1154
1280
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1155
1281
|
className: "flex-grow overflow-hidden",
|
|
1156
1282
|
children: /* @__PURE__ */ jsx(ResizablePanels, {
|
|
1283
|
+
dividerAriaLabel: t("resize_sidebar"),
|
|
1157
1284
|
orientation: "horizontal",
|
|
1158
1285
|
panelSizePercent: sidebarSize,
|
|
1159
1286
|
onPanelSizeChange: setSidebarSize,
|
|
@@ -1193,6 +1320,7 @@ function JSEditor() {
|
|
|
1193
1320
|
children: tab.name
|
|
1194
1321
|
}),
|
|
1195
1322
|
tabs.length > 1 && /* @__PURE__ */ jsx(IconButton, {
|
|
1323
|
+
"aria-label": t("close_tab"),
|
|
1196
1324
|
size: "smallest",
|
|
1197
1325
|
onClick: (e) => {
|
|
1198
1326
|
e.stopPropagation();
|
|
@@ -1204,6 +1332,7 @@ function JSEditor() {
|
|
|
1204
1332
|
]
|
|
1205
1333
|
}, tab.id))
|
|
1206
1334
|
}), /* @__PURE__ */ jsx(IconButton, {
|
|
1335
|
+
"aria-label": t("new_tab"),
|
|
1207
1336
|
size: "small",
|
|
1208
1337
|
onClick: addTab,
|
|
1209
1338
|
className: "ml-2 flex-shrink-0",
|
|
@@ -1216,6 +1345,7 @@ function JSEditor() {
|
|
|
1216
1345
|
/* @__PURE__ */ jsx(Tooltip, {
|
|
1217
1346
|
title: "Save as snippet",
|
|
1218
1347
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1348
|
+
"aria-label": "Save as snippet",
|
|
1219
1349
|
size: "small",
|
|
1220
1350
|
onClick: () => {
|
|
1221
1351
|
setSnippetName("");
|
|
@@ -1228,6 +1358,7 @@ function JSEditor() {
|
|
|
1228
1358
|
result?.value != null && /* @__PURE__ */ jsx(Tooltip, {
|
|
1229
1359
|
title: "Export result as JSON",
|
|
1230
1360
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1361
|
+
"aria-label": "Export result as JSON",
|
|
1231
1362
|
size: "small",
|
|
1232
1363
|
onClick: exportResult,
|
|
1233
1364
|
children: /* @__PURE__ */ jsx(DownloadIcon, { size: iconSize.smallest })
|
|
@@ -1251,6 +1382,7 @@ function JSEditor() {
|
|
|
1251
1382
|
}), /* @__PURE__ */ jsx("div", {
|
|
1252
1383
|
className: "flex-grow overflow-hidden",
|
|
1253
1384
|
children: /* @__PURE__ */ jsx(ResizablePanels, {
|
|
1385
|
+
dividerAriaLabel: t("resize_editor"),
|
|
1254
1386
|
orientation: "vertical",
|
|
1255
1387
|
panelSizePercent: editorHeight,
|
|
1256
1388
|
onPanelSizeChange: setEditorHeight,
|
|
@@ -1265,7 +1397,9 @@ function JSEditor() {
|
|
|
1265
1397
|
selectedUser,
|
|
1266
1398
|
setSelectedUser,
|
|
1267
1399
|
users,
|
|
1268
|
-
loading:
|
|
1400
|
+
loading: usersLoading,
|
|
1401
|
+
onUserSearchTextChange,
|
|
1402
|
+
userTotalCount,
|
|
1269
1403
|
currentUser: currentSelectableUser
|
|
1270
1404
|
})
|
|
1271
1405
|
}), /* @__PURE__ */ jsx("div", {
|
|
@@ -1406,87 +1540,10 @@ function JSEditor() {
|
|
|
1406
1540
|
className: "flex-grow relative h-full min-h-0 min-w-0",
|
|
1407
1541
|
children: /* @__PURE__ */ jsx(VirtualTable, {
|
|
1408
1542
|
data: tableData.data,
|
|
1409
|
-
columns:
|
|
1410
|
-
key: "__entity_action__",
|
|
1411
|
-
title: "",
|
|
1412
|
-
width: 36,
|
|
1413
|
-
sortable: false,
|
|
1414
|
-
resizable: false
|
|
1415
|
-
}, ...tableData.columns] : tableData.columns,
|
|
1543
|
+
columns: resultColumns,
|
|
1416
1544
|
rowHeight: 32,
|
|
1417
1545
|
headerHeight: 32,
|
|
1418
|
-
cellRenderer:
|
|
1419
|
-
if (column.key === "__entity_action__") {
|
|
1420
|
-
const rowActions = getRowEntityActions(rowData ?? {});
|
|
1421
|
-
if (rowActions.length === 0) return /* @__PURE__ */ jsx("div", { className: "h-full w-full" });
|
|
1422
|
-
if (rowActions.length === 1) {
|
|
1423
|
-
const ra = rowActions[0];
|
|
1424
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1425
|
-
className: "h-full flex items-center justify-center",
|
|
1426
|
-
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
1427
|
-
title: t("studio_sql_edit_entity", {
|
|
1428
|
-
name: ra.collection.collection.name,
|
|
1429
|
-
id: String(ra.entityId)
|
|
1430
|
-
}),
|
|
1431
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
1432
|
-
size: "small",
|
|
1433
|
-
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300",
|
|
1434
|
-
onClick: (e) => {
|
|
1435
|
-
e.stopPropagation();
|
|
1436
|
-
sidePanelController.open({
|
|
1437
|
-
path: ra.collection.collectionSlug,
|
|
1438
|
-
entityId: ra.entityId,
|
|
1439
|
-
collection: ra.collection.collection,
|
|
1440
|
-
updateUrl: false
|
|
1441
|
-
});
|
|
1442
|
-
},
|
|
1443
|
-
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
1444
|
-
})
|
|
1445
|
-
})
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1449
|
-
className: "h-full flex items-center justify-center",
|
|
1450
|
-
children: /* @__PURE__ */ jsx(Menu, {
|
|
1451
|
-
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
1452
|
-
size: "small",
|
|
1453
|
-
className: "text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300",
|
|
1454
|
-
onClick: (e) => e.stopPropagation(),
|
|
1455
|
-
children: /* @__PURE__ */ jsx(MoreVerticalIcon, { size: iconSize.smallest })
|
|
1456
|
-
}),
|
|
1457
|
-
children: rowActions.map((ra) => /* @__PURE__ */ jsx(MenuItem, {
|
|
1458
|
-
dense: true,
|
|
1459
|
-
onClick: () => {
|
|
1460
|
-
sidePanelController.open({
|
|
1461
|
-
path: ra.collection.collectionSlug,
|
|
1462
|
-
entityId: ra.entityId,
|
|
1463
|
-
collection: ra.collection.collection,
|
|
1464
|
-
updateUrl: false
|
|
1465
|
-
});
|
|
1466
|
-
},
|
|
1467
|
-
children: t("studio_sql_edit_entity", {
|
|
1468
|
-
name: ra.collection.collection.name,
|
|
1469
|
-
id: String(ra.entityId)
|
|
1470
|
-
})
|
|
1471
|
-
}, ra.collection.collectionSlug))
|
|
1472
|
-
})
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
if (!rowData) return null;
|
|
1476
|
-
const val = rowData[column.key];
|
|
1477
|
-
const displayValue = typeof val === "object" && val !== null ? JSON.stringify(val) : String(val ?? "");
|
|
1478
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1479
|
-
className: "px-4 py-1.5 h-full flex items-center whitespace-nowrap text-[13px] text-text-primary dark:text-text-primary-dark font-mono",
|
|
1480
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
1481
|
-
className: "truncate flex-grow",
|
|
1482
|
-
title: displayValue,
|
|
1483
|
-
children: displayValue === "" ? /* @__PURE__ */ jsx("span", {
|
|
1484
|
-
className: "text-text-disabled dark:text-text-disabled-dark italic text-[11px]",
|
|
1485
|
-
children: "NULL"
|
|
1486
|
-
}) : displayValue
|
|
1487
|
-
})
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1546
|
+
cellRenderer: renderResultCell
|
|
1490
1547
|
})
|
|
1491
1548
|
})]
|
|
1492
1549
|
}),
|
|
@@ -1513,7 +1570,7 @@ function JSEditor() {
|
|
|
1513
1570
|
children: "Return Value"
|
|
1514
1571
|
}), /* @__PURE__ */ jsx("pre", {
|
|
1515
1572
|
className: "text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words text-[12px]",
|
|
1516
|
-
children:
|
|
1573
|
+
children: /* @__PURE__ */ jsx(FormattedJSON, { value: result.value })
|
|
1517
1574
|
})]
|
|
1518
1575
|
})]
|
|
1519
1576
|
})
|
|
@@ -1605,7 +1662,11 @@ function JSEditor() {
|
|
|
1605
1662
|
})]
|
|
1606
1663
|
});
|
|
1607
1664
|
}
|
|
1608
|
-
|
|
1665
|
+
/**
|
|
1666
|
+
* Memoised on the value: tokenising a large result is the costliest thing
|
|
1667
|
+
* this pane draws, and it used to run again for every keystroke in the editor.
|
|
1668
|
+
*/
|
|
1669
|
+
var JSONHighlight = React.memo(function JSONHighlight({ value }) {
|
|
1609
1670
|
const json = formatJSON(value);
|
|
1610
1671
|
const { mode } = useModeController();
|
|
1611
1672
|
return /* @__PURE__ */ jsx(Highlight, {
|
|
@@ -1623,8 +1684,12 @@ function JSONHighlight({ value }) {
|
|
|
1623
1684
|
}, i))
|
|
1624
1685
|
})
|
|
1625
1686
|
});
|
|
1626
|
-
}
|
|
1687
|
+
});
|
|
1688
|
+
/** A value as indented JSON, formatted again only when the value changes. */
|
|
1689
|
+
var FormattedJSON = React.memo(function FormattedJSON({ value }) {
|
|
1690
|
+
return /* @__PURE__ */ jsx(Fragment, { children: formatJSON(value) });
|
|
1691
|
+
});
|
|
1627
1692
|
//#endregion
|
|
1628
1693
|
export { JSEditor };
|
|
1629
1694
|
|
|
1630
|
-
//# sourceMappingURL=JSEditor-
|
|
1695
|
+
//# sourceMappingURL=JSEditor-DIO2Mw8q.js.map
|