@rebasepro/studio 0.2.1 → 0.2.4
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-BmcdhAX0.js → ApiExplorer-CGHEF1uL.js} +4 -4
- package/dist/ApiExplorer-CGHEF1uL.js.map +1 -0
- package/dist/{CronJobsView-CNfz0etw.js → CronJobsView-3PM_qR8v.js} +20 -3
- package/dist/CronJobsView-3PM_qR8v.js.map +1 -0
- package/dist/{JSEditor-Ch8z8lJ4.js → JSEditor-Br4ke-J4.js} +30 -27
- package/dist/JSEditor-Br4ke-J4.js.map +1 -0
- package/dist/LogsExplorer-_4sZadKn.js +162 -0
- package/dist/LogsExplorer-_4sZadKn.js.map +1 -0
- package/dist/{SQLEditor-BELYJQRP.js → SQLEditor-BC0IOUQu.js} +4 -4
- package/dist/SQLEditor-BC0IOUQu.js.map +1 -0
- package/dist/common/src/collections/default-collections.d.ts +12 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/query_builder.d.ts +51 -0
- package/dist/common/src/index.d.ts +1 -0
- package/dist/common/src/util/permissions.d.ts +1 -0
- package/dist/core/src/components/LoginView/LoginView.d.ts +17 -1
- package/dist/core/src/components/common/types.d.ts +10 -7
- package/dist/core/src/components/common/useDebouncedData.d.ts +1 -1
- package/dist/core/src/core/RebaseProps.d.ts +13 -2
- package/dist/core/src/core/RebaseRouter.d.ts +1 -1
- package/dist/core/src/hooks/index.d.ts +0 -1
- package/dist/core/src/util/entity_cache.d.ts +0 -5
- package/dist/core/src/util/index.d.ts +0 -2
- package/dist/core/src/util/useStorageUploadController.d.ts +2 -2
- package/dist/formex/src/utils.d.ts +2 -2
- package/dist/index.es.js +23 -5
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +232 -35
- package/dist/index.umd.js.map +1 -1
- package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +1 -1
- package/dist/studio/src/components/ApiExplorer/types.d.ts +3 -3
- package/dist/studio/src/components/LogsExplorer/LogsExplorer.d.ts +1 -0
- package/dist/types/src/controllers/auth.d.ts +2 -24
- package/dist/types/src/controllers/client.d.ts +0 -3
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data.d.ts +21 -0
- package/dist/types/src/controllers/data_driver.d.ts +18 -0
- package/dist/types/src/controllers/registry.d.ts +5 -4
- package/dist/types/src/rebase_context.d.ts +1 -1
- package/dist/types/src/types/auth_adapter.d.ts +2 -4
- package/dist/types/src/types/collections.d.ts +0 -4
- package/dist/types/src/types/component_ref.d.ts +1 -1
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +1 -0
- package/dist/types/src/types/export_import.d.ts +1 -1
- package/dist/types/src/types/formex.d.ts +2 -2
- package/dist/types/src/types/properties.d.ts +2 -2
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +6 -4
- package/dist/types/src/users/roles.d.ts +0 -8
- package/dist/ui/src/components/Button.d.ts +2 -2
- package/dist/ui/src/components/ErrorBoundary.d.ts +25 -3
- package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +1 -1
- package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +6 -6
- package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +8 -8
- package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +11 -11
- package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +1 -1
- package/dist/ui/src/components/VirtualTable/types.d.ts +9 -9
- package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -1
- package/dist/ui/src/util/debounce.d.ts +1 -1
- package/package.json +13 -8
- package/src/components/ApiExplorer/ApiExplorer.tsx +2 -2
- package/src/components/ApiExplorer/EndpointDetail.tsx +1 -1
- package/src/components/ApiExplorer/TryItPanel.tsx +5 -5
- package/src/components/ApiExplorer/parseSpec.ts +3 -3
- package/src/components/ApiExplorer/types.ts +3 -3
- package/src/components/CronJobs/CronJobsView.tsx +27 -2
- package/src/components/JSEditor/JSEditor.tsx +21 -18
- package/src/components/JSEditor/JSMonacoEditor.tsx +10 -10
- package/src/components/LogsExplorer/LogsExplorer.tsx +224 -0
- package/src/components/RebaseStudio.tsx +10 -1
- package/src/components/SQLEditor/SQLEditor.tsx +28 -7
- package/src/components/StudioHomePage.tsx +2 -1
- package/src/utils/parseSpec.test.ts +274 -0
- package/src/utils/pgColumnToProperty.ts +16 -2
- package/dist/ApiExplorer-BmcdhAX0.js.map +0 -1
- package/dist/CronJobsView-CNfz0etw.js.map +0 -1
- package/dist/JSEditor-Ch8z8lJ4.js.map +0 -1
- package/dist/SQLEditor-BELYJQRP.js.map +0 -1
- package/dist/core/src/hooks/useValidateAuthenticator.d.ts +0 -21
- package/dist/core/src/util/icon_synonyms.d.ts +0 -1
- package/dist/core/src/util/useTraceUpdate.d.ts +0 -2
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CellRendererParams, OnRowClickParams, OnVirtualTableColumnResizeParams, VirtualTableColumn, VirtualTableFilterValues, VirtualTableWhereFilterOp } from "./VirtualTableProps";
|
|
3
3
|
import { FilterFormFieldProps } from "./VirtualTableHeader";
|
|
4
|
-
export type VirtualTableRowProps<T
|
|
5
|
-
style:
|
|
4
|
+
export type VirtualTableRowProps<T extends Record<string, unknown>> = {
|
|
5
|
+
style: React.CSSProperties;
|
|
6
6
|
rowHeight: number;
|
|
7
7
|
rowData: T;
|
|
8
8
|
rowIndex: number;
|
|
9
|
-
onRowClick?: (props: OnRowClickParams<
|
|
9
|
+
onRowClick?: (props: OnRowClickParams<Record<string, unknown>>) => void;
|
|
10
10
|
children: React.ReactNode[];
|
|
11
11
|
columns: VirtualTableColumn[];
|
|
12
12
|
hoverRow?: boolean;
|
|
@@ -19,20 +19,20 @@ export type VirtualTableContextProps<T> = {
|
|
|
19
19
|
columns: VirtualTableColumn[];
|
|
20
20
|
cellRenderer: React.ComponentType<CellRendererParams<T>>;
|
|
21
21
|
currentSort: "asc" | "desc" | undefined;
|
|
22
|
-
filter?: VirtualTableFilterValues<
|
|
23
|
-
onRowClick?: (props: OnRowClickParams<
|
|
24
|
-
onColumnSort: (key: string) =>
|
|
22
|
+
filter?: VirtualTableFilterValues<string>;
|
|
23
|
+
onRowClick?: (props: OnRowClickParams<Record<string, unknown>>) => void;
|
|
24
|
+
onColumnSort: (key: string) => void;
|
|
25
25
|
onColumnResize: (params: OnVirtualTableColumnResizeParams) => void;
|
|
26
26
|
onColumnResizeEnd: (params: OnVirtualTableColumnResizeParams) => void;
|
|
27
|
-
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp,
|
|
27
|
+
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
|
|
28
28
|
sortByProperty?: string;
|
|
29
29
|
customView?: React.ReactNode;
|
|
30
30
|
hoverRow: boolean;
|
|
31
|
-
createFilterField?: (props: FilterFormFieldProps<
|
|
31
|
+
createFilterField?: (props: FilterFormFieldProps<unknown>) => React.ReactNode;
|
|
32
32
|
rowClassName?: (rowData: T) => string | undefined;
|
|
33
33
|
endAdornment?: React.ReactNode;
|
|
34
34
|
AddColumnComponent?: React.ComponentType;
|
|
35
35
|
onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
|
|
36
36
|
draggingColumnId?: string | null;
|
|
37
|
-
extraData?:
|
|
37
|
+
extraData?: unknown;
|
|
38
38
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function useDebounceCallback<T extends (...args: any[]) =>
|
|
1
|
+
export declare function useDebounceCallback<T extends (...args: any[]) => unknown>(callback?: T, delay?: number): T;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @ignore
|
|
3
3
|
*/
|
|
4
|
-
export declare function debounce<T extends (...args: any[]) =>
|
|
4
|
+
export declare function debounce<T extends (...args: any[]) => unknown>(func: T, wait?: number): T & Cancelable;
|
|
5
5
|
/**
|
|
6
6
|
* @ignore
|
|
7
7
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/studio",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"main": "./dist/index.umd.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
7
7
|
"types": "dist/studio/src/index.d.ts",
|
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
"pgsql-ast-parser": "12.0.2",
|
|
16
16
|
"prism-react-renderer": "^2.4.1",
|
|
17
17
|
"react-dropzone": "^14.4.1",
|
|
18
|
-
"@rebasepro/common": "0.2.
|
|
19
|
-
"@rebasepro/
|
|
20
|
-
"@rebasepro/
|
|
21
|
-
"@rebasepro/
|
|
22
|
-
"@rebasepro/
|
|
23
|
-
"@rebasepro/
|
|
18
|
+
"@rebasepro/common": "0.2.4",
|
|
19
|
+
"@rebasepro/client": "0.2.4",
|
|
20
|
+
"@rebasepro/core": "0.2.4",
|
|
21
|
+
"@rebasepro/ui": "0.2.4",
|
|
22
|
+
"@rebasepro/utils": "0.2.4",
|
|
23
|
+
"@rebasepro/types": "0.2.4"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"react": ">=19.0.0",
|
|
27
27
|
"react-dom": ">=19.0.0",
|
|
28
28
|
"react-router": "^7.0.0",
|
|
29
29
|
"react-router-dom": "^7.0.0",
|
|
30
|
-
"@rebasepro/admin": "0.2.
|
|
30
|
+
"@rebasepro/admin": "0.2.4"
|
|
31
31
|
},
|
|
32
32
|
"peerDependenciesMeta": {
|
|
33
33
|
"@rebasepro/admin": {
|
|
@@ -78,6 +78,11 @@
|
|
|
78
78
|
"access": "public"
|
|
79
79
|
},
|
|
80
80
|
"gitHead": "9ecf37abf793bd2f2daaaed6f517ee5ee19b01ae",
|
|
81
|
+
"repository": {
|
|
82
|
+
"type": "git",
|
|
83
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
84
|
+
"directory": "packages/studio"
|
|
85
|
+
},
|
|
81
86
|
"scripts": {
|
|
82
87
|
"dev": "vite",
|
|
83
88
|
"test": "jest --passWithNoTests",
|
|
@@ -50,9 +50,9 @@ export function ApiExplorer() {
|
|
|
50
50
|
setSpec(data);
|
|
51
51
|
setLoading(false);
|
|
52
52
|
}
|
|
53
|
-
} catch (err:
|
|
53
|
+
} catch (err: unknown) {
|
|
54
54
|
if (!cancelled) {
|
|
55
|
-
setError(err.message
|
|
55
|
+
setError(err instanceof Error ? err.message : "Failed to load API spec");
|
|
56
56
|
setLoading(false);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -153,7 +153,7 @@ function SchemaBlock({ schema, spec, depth }: { schema: OpenApiSchema; spec: Ope
|
|
|
153
153
|
// Resolve $ref
|
|
154
154
|
if (schema.$ref) {
|
|
155
155
|
const name = resolveRefName(schema.$ref);
|
|
156
|
-
const resolved = resolveRef(spec, schema.$ref);
|
|
156
|
+
const resolved = resolveRef(spec, schema.$ref) as OpenApiSchema;
|
|
157
157
|
return (
|
|
158
158
|
<div>
|
|
159
159
|
<Typography
|
|
@@ -125,8 +125,8 @@ export function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath = ""
|
|
|
125
125
|
if (hasBody && body.trim()) {
|
|
126
126
|
try {
|
|
127
127
|
JSON.parse(body);
|
|
128
|
-
} catch (err:
|
|
129
|
-
setValidationError(`Invalid JSON: ${err.message}`);
|
|
128
|
+
} catch (err: unknown) {
|
|
129
|
+
setValidationError(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -171,11 +171,11 @@ export function TryItPanel({ endpoint, apiUrl, getAuthToken, user, basePath = ""
|
|
|
171
171
|
statusText: res.statusText,
|
|
172
172
|
body: text,
|
|
173
173
|
time: elapsed });
|
|
174
|
-
} catch (err:
|
|
174
|
+
} catch (err: unknown) {
|
|
175
175
|
setResponse({
|
|
176
176
|
status: 0,
|
|
177
177
|
statusText: "Network Error",
|
|
178
|
-
body: err.message
|
|
178
|
+
body: err instanceof Error ? err.message : "Request failed",
|
|
179
179
|
time: Math.round(performance.now() - start)
|
|
180
180
|
});
|
|
181
181
|
} finally {
|
|
@@ -479,7 +479,7 @@ function buildBodyTemplate(endpoint: ParsedEndpoint): string {
|
|
|
479
479
|
return lines.join("\n");
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
function defaultValue(schema:
|
|
482
|
+
function defaultValue(schema: { type?: string; format?: string; enum?: (string | number)[] }): string {
|
|
483
483
|
if (schema.enum) return JSON.stringify(schema.enum[0]);
|
|
484
484
|
switch (schema.type) {
|
|
485
485
|
case "string":
|
|
@@ -94,11 +94,11 @@ export function resolveRefName(ref: string): string {
|
|
|
94
94
|
/**
|
|
95
95
|
* Resolve a $ref to its actual schema from the spec.
|
|
96
96
|
*/
|
|
97
|
-
export function resolveRef(spec: OpenApiSpec, ref: string):
|
|
97
|
+
export function resolveRef(spec: OpenApiSpec, ref: string): unknown {
|
|
98
98
|
const parts = ref.replace("#/", "").split("/");
|
|
99
|
-
let current:
|
|
99
|
+
let current: unknown = spec;
|
|
100
100
|
for (const part of parts) {
|
|
101
|
-
current = current?.[part];
|
|
101
|
+
current = (current as Record<string, unknown>)?.[part];
|
|
102
102
|
}
|
|
103
103
|
return current ?? {};
|
|
104
104
|
}
|
|
@@ -6,7 +6,7 @@ export interface OpenApiSpec {
|
|
|
6
6
|
paths: Record<string, Record<string, OpenApiOperation>>;
|
|
7
7
|
components?: {
|
|
8
8
|
schemas?: Record<string, OpenApiSchema>;
|
|
9
|
-
securitySchemes?: Record<string,
|
|
9
|
+
securitySchemes?: Record<string, unknown>;
|
|
10
10
|
};
|
|
11
11
|
tags?: { name: string; description?: string }[];
|
|
12
12
|
security?: Record<string, string[]>[];
|
|
@@ -57,8 +57,8 @@ export interface OpenApiSchema {
|
|
|
57
57
|
maximum?: number;
|
|
58
58
|
maxLength?: number;
|
|
59
59
|
minLength?: number;
|
|
60
|
-
default?:
|
|
61
|
-
example?:
|
|
60
|
+
default?: unknown;
|
|
61
|
+
example?: unknown;
|
|
62
62
|
additionalProperties?: boolean | OpenApiSchema;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -91,8 +91,33 @@ export function CronJobsView() {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
load();
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
|
|
95
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
96
|
+
|
|
97
|
+
const scheduleNext = () => {
|
|
98
|
+
if (cancelled) return;
|
|
99
|
+
timeoutId = setTimeout(async () => {
|
|
100
|
+
if (document.visibilityState === "visible") {
|
|
101
|
+
await load();
|
|
102
|
+
}
|
|
103
|
+
scheduleNext();
|
|
104
|
+
}, 15_000);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
scheduleNext();
|
|
108
|
+
|
|
109
|
+
const handleVisibility = () => {
|
|
110
|
+
if (document.visibilityState === "visible") {
|
|
111
|
+
load();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
cancelled = true;
|
|
118
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
119
|
+
document.removeEventListener("visibilitychange", handleVisibility);
|
|
120
|
+
};
|
|
96
121
|
}, []); // runs once
|
|
97
122
|
|
|
98
123
|
// ── Fetch logs when selection changes ──
|
|
@@ -47,12 +47,12 @@ import { AuthSimulationSelector } from "../AuthSimulationSelector";
|
|
|
47
47
|
|
|
48
48
|
interface ConsoleEntry {
|
|
49
49
|
type: "log" | "warn" | "error" | "info";
|
|
50
|
-
args:
|
|
50
|
+
args: unknown[];
|
|
51
51
|
timestamp: number;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
interface ExecutionResult {
|
|
55
|
-
value:
|
|
55
|
+
value: unknown;
|
|
56
56
|
console: ConsoleEntry[];
|
|
57
57
|
duration: number;
|
|
58
58
|
error?: string;
|
|
@@ -101,7 +101,7 @@ function saveToStorage<T>(key: string, value: T) {
|
|
|
101
101
|
} catch { /* quota */ }
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
function formatJSON(value:
|
|
104
|
+
function formatJSON(value: unknown): string {
|
|
105
105
|
try {
|
|
106
106
|
return JSON.stringify(value, null, 2);
|
|
107
107
|
} catch {
|
|
@@ -125,7 +125,7 @@ interface MatchedJSCollection {
|
|
|
125
125
|
*/
|
|
126
126
|
function detectCollectionsInResult(
|
|
127
127
|
code: string,
|
|
128
|
-
resultValue:
|
|
128
|
+
resultValue: unknown,
|
|
129
129
|
collections: EntityCollection[]
|
|
130
130
|
): MatchedJSCollection[] {
|
|
131
131
|
if (!resultValue || !collections?.length) return [];
|
|
@@ -151,9 +151,10 @@ function detectCollectionsInResult(
|
|
|
151
151
|
if (mentionedSlugs.size === 0) return [];
|
|
152
152
|
|
|
153
153
|
// Check if result has rows with an "id" field
|
|
154
|
-
let rows:
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
let rows: Record<string, unknown>[] = [];
|
|
155
|
+
const rv = resultValue as Record<string, unknown>;
|
|
156
|
+
if (rv?.data && Array.isArray(rv.data)) {
|
|
157
|
+
rows = rv.data;
|
|
157
158
|
} else if (Array.isArray(resultValue)) {
|
|
158
159
|
rows = resultValue;
|
|
159
160
|
}
|
|
@@ -405,7 +406,7 @@ isScoped: true };
|
|
|
405
406
|
info: console.info
|
|
406
407
|
};
|
|
407
408
|
|
|
408
|
-
const captureConsole = (type: ConsoleEntry["type"]) => (...args:
|
|
409
|
+
const captureConsole = (type: ConsoleEntry["type"]) => (...args: unknown[]) => {
|
|
409
410
|
consoleEntries.push({ type,
|
|
410
411
|
args,
|
|
411
412
|
timestamp: Date.now() });
|
|
@@ -451,7 +452,8 @@ timestamp: Date.now() });
|
|
|
451
452
|
});
|
|
452
453
|
|
|
453
454
|
// Auto-detect best view
|
|
454
|
-
|
|
455
|
+
const resultObj = value as Record<string, unknown> | undefined;
|
|
456
|
+
if (resultObj?.data && Array.isArray(resultObj.data)) {
|
|
455
457
|
setResultView("table");
|
|
456
458
|
} else if (consoleEntries.length > 0 && value === undefined) {
|
|
457
459
|
setResultView("console");
|
|
@@ -522,11 +524,12 @@ message: "Snippet saved" });
|
|
|
522
524
|
if (!result?.value) return { columns: [] as VirtualTableColumn[],
|
|
523
525
|
data: [] as Record<string, unknown>[] };
|
|
524
526
|
|
|
525
|
-
let rows:
|
|
526
|
-
|
|
527
|
-
|
|
527
|
+
let rows: Record<string, unknown>[] = [];
|
|
528
|
+
const val = result.value as Record<string, unknown>;
|
|
529
|
+
if (val?.data && Array.isArray(val.data)) {
|
|
530
|
+
rows = (val.data as Record<string, unknown>[]).map((entity) => ({
|
|
528
531
|
id: entity.id,
|
|
529
|
-
...entity.values,
|
|
532
|
+
...(entity.values as Record<string, unknown> ?? {}),
|
|
530
533
|
...(entity.values ? {} : entity)
|
|
531
534
|
}));
|
|
532
535
|
} else if (Array.isArray(result.value)) {
|
|
@@ -564,13 +567,13 @@ data: rows };
|
|
|
564
567
|
);
|
|
565
568
|
}, [result, activeTab?.code, collectionRegistry?.collections]);
|
|
566
569
|
|
|
567
|
-
const getRowEntityActions = useCallback((rowData:
|
|
570
|
+
const getRowEntityActions = useCallback((rowData: Record<string, unknown>): { collection: MatchedJSCollection; entityId: string | number }[] => {
|
|
568
571
|
if (!rowData || matchedCollections.length === 0) return [];
|
|
569
572
|
return matchedCollections
|
|
570
573
|
.filter(mc => rowData[mc.pkColumn] != null)
|
|
571
574
|
.map(mc => ({
|
|
572
575
|
collection: mc,
|
|
573
|
-
entityId: rowData[mc.pkColumn]
|
|
576
|
+
entityId: rowData[mc.pkColumn] as string | number
|
|
574
577
|
}));
|
|
575
578
|
}, [matchedCollections]);
|
|
576
579
|
|
|
@@ -685,7 +688,7 @@ message: t("studio_sql_markdown_copy_failed") });
|
|
|
685
688
|
</IconButton>
|
|
686
689
|
</Tooltip>
|
|
687
690
|
|
|
688
|
-
{result?.value && (
|
|
691
|
+
{result?.value != null && (
|
|
689
692
|
<Tooltip title="Export result as JSON">
|
|
690
693
|
<IconButton size="small" onClick={exportResult}>
|
|
691
694
|
<DownloadIcon size={iconSize.smallest}/>
|
|
@@ -843,7 +846,7 @@ resizable: false }, ...tableData.columns]
|
|
|
843
846
|
cellRenderer={({ rowData, column, rowIndex }: CellRendererParams<Record<string, unknown>>) => {
|
|
844
847
|
// Entity action column
|
|
845
848
|
if (column.key === "__entity_action__") {
|
|
846
|
-
const rowActions = getRowEntityActions(rowData);
|
|
849
|
+
const rowActions = getRowEntityActions(rowData ?? {});
|
|
847
850
|
if (rowActions.length === 0) return <div className="h-full w-full"/>;
|
|
848
851
|
if (rowActions.length === 1) {
|
|
849
852
|
const ra = rowActions[0];
|
|
@@ -1037,7 +1040,7 @@ id: String(ra.entityId) })}
|
|
|
1037
1040
|
|
|
1038
1041
|
// ─── JSON Syntax Highlighting ────────────────────────────────────────
|
|
1039
1042
|
|
|
1040
|
-
function JSONHighlight({ value }: { value:
|
|
1043
|
+
function JSONHighlight({ value }: { value: unknown }) {
|
|
1041
1044
|
const json = formatJSON(value);
|
|
1042
1045
|
const { mode } = useModeController();
|
|
1043
1046
|
|
|
@@ -111,7 +111,7 @@ interface RebaseAuth {
|
|
|
111
111
|
changePassword(oldPassword: string, newPassword: string): Promise<{ success: boolean; message: string }>;
|
|
112
112
|
sendVerificationEmail(): Promise<{ success: boolean; message: string }>;
|
|
113
113
|
verifyEmail(token: string): Promise<{ success: boolean; message: string }>;
|
|
114
|
-
getSessions(): Promise<
|
|
114
|
+
getSessions(): Promise<RebaseSession[]>;
|
|
115
115
|
revokeSession(sessionId: string): Promise<{ success: boolean }>;
|
|
116
116
|
revokeAllSessions(): Promise<{ success: boolean }>;
|
|
117
117
|
getSession(): RebaseSession | null;
|
|
@@ -133,8 +133,8 @@ interface RebaseRole {
|
|
|
133
133
|
id: string;
|
|
134
134
|
name: string;
|
|
135
135
|
isAdmin: boolean;
|
|
136
|
-
defaultPermissions: Record<string,
|
|
137
|
-
config: Record<string,
|
|
136
|
+
defaultPermissions: Record<string, unknown> | null;
|
|
137
|
+
config: Record<string, unknown> | null;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
interface RebaseAdmin {
|
|
@@ -145,8 +145,8 @@ interface RebaseAdmin {
|
|
|
145
145
|
deleteUser(userId: string): Promise<{ success: boolean }>;
|
|
146
146
|
listRoles(): Promise<{ roles: RebaseRole[] }>;
|
|
147
147
|
getRole(roleId: string): Promise<{ role: RebaseRole }>;
|
|
148
|
-
createRole(data: { id: string; name: string; isAdmin?: boolean; defaultPermissions?:
|
|
149
|
-
updateRole(roleId: string, data: { name?: string; isAdmin?: boolean; defaultPermissions?:
|
|
148
|
+
createRole(data: { id: string; name: string; isAdmin?: boolean; defaultPermissions?: Record<string, unknown>; config?: Record<string, unknown> }): Promise<{ role: RebaseRole }>;
|
|
149
|
+
updateRole(roleId: string, data: { name?: string; isAdmin?: boolean; defaultPermissions?: Record<string, unknown>; config?: Record<string, unknown> }): Promise<{ role: RebaseRole }>;
|
|
150
150
|
deleteRole(roleId: string): Promise<{ success: boolean }>;
|
|
151
151
|
bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
|
|
152
152
|
}
|
|
@@ -155,7 +155,7 @@ interface UploadFileProps {
|
|
|
155
155
|
file: FileIcon;
|
|
156
156
|
fileName?: string;
|
|
157
157
|
path?: string;
|
|
158
|
-
metadata?: Record<string,
|
|
158
|
+
metadata?: Record<string, unknown>;
|
|
159
159
|
bucket?: string;
|
|
160
160
|
}
|
|
161
161
|
|
|
@@ -168,7 +168,7 @@ interface UploadFileResult {
|
|
|
168
168
|
interface DownloadConfig {
|
|
169
169
|
url: string | null;
|
|
170
170
|
fileNotFound?: boolean;
|
|
171
|
-
metadata?:
|
|
171
|
+
metadata?: Record<string, unknown>;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
interface StorageSource {
|
|
@@ -176,7 +176,7 @@ interface StorageSource {
|
|
|
176
176
|
getSignedUrl(pathOrUrl: string, bucket?: string): Promise<DownloadConfig>;
|
|
177
177
|
getObject(path: string, bucket?: string): Promise<FileIcon | null>;
|
|
178
178
|
deleteObject(path: string, bucket?: string): Promise<void>;
|
|
179
|
-
listObjects(path: string, options?: { bucket?: string; maxResults?: number; pageToken?: string }): Promise<
|
|
179
|
+
listObjects(path: string, options?: { bucket?: string; maxResults?: number; pageToken?: string }): Promise<unknown>;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
type RebaseData = {
|
|
@@ -220,9 +220,9 @@ interface RebaseClient {
|
|
|
220
220
|
/** Storage operations */
|
|
221
221
|
storage?: StorageSource;
|
|
222
222
|
/** Call a custom server-side endpoint */
|
|
223
|
-
call<T =
|
|
223
|
+
call<T = unknown>(endpoint: string, payload?: unknown): Promise<T>;
|
|
224
224
|
/** Direct collection access (shorthand) */
|
|
225
|
-
[collectionSlug: string]:
|
|
225
|
+
[collectionSlug: string]: unknown;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/** The pre-configured client instance. Already authenticated with the current user session. */
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import { Select, SelectItem, TextField, Checkbox, Label } from "@rebasepro/ui";
|
|
3
|
+
|
|
4
|
+
interface LogEntry {
|
|
5
|
+
id: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
level: "debug" | "info" | "warn" | "error";
|
|
8
|
+
source: "api" | "auth" | "storage" | "realtime" | "system";
|
|
9
|
+
message: string;
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LEVEL_COLORS: Record<string, string> = {
|
|
14
|
+
debug: "#6c7086",
|
|
15
|
+
info: "#89b4fa",
|
|
16
|
+
warn: "#f9e2af",
|
|
17
|
+
error: "#f38ba8"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SOURCE_COLORS: Record<string, string> = {
|
|
21
|
+
api: "#74c7ec",
|
|
22
|
+
auth: "#cba6f7",
|
|
23
|
+
storage: "#a6e3a1",
|
|
24
|
+
realtime: "#fab387",
|
|
25
|
+
system: "#6c7086"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function LogsExplorer() {
|
|
29
|
+
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
30
|
+
const [level, setLevel] = useState<string>("");
|
|
31
|
+
const [source, setSource] = useState<string>("");
|
|
32
|
+
const [search, setSearch] = useState("");
|
|
33
|
+
const [autoScroll, setAutoScroll] = useState(true);
|
|
34
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const fetchLogs = useCallback(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
if (level) params.set("level", level);
|
|
39
|
+
if (source) params.set("source", source);
|
|
40
|
+
if (search) params.set("search", search);
|
|
41
|
+
params.set("limit", "200");
|
|
42
|
+
|
|
43
|
+
const resp = await fetch(`/api/logs?${params}`);
|
|
44
|
+
if (resp.ok) {
|
|
45
|
+
const data: { entries?: LogEntry[] } = await resp.json();
|
|
46
|
+
setLogs(data.entries || []);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
/* ignore poll failures */
|
|
50
|
+
}
|
|
51
|
+
}, [level, source, search]);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
55
|
+
let cancelled = false;
|
|
56
|
+
|
|
57
|
+
fetchLogs();
|
|
58
|
+
|
|
59
|
+
const scheduleNext = () => {
|
|
60
|
+
if (cancelled) return;
|
|
61
|
+
timeoutId = setTimeout(async () => {
|
|
62
|
+
if (document.visibilityState === "visible") {
|
|
63
|
+
await fetchLogs();
|
|
64
|
+
}
|
|
65
|
+
scheduleNext();
|
|
66
|
+
}, 3000);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
scheduleNext();
|
|
70
|
+
|
|
71
|
+
const handleVisibility = () => {
|
|
72
|
+
if (document.visibilityState === "visible") {
|
|
73
|
+
fetchLogs();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
document.addEventListener("visibilitychange", handleVisibility);
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
81
|
+
document.removeEventListener("visibilitychange", handleVisibility);
|
|
82
|
+
};
|
|
83
|
+
}, [fetchLogs]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (autoScroll && containerRef.current) {
|
|
87
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
88
|
+
}
|
|
89
|
+
}, [logs, autoScroll]);
|
|
90
|
+
|
|
91
|
+
const selectStyle: React.CSSProperties = {
|
|
92
|
+
background: "#313244",
|
|
93
|
+
color: "#cdd6f4",
|
|
94
|
+
border: "1px solid #45475a",
|
|
95
|
+
borderRadius: 4,
|
|
96
|
+
padding: "4px 8px"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div style={{
|
|
101
|
+
display: "flex",
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
height: "calc(100vh - 64px)",
|
|
104
|
+
background: "#1e1e2e",
|
|
105
|
+
color: "#cdd6f4"
|
|
106
|
+
}}>
|
|
107
|
+
{/* Toolbar */}
|
|
108
|
+
<div style={{
|
|
109
|
+
display: "flex",
|
|
110
|
+
gap: 8,
|
|
111
|
+
padding: "8px 16px",
|
|
112
|
+
borderBottom: "1px solid #313244",
|
|
113
|
+
alignItems: "center",
|
|
114
|
+
flexWrap: "wrap"
|
|
115
|
+
}}>
|
|
116
|
+
<Select
|
|
117
|
+
value={level}
|
|
118
|
+
onValueChange={setLevel}
|
|
119
|
+
size="small"
|
|
120
|
+
placeholder="All Levels"
|
|
121
|
+
>
|
|
122
|
+
<SelectItem value="">All Levels</SelectItem>
|
|
123
|
+
<SelectItem value="debug">Debug</SelectItem>
|
|
124
|
+
<SelectItem value="info">Info</SelectItem>
|
|
125
|
+
<SelectItem value="warn">Warn</SelectItem>
|
|
126
|
+
<SelectItem value="error">Error</SelectItem>
|
|
127
|
+
</Select>
|
|
128
|
+
<Select
|
|
129
|
+
value={source}
|
|
130
|
+
onValueChange={setSource}
|
|
131
|
+
size="small"
|
|
132
|
+
placeholder="All Sources"
|
|
133
|
+
>
|
|
134
|
+
<SelectItem value="">All Sources</SelectItem>
|
|
135
|
+
<SelectItem value="api">API</SelectItem>
|
|
136
|
+
<SelectItem value="auth">Auth</SelectItem>
|
|
137
|
+
<SelectItem value="storage">Storage</SelectItem>
|
|
138
|
+
<SelectItem value="realtime">Realtime</SelectItem>
|
|
139
|
+
<SelectItem value="system">System</SelectItem>
|
|
140
|
+
</Select>
|
|
141
|
+
<TextField
|
|
142
|
+
size="small"
|
|
143
|
+
placeholder="Search logs..."
|
|
144
|
+
value={search}
|
|
145
|
+
onChange={e => setSearch(e.target.value)}
|
|
146
|
+
className="flex-1 min-w-[200px]"
|
|
147
|
+
/>
|
|
148
|
+
<div className="flex items-center gap-1.5 cursor-pointer">
|
|
149
|
+
<Checkbox
|
|
150
|
+
id="auto-scroll"
|
|
151
|
+
checked={autoScroll}
|
|
152
|
+
onCheckedChange={setAutoScroll}
|
|
153
|
+
size="small"
|
|
154
|
+
padding={false}
|
|
155
|
+
/>
|
|
156
|
+
<Label
|
|
157
|
+
htmlFor="auto-scroll"
|
|
158
|
+
className="text-xs select-none cursor-pointer"
|
|
159
|
+
>
|
|
160
|
+
Auto-scroll
|
|
161
|
+
</Label>
|
|
162
|
+
</div>
|
|
163
|
+
<span style={{ fontSize: 12, color: "#6c7086" }}>
|
|
164
|
+
{logs.length} entries
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
{/* Log entries */}
|
|
168
|
+
<div
|
|
169
|
+
ref={containerRef}
|
|
170
|
+
style={{
|
|
171
|
+
flex: 1,
|
|
172
|
+
overflow: "auto",
|
|
173
|
+
fontFamily: "monospace",
|
|
174
|
+
fontSize: 12,
|
|
175
|
+
padding: "8px 0"
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
{logs.map(log => (
|
|
179
|
+
<div
|
|
180
|
+
key={log.id}
|
|
181
|
+
style={{
|
|
182
|
+
padding: "2px 16px",
|
|
183
|
+
display: "flex",
|
|
184
|
+
gap: 8,
|
|
185
|
+
borderBottom: "1px solid #181825"
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<span style={{ color: "#6c7086", flexShrink: 0 }}>
|
|
189
|
+
{new Date(log.timestamp).toLocaleTimeString()}
|
|
190
|
+
</span>
|
|
191
|
+
<span style={{
|
|
192
|
+
color: LEVEL_COLORS[log.level] || "#cdd6f4",
|
|
193
|
+
width: 40,
|
|
194
|
+
flexShrink: 0,
|
|
195
|
+
textTransform: "uppercase",
|
|
196
|
+
fontWeight: 600
|
|
197
|
+
}}>
|
|
198
|
+
{log.level}
|
|
199
|
+
</span>
|
|
200
|
+
<span style={{
|
|
201
|
+
color: SOURCE_COLORS[log.source] || "#cdd6f4",
|
|
202
|
+
width: 64,
|
|
203
|
+
flexShrink: 0
|
|
204
|
+
}}>
|
|
205
|
+
[{log.source}]
|
|
206
|
+
</span>
|
|
207
|
+
<span style={{ color: "#cdd6f4", flex: 1 }}>
|
|
208
|
+
{log.message}
|
|
209
|
+
</span>
|
|
210
|
+
</div>
|
|
211
|
+
))}
|
|
212
|
+
{logs.length === 0 && (
|
|
213
|
+
<div style={{
|
|
214
|
+
padding: 32,
|
|
215
|
+
textAlign: "center",
|
|
216
|
+
color: "#6c7086"
|
|
217
|
+
}}>
|
|
218
|
+
No log entries yet. Logs will appear here as requests come in.
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
}
|