@promptbook/cli 0.112.0-93 → 0.112.0-96
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/apps/agents-server/next.config.ts +8 -1
- package/apps/agents-server/playwright.config.ts +2 -0
- package/apps/agents-server/src/app/admin/_components/AdminConfigurationShell.tsx +13 -7
- package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +225 -0
- package/apps/agents-server/src/app/admin/code-runners/page.tsx +14 -0
- package/apps/agents-server/src/app/admin/database/DatabaseAdminClient.tsx +38 -0
- package/apps/agents-server/src/app/admin/database/DatabaseAdminStudioSurface.tsx +42 -0
- package/apps/agents-server/src/app/admin/database/page.tsx +33 -0
- package/apps/agents-server/src/app/admin/environment/EnvironmentVariablesClient.tsx +259 -0
- package/apps/agents-server/src/app/admin/environment/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/logs/LogsClient.tsx +78 -0
- package/apps/agents-server/src/app/admin/logs/page.tsx +14 -0
- package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +64 -33
- package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
- package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +15 -2
- package/apps/agents-server/src/app/admin/servers/page.tsx +3 -3
- package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +12 -2
- package/apps/agents-server/src/app/api/admin/code-runners/route.ts +104 -0
- package/apps/agents-server/src/app/api/admin/database/studio/route.ts +113 -0
- package/apps/agents-server/src/app/api/admin/environment/route.ts +65 -0
- package/apps/agents-server/src/app/api/admin/logs/route.ts +24 -0
- package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +79 -3
- package/apps/agents-server/src/app/api/admin/servers/route.ts +36 -1
- package/apps/agents-server/src/app/layout.tsx +1 -0
- package/apps/agents-server/src/app/page.tsx +101 -1
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +27 -4
- package/apps/agents-server/src/database/$provideClientSql.ts +2 -6
- package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +273 -0
- package/apps/agents-server/src/database/resolvePostgresConnectionString.ts +26 -0
- package/apps/agents-server/src/database/sqlite/$provideAgentsServerSqliteDatabase.ts +83 -0
- package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +20 -71
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +5 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +5 -0
- package/apps/agents-server/src/languages/translations/english.yaml +5 -0
- package/apps/agents-server/src/tools/$provideServer.ts +27 -0
- package/apps/agents-server/src/utils/serverRegistry.ts +20 -1
- package/apps/agents-server/src/utils/session.ts +123 -2
- package/apps/agents-server/src/utils/vpsConfiguration.ts +550 -0
- package/esm/index.es.js +1 -1
- package/esm/index.es.js.map +1 -1
- package/esm/src/book-components/Chat/utils/renderMarkdown.test.d.ts +1 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +3 -1
- package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +9 -398
- package/src/book-components/Chat/utils/renderMarkdown.ts +323 -8
- package/src/other/templates/getTemplatesPipelineCollection.ts +712 -829
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +1 -1
- package/umd/index.umd.js.map +1 -1
- package/umd/src/book-components/Chat/utils/renderMarkdown.test.d.ts +1 -0
- package/umd/src/version.d.ts +1 -1
|
@@ -36,7 +36,14 @@ const nextConfig: NextConfig = {
|
|
|
36
36
|
eslint: {
|
|
37
37
|
ignoreDuringBuilds: isNextValidationIgnored,
|
|
38
38
|
},
|
|
39
|
-
serverExternalPackages: [
|
|
39
|
+
serverExternalPackages: [
|
|
40
|
+
'pg',
|
|
41
|
+
'better-sqlite3',
|
|
42
|
+
'@prisma/studio-core',
|
|
43
|
+
'@napi-rs/canvas',
|
|
44
|
+
'playwright',
|
|
45
|
+
'playwright-core',
|
|
46
|
+
],
|
|
40
47
|
typescript: {
|
|
41
48
|
ignoreBuildErrors: isNextValidationIgnored,
|
|
42
49
|
},
|
|
@@ -28,6 +28,8 @@ const APP_E2E_ENV = {
|
|
|
28
28
|
ADMIN_PASSWORD: 'e2e-admin-password',
|
|
29
29
|
NEXT_DIST_DIR: '.next-e2e',
|
|
30
30
|
NEXT_PUBLIC_SITE_URL: APP_URL,
|
|
31
|
+
PTBK_AGENTS_SERVER_DATABASE: 'supabase',
|
|
32
|
+
PTBK_AGENTS_SERVER_SQLITE_PATH: '',
|
|
31
33
|
SUPABASE_TABLE_PREFIX: '',
|
|
32
34
|
POSTGRES_URL: '',
|
|
33
35
|
DATABASE_URL: '',
|
|
@@ -6,7 +6,7 @@ import Link from 'next/link';
|
|
|
6
6
|
*
|
|
7
7
|
* @private admin configuration UI helper
|
|
8
8
|
*/
|
|
9
|
-
type AdminConfigurationPage = 'metadata' | 'limits';
|
|
9
|
+
type AdminConfigurationPage = 'environment' | 'metadata' | 'limits';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* One shared navigation item rendered in the configuration shell.
|
|
@@ -15,15 +15,21 @@ type AdminConfigurationPage = 'metadata' | 'limits';
|
|
|
15
15
|
*/
|
|
16
16
|
const ADMIN_CONFIGURATION_NAVIGATION_ITEMS: ReadonlyArray<{
|
|
17
17
|
readonly id: AdminConfigurationPage;
|
|
18
|
-
readonly href: '/admin/metadata' | '/admin/limits';
|
|
18
|
+
readonly href: '/admin/environment' | '/admin/metadata' | '/admin/limits';
|
|
19
19
|
readonly label: string;
|
|
20
20
|
readonly description: string;
|
|
21
21
|
}> = [
|
|
22
|
+
{
|
|
23
|
+
id: 'environment',
|
|
24
|
+
href: '/admin/environment',
|
|
25
|
+
label: 'Environment variables',
|
|
26
|
+
description: 'VPS-wide .env values with secrets masked in the browser.',
|
|
27
|
+
},
|
|
22
28
|
{
|
|
23
29
|
id: 'metadata',
|
|
24
30
|
href: '/admin/metadata',
|
|
25
31
|
label: 'Metadata',
|
|
26
|
-
description: '
|
|
32
|
+
description: 'Domain-specific feature flags, text settings, and compatibility keys.',
|
|
27
33
|
},
|
|
28
34
|
{
|
|
29
35
|
id: 'limits',
|
|
@@ -44,7 +50,7 @@ type AdminConfigurationShellProps = {
|
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
/**
|
|
47
|
-
* Shared page shell used by the Metadata and Limits admin pages.
|
|
53
|
+
* Shared page shell used by the Environment variables, Metadata, and Limits admin pages.
|
|
48
54
|
*/
|
|
49
55
|
export function AdminConfigurationShell({ activePage, children }: AdminConfigurationShellProps) {
|
|
50
56
|
return (
|
|
@@ -53,11 +59,11 @@ export function AdminConfigurationShell({ activePage, children }: AdminConfigura
|
|
|
53
59
|
<div>
|
|
54
60
|
<h1 className="text-3xl font-light text-gray-900">Server configuration</h1>
|
|
55
61
|
<p className="mt-1 max-w-3xl text-sm text-gray-500">
|
|
56
|
-
Manage
|
|
57
|
-
|
|
62
|
+
Manage VPS-wide environment variables, domain-specific metadata, and dedicated operational
|
|
63
|
+
limits from one configuration area.
|
|
58
64
|
</p>
|
|
59
65
|
</div>
|
|
60
|
-
<div className="grid gap-3 md:grid-cols-
|
|
66
|
+
<div className="grid gap-3 md:grid-cols-3">
|
|
61
67
|
{ADMIN_CONFIGURATION_NAVIGATION_ITEMS.map((item) => {
|
|
62
68
|
const isActive = item.id === activePage;
|
|
63
69
|
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Loader2, Save, ServerCog } from 'lucide-react';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { Card } from '../../../components/Homepage/Card';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Code-runner API response.
|
|
9
|
+
*/
|
|
10
|
+
type CodeRunnersResponse = {
|
|
11
|
+
readonly agent?: string;
|
|
12
|
+
readonly model?: string;
|
|
13
|
+
readonly thinkingLevel?: string;
|
|
14
|
+
readonly status?: string;
|
|
15
|
+
readonly applyResult?: {
|
|
16
|
+
readonly isAvailable: boolean;
|
|
17
|
+
readonly output: string;
|
|
18
|
+
} | null;
|
|
19
|
+
readonly error?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Supported runner options shown by the standalone UI.
|
|
24
|
+
*/
|
|
25
|
+
const RUNNER_OPTIONS = [
|
|
26
|
+
{ value: 'github-copilot', label: 'GitHub Copilot' },
|
|
27
|
+
{ value: 'openai-codex', label: 'OpenAI Codex' },
|
|
28
|
+
{ value: 'claude-code', label: 'Claude Code' },
|
|
29
|
+
{ value: 'opencode', label: 'Opencode' },
|
|
30
|
+
{ value: 'gemini', label: 'Gemini' },
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Shared input styling for code-runner controls.
|
|
35
|
+
*/
|
|
36
|
+
const INPUT_CLASS_NAME =
|
|
37
|
+
'w-full rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:bg-gray-50 disabled:text-gray-500';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Client UI for configuring the local coding runner used by durable chats.
|
|
41
|
+
*/
|
|
42
|
+
export function CodeRunnersClient() {
|
|
43
|
+
const [agent, setAgent] = useState('github-copilot');
|
|
44
|
+
const [model, setModel] = useState('gpt-5.4');
|
|
45
|
+
const [thinkingLevel, setThinkingLevel] = useState('xhigh');
|
|
46
|
+
const [status, setStatus] = useState('');
|
|
47
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
48
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
49
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
50
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
51
|
+
const [applyOutput, setApplyOutput] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
void loadConfiguration();
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Loads current code-runner settings.
|
|
59
|
+
*/
|
|
60
|
+
async function loadConfiguration(): Promise<void> {
|
|
61
|
+
try {
|
|
62
|
+
setIsLoading(true);
|
|
63
|
+
setErrorMessage(null);
|
|
64
|
+
const response = await fetch('/api/admin/code-runners', { cache: 'no-store' });
|
|
65
|
+
const payload = (await response.json()) as CodeRunnersResponse;
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
throw new Error(payload.error || 'Failed to load code-runner configuration.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setAgent(payload.agent || 'github-copilot');
|
|
72
|
+
setModel(payload.model || 'gpt-5.4');
|
|
73
|
+
setThinkingLevel(payload.thinkingLevel || 'xhigh');
|
|
74
|
+
setStatus(payload.status || '');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
setErrorMessage(error instanceof Error ? error.message : 'Failed to load code-runner configuration.');
|
|
77
|
+
} finally {
|
|
78
|
+
setIsLoading(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Saves code-runner settings into `.env`.
|
|
84
|
+
*/
|
|
85
|
+
async function saveConfiguration(applyRuntimeConfiguration: boolean): Promise<void> {
|
|
86
|
+
try {
|
|
87
|
+
setIsSaving(true);
|
|
88
|
+
setErrorMessage(null);
|
|
89
|
+
setSuccessMessage(null);
|
|
90
|
+
setApplyOutput(null);
|
|
91
|
+
|
|
92
|
+
const response = await fetch('/api/admin/code-runners', {
|
|
93
|
+
method: 'PATCH',
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({ agent, model, thinkingLevel, applyRuntimeConfiguration }),
|
|
98
|
+
});
|
|
99
|
+
const payload = (await response.json()) as CodeRunnersResponse;
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(payload.error || 'Failed to save code-runner configuration.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
setAgent(payload.agent || agent);
|
|
106
|
+
setModel(payload.model || model);
|
|
107
|
+
setThinkingLevel(payload.thinkingLevel || thinkingLevel);
|
|
108
|
+
setStatus(payload.status || '');
|
|
109
|
+
setApplyOutput(payload.applyResult?.output || null);
|
|
110
|
+
setSuccessMessage(
|
|
111
|
+
applyRuntimeConfiguration
|
|
112
|
+
? 'Code-runner configuration was saved and applied.'
|
|
113
|
+
: 'Code-runner configuration was saved.',
|
|
114
|
+
);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
setErrorMessage(error instanceof Error ? error.message : 'Failed to save code-runner configuration.');
|
|
117
|
+
} finally {
|
|
118
|
+
setIsSaving(false);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div className="container mx-auto space-y-6 px-4 py-8">
|
|
124
|
+
<div className="mt-20">
|
|
125
|
+
<h1 className="text-3xl font-light text-gray-900">Code runners</h1>
|
|
126
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
127
|
+
Configure the local runner used by the standalone Agents Server durable chat worker.
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{errorMessage && (
|
|
132
|
+
<div className="rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">
|
|
133
|
+
{errorMessage}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
{successMessage && (
|
|
137
|
+
<div className="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700">
|
|
138
|
+
{successMessage}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
<Card className="hover:border-gray-200 hover:shadow-md">
|
|
143
|
+
<div className="grid gap-5 md:grid-cols-3">
|
|
144
|
+
<label className="space-y-2">
|
|
145
|
+
<span className="text-sm font-semibold text-slate-700">Runner</span>
|
|
146
|
+
<select
|
|
147
|
+
value={agent}
|
|
148
|
+
onChange={(event) => setAgent(event.target.value)}
|
|
149
|
+
disabled={isLoading || isSaving}
|
|
150
|
+
className={INPUT_CLASS_NAME}
|
|
151
|
+
>
|
|
152
|
+
{RUNNER_OPTIONS.map((option) => (
|
|
153
|
+
<option key={option.value} value={option.value}>
|
|
154
|
+
{option.label}
|
|
155
|
+
</option>
|
|
156
|
+
))}
|
|
157
|
+
</select>
|
|
158
|
+
</label>
|
|
159
|
+
<label className="space-y-2">
|
|
160
|
+
<span className="text-sm font-semibold text-slate-700">Model</span>
|
|
161
|
+
<input
|
|
162
|
+
type="text"
|
|
163
|
+
value={model}
|
|
164
|
+
onChange={(event) => setModel(event.target.value)}
|
|
165
|
+
disabled={isLoading || isSaving}
|
|
166
|
+
className={INPUT_CLASS_NAME}
|
|
167
|
+
/>
|
|
168
|
+
</label>
|
|
169
|
+
<label className="space-y-2">
|
|
170
|
+
<span className="text-sm font-semibold text-slate-700">Thinking level</span>
|
|
171
|
+
<input
|
|
172
|
+
type="text"
|
|
173
|
+
value={thinkingLevel}
|
|
174
|
+
onChange={(event) => setThinkingLevel(event.target.value)}
|
|
175
|
+
disabled={isLoading || isSaving}
|
|
176
|
+
className={INPUT_CLASS_NAME}
|
|
177
|
+
/>
|
|
178
|
+
</label>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<div className="mt-6 flex flex-wrap items-center gap-3">
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
onClick={() => void saveConfiguration(false)}
|
|
185
|
+
disabled={isLoading || isSaving}
|
|
186
|
+
className="inline-flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
|
|
187
|
+
>
|
|
188
|
+
{isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
|
189
|
+
Save runner
|
|
190
|
+
</button>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={() => void saveConfiguration(true)}
|
|
194
|
+
disabled={isLoading || isSaving}
|
|
195
|
+
className="inline-flex items-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-60"
|
|
196
|
+
>
|
|
197
|
+
{isSaving ? <Loader2 className="h-4 w-4 animate-spin" /> : <ServerCog className="h-4 w-4" />}
|
|
198
|
+
Save and apply runner
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
</Card>
|
|
202
|
+
|
|
203
|
+
{applyOutput ? (
|
|
204
|
+
<pre className="max-h-72 overflow-auto rounded-xl border border-slate-200 bg-slate-950 p-4 text-xs text-slate-100">
|
|
205
|
+
{applyOutput}
|
|
206
|
+
</pre>
|
|
207
|
+
) : null}
|
|
208
|
+
|
|
209
|
+
<Card className="hover:border-gray-200 hover:shadow-md">
|
|
210
|
+
<div className="space-y-3">
|
|
211
|
+
<h2 className="text-lg font-semibold text-slate-900">Authentication</h2>
|
|
212
|
+
<p className="text-sm text-slate-600">
|
|
213
|
+
GitHub Copilot still requires an interactive CLI login and project trust setup on the VPS
|
|
214
|
+
terminal. Use <span className="font-mono">sudo -u $USER copilot</span>, run{' '}
|
|
215
|
+
<span className="font-mono">/login</span> when prompted, trust the install directory, then
|
|
216
|
+
restart the pm2 process.
|
|
217
|
+
</p>
|
|
218
|
+
<pre className="max-h-64 overflow-auto rounded-xl border border-slate-200 bg-slate-950 p-4 text-xs text-slate-100">
|
|
219
|
+
{status || 'Runner status was not available.'}
|
|
220
|
+
</pre>
|
|
221
|
+
</div>
|
|
222
|
+
</Card>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
+
import { isUserGlobalAdmin } from '../../../utils/isUserGlobalAdmin';
|
|
3
|
+
import { CodeRunnersClient } from './CodeRunnersClient';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Super-admin page for configuring standalone code runners.
|
|
7
|
+
*/
|
|
8
|
+
export default async function CodeRunnersPage() {
|
|
9
|
+
if (!(await isUserGlobalAdmin())) {
|
|
10
|
+
return <ForbiddenPage />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return <CodeRunnersClient />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
import type { AgentsServerDatabaseMode } from '../../../database/agentsServerDatabaseMode';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props consumed by the lazy Embedded Prisma Studio client.
|
|
8
|
+
*
|
|
9
|
+
* @private route component props of DatabaseAdminPage
|
|
10
|
+
*/
|
|
11
|
+
type DatabaseAdminClientProps = {
|
|
12
|
+
readonly databaseMode: AgentsServerDatabaseMode;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Client-only Embedded Prisma Studio surface.
|
|
17
|
+
*/
|
|
18
|
+
const DatabaseAdminStudioSurface = dynamic(
|
|
19
|
+
() => import('./DatabaseAdminStudioSurface').then((module) => module.DatabaseAdminStudioSurface),
|
|
20
|
+
{
|
|
21
|
+
ssr: false,
|
|
22
|
+
loading: () => (
|
|
23
|
+
<div className="flex h-full items-center justify-center text-sm text-gray-500">Loading database...</div>
|
|
24
|
+
),
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders the Embedded Prisma Studio client after hydration.
|
|
30
|
+
*
|
|
31
|
+
* @param props - Active database mode.
|
|
32
|
+
* @returns Database admin client.
|
|
33
|
+
*
|
|
34
|
+
* @private route component of DatabaseAdminPage
|
|
35
|
+
*/
|
|
36
|
+
export function DatabaseAdminClient({ databaseMode }: DatabaseAdminClientProps) {
|
|
37
|
+
return <DatabaseAdminStudioSurface databaseMode={databaseMode} />;
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createStudioBFFClient } from '@prisma/studio-core/data/bff';
|
|
4
|
+
import { createPostgresAdapter } from '@prisma/studio-core/data/postgres-core';
|
|
5
|
+
import { createSQLiteAdapter } from '@prisma/studio-core/data/sqlite-core';
|
|
6
|
+
import { Studio } from '@prisma/studio-core/ui';
|
|
7
|
+
import { useMemo } from 'react';
|
|
8
|
+
import type { AgentsServerDatabaseMode } from '../../../database/agentsServerDatabaseMode';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Backend endpoint used by Embedded Prisma Studio.
|
|
12
|
+
*/
|
|
13
|
+
const DATABASE_ADMIN_STUDIO_ENDPOINT = '/api/admin/database/studio';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Props consumed by the hydrated Embedded Prisma Studio surface.
|
|
17
|
+
*
|
|
18
|
+
* @private route component props of DatabaseAdminPage
|
|
19
|
+
*/
|
|
20
|
+
type DatabaseAdminStudioSurfaceProps = {
|
|
21
|
+
readonly databaseMode: AgentsServerDatabaseMode;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Renders Prisma Studio with an adapter matching the configured database backend.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Active database mode.
|
|
28
|
+
* @returns Embedded Prisma Studio surface.
|
|
29
|
+
*
|
|
30
|
+
* @private route component of DatabaseAdminPage
|
|
31
|
+
*/
|
|
32
|
+
export function DatabaseAdminStudioSurface({ databaseMode }: DatabaseAdminStudioSurfaceProps) {
|
|
33
|
+
const adapter = useMemo(() => {
|
|
34
|
+
const executor = createStudioBFFClient({
|
|
35
|
+
url: DATABASE_ADMIN_STUDIO_ENDPOINT,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return databaseMode === 'sqlite' ? createSQLiteAdapter({ executor }) : createPostgresAdapter({ executor });
|
|
39
|
+
}, [databaseMode]);
|
|
40
|
+
|
|
41
|
+
return <Studio adapter={adapter} />;
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
+
import { resolveAgentsServerDatabaseMode } from '../../../database/agentsServerDatabaseMode';
|
|
3
|
+
import { isUserGlobalAdmin } from '../../../utils/isUserGlobalAdmin';
|
|
4
|
+
import { DatabaseAdminClient } from './DatabaseAdminClient';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Super-admin page exposing raw database access through Embedded Prisma Studio.
|
|
8
|
+
*/
|
|
9
|
+
export default async function DatabaseAdminPage() {
|
|
10
|
+
if (!(await isUserGlobalAdmin())) {
|
|
11
|
+
return <ForbiddenPage />;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const databaseMode = resolveAgentsServerDatabaseMode();
|
|
15
|
+
const databaseModeLabel = databaseMode === 'sqlite' ? 'SQLite' : 'Supabase';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex h-[calc(100vh-60px)] flex-col overflow-hidden bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100">
|
|
19
|
+
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-gray-200 px-5 py-3 dark:border-gray-800">
|
|
20
|
+
<div>
|
|
21
|
+
<p className="text-xs uppercase tracking-[0.25em] text-gray-400">Super Admin</p>
|
|
22
|
+
<h1 className="text-xl font-semibold">Database</h1>
|
|
23
|
+
</div>
|
|
24
|
+
<span className="rounded border border-gray-200 px-2 py-1 text-xs font-medium text-gray-600 dark:border-gray-700 dark:text-gray-300">
|
|
25
|
+
{databaseModeLabel}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="min-h-0 flex-1">
|
|
29
|
+
<DatabaseAdminClient databaseMode={databaseMode} />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|