@promptbook/cli 0.112.0-93 → 0.112.0-95
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/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/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/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/page.tsx +101 -1
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +23 -4
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +4 -0
- package/apps/agents-server/src/languages/translations/czech.yaml +4 -0
- package/apps/agents-server/src/languages/translations/english.yaml +4 -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 +2 -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 +683 -879
- 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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Plus } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { useSearchParams } from 'next/navigation';
|
|
5
|
+
import { useCallback, useEffect } from 'react';
|
|
5
6
|
import { Card } from '../../../components/Homepage/Card';
|
|
6
7
|
import { Section } from '../../../components/Homepage/Section';
|
|
7
8
|
import { useUnsavedChangesGuard } from '../../../components/utils/useUnsavedChangesGuard';
|
|
@@ -25,7 +26,9 @@ const PRIMARY_BUTTON_CLASS_NAME =
|
|
|
25
26
|
* @private route component of AdminServersPage
|
|
26
27
|
*/
|
|
27
28
|
export function ServersClient() {
|
|
29
|
+
const searchParams = useSearchParams();
|
|
28
30
|
const {
|
|
31
|
+
canEdit,
|
|
29
32
|
currentServer,
|
|
30
33
|
currentServerId,
|
|
31
34
|
deleteCurrentServer,
|
|
@@ -75,16 +78,39 @@ export function ServersClient() {
|
|
|
75
78
|
});
|
|
76
79
|
}, [allowNextNavigation, deleteCurrentServer]);
|
|
77
80
|
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (
|
|
83
|
+
canEdit &&
|
|
84
|
+
!loading &&
|
|
85
|
+
servers.length === 0 &&
|
|
86
|
+
searchParams?.get('setup') === '1' &&
|
|
87
|
+
!createServerWizard.isDialogOpen
|
|
88
|
+
) {
|
|
89
|
+
createServerWizard.openDialog();
|
|
90
|
+
}
|
|
91
|
+
}, [canEdit, createServerWizard, loading, searchParams, servers.length]);
|
|
92
|
+
|
|
78
93
|
return (
|
|
79
94
|
<div className="container mx-auto space-y-8 px-4 py-8">
|
|
80
95
|
<div className="mt-20 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
81
96
|
<h1 className="text-3xl font-light text-gray-900">Servers</h1>
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
{canEdit ? (
|
|
98
|
+
<button type="button" onClick={createServerWizard.openDialog} className={PRIMARY_BUTTON_CLASS_NAME}>
|
|
99
|
+
<Plus className="h-4 w-4" />
|
|
100
|
+
Create new server
|
|
101
|
+
</button>
|
|
102
|
+
) : null}
|
|
86
103
|
</div>
|
|
87
104
|
|
|
105
|
+
{!canEdit ? (
|
|
106
|
+
<Card className="border-amber-200 bg-amber-50 hover:border-amber-200 hover:shadow-md">
|
|
107
|
+
<p className="text-sm text-amber-800">
|
|
108
|
+
You can view servers as an administrator. Editing domains, migrations, and deletion is restricted
|
|
109
|
+
to the super admin authenticated with <span className="font-mono">ADMIN_PASSWORD</span>.
|
|
110
|
+
</p>
|
|
111
|
+
</Card>
|
|
112
|
+
) : null}
|
|
113
|
+
|
|
88
114
|
{error ? (
|
|
89
115
|
<Card className="border-red-200 bg-red-50 hover:border-red-200 hover:shadow-md">
|
|
90
116
|
<p className="text-sm text-red-700">{error}</p>
|
|
@@ -95,6 +121,7 @@ export function ServersClient() {
|
|
|
95
121
|
<Card className="hover:border-gray-200 hover:shadow-md">
|
|
96
122
|
<ServersRegistryTable
|
|
97
123
|
currentServerId={currentServerId}
|
|
124
|
+
canEdit={canEdit}
|
|
98
125
|
loading={loading}
|
|
99
126
|
migratingServerId={migratingServerId}
|
|
100
127
|
navigatingServerId={navigatingServerId}
|
|
@@ -110,35 +137,39 @@ export function ServersClient() {
|
|
|
110
137
|
</Card>
|
|
111
138
|
</Section>
|
|
112
139
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
{canEdit ? (
|
|
141
|
+
<CreateServerDialog
|
|
142
|
+
addAdditionalUser={createServerWizard.addAdditionalUser}
|
|
143
|
+
derivedWizardTablePrefix={createServerWizard.derivedWizardTablePrefix}
|
|
144
|
+
handleCreateServer={createServerWizard.handleCreateServer}
|
|
145
|
+
handleIconUpload={createServerWizard.handleIconUpload}
|
|
146
|
+
handleWizardBack={createServerWizard.handleWizardBack}
|
|
147
|
+
handleWizardNext={createServerWizard.handleWizardNext}
|
|
148
|
+
handleWizardStepSelection={createServerWizard.handleWizardStepSelection}
|
|
149
|
+
iconInputRef={createServerWizard.iconInputRef}
|
|
150
|
+
isCreatingServer={createServerWizard.isCreatingServer}
|
|
151
|
+
isOpen={createServerWizard.isDialogOpen}
|
|
152
|
+
isUploadingIcon={createServerWizard.isUploadingIcon}
|
|
153
|
+
removeAdditionalUser={createServerWizard.removeAdditionalUser}
|
|
154
|
+
requestClose={createServerWizard.requestClose}
|
|
155
|
+
resetWizard={createServerWizard.resetWizard}
|
|
156
|
+
updateAdditionalUser={createServerWizard.updateAdditionalUser}
|
|
157
|
+
updateAdminUser={createServerWizard.updateAdminUser}
|
|
158
|
+
updateInitialSetting={createServerWizard.updateInitialSetting}
|
|
159
|
+
updateWizardField={createServerWizard.updateWizardField}
|
|
160
|
+
wizardError={createServerWizard.wizardError}
|
|
161
|
+
wizardState={createServerWizard.wizardState}
|
|
162
|
+
wizardStep={createServerWizard.wizardStep}
|
|
163
|
+
/>
|
|
164
|
+
) : null}
|
|
136
165
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
166
|
+
{canEdit ? (
|
|
167
|
+
<DeleteCurrentServerSection
|
|
168
|
+
currentServer={currentServer}
|
|
169
|
+
deletingServerId={deletingServerId}
|
|
170
|
+
onDeleteCurrentServer={handleDeleteCurrentServer}
|
|
171
|
+
/>
|
|
172
|
+
) : null}
|
|
142
173
|
</div>
|
|
143
174
|
);
|
|
144
175
|
}
|
|
@@ -17,6 +17,11 @@ type ManagedServersReadResponse = {
|
|
|
17
17
|
*/
|
|
18
18
|
readonly currentServerId: number | null;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Whether the current viewer can mutate server rows.
|
|
22
|
+
*/
|
|
23
|
+
readonly canEdit: boolean;
|
|
24
|
+
|
|
20
25
|
/**
|
|
21
26
|
* Optional failure message returned by the API.
|
|
22
27
|
*/
|
|
@@ -40,6 +40,11 @@ const PRIMARY_BUTTON_CLASS_NAME =
|
|
|
40
40
|
* @private function of <ServersClient/>
|
|
41
41
|
*/
|
|
42
42
|
type ServersRegistryTableProps = {
|
|
43
|
+
/**
|
|
44
|
+
* Whether the viewer can edit server rows.
|
|
45
|
+
*/
|
|
46
|
+
readonly canEdit: boolean;
|
|
47
|
+
|
|
43
48
|
/**
|
|
44
49
|
* Identifier of the server resolved from the current request domain.
|
|
45
50
|
*/
|
|
@@ -107,6 +112,7 @@ type ServersRegistryTableProps = {
|
|
|
107
112
|
* @private function of <ServersClient/>
|
|
108
113
|
*/
|
|
109
114
|
type ServersRegistryTableRowProps = {
|
|
115
|
+
readonly canEdit: boolean;
|
|
110
116
|
readonly currentServerId: number | null;
|
|
111
117
|
readonly draft: ServerDraft | undefined;
|
|
112
118
|
readonly isDirty: boolean;
|
|
@@ -163,6 +169,7 @@ function ServerStatusBadge(props: { readonly label: string; readonly tone: 'blue
|
|
|
163
169
|
function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
164
170
|
const {
|
|
165
171
|
currentServerId,
|
|
172
|
+
canEdit,
|
|
166
173
|
draft,
|
|
167
174
|
isDirty,
|
|
168
175
|
isMigrating,
|
|
@@ -184,6 +191,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
184
191
|
value={draft?.name || ''}
|
|
185
192
|
onChange={(event) => onUpdateServerDraft(server.id, 'name', event.target.value)}
|
|
186
193
|
className={INPUT_CLASS_NAME}
|
|
194
|
+
disabled={!canEdit}
|
|
187
195
|
aria-label={`Server name for ${server.name}`}
|
|
188
196
|
/>
|
|
189
197
|
</td>
|
|
@@ -194,6 +202,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
194
202
|
onUpdateServerDraft(server.id, 'environment', event.target.value as ManagedServerEnvironment)
|
|
195
203
|
}
|
|
196
204
|
className={INPUT_CLASS_NAME}
|
|
205
|
+
disabled={!canEdit}
|
|
197
206
|
aria-label={`Environment for ${server.name}`}
|
|
198
207
|
>
|
|
199
208
|
{MANAGED_SERVER_ENVIRONMENT_OPTIONS.map((environment) => (
|
|
@@ -209,6 +218,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
209
218
|
value={draft?.domain || ''}
|
|
210
219
|
onChange={(event) => onUpdateServerDraft(server.id, 'domain', event.target.value)}
|
|
211
220
|
className={INPUT_CLASS_NAME}
|
|
221
|
+
disabled={!canEdit}
|
|
212
222
|
aria-label={`Domain for ${server.name}`}
|
|
213
223
|
/>
|
|
214
224
|
</td>
|
|
@@ -218,6 +228,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
218
228
|
value={draft?.tablePrefix || ''}
|
|
219
229
|
onChange={(event) => onUpdateServerDraft(server.id, 'tablePrefix', event.target.value)}
|
|
220
230
|
className={`${INPUT_CLASS_NAME} font-mono`}
|
|
231
|
+
disabled={!canEdit}
|
|
221
232
|
aria-label={`Table prefix for ${server.name}`}
|
|
222
233
|
/>
|
|
223
234
|
</td>
|
|
@@ -239,7 +250,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
239
250
|
<button
|
|
240
251
|
type="button"
|
|
241
252
|
onClick={() => void onSaveServer(server.id)}
|
|
242
|
-
disabled={!isDirty || isSaving}
|
|
253
|
+
disabled={!canEdit || !isDirty || isSaving}
|
|
243
254
|
className={`${PRIMARY_BUTTON_CLASS_NAME} px-2 py-1 text-xs`}
|
|
244
255
|
>
|
|
245
256
|
{isSaving ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Save className="h-3.5 w-3.5" />}
|
|
@@ -248,7 +259,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
248
259
|
<button
|
|
249
260
|
type="button"
|
|
250
261
|
onClick={() => void onMigrateServer(server.id)}
|
|
251
|
-
disabled={isMigrating}
|
|
262
|
+
disabled={!canEdit || isMigrating}
|
|
252
263
|
className={`${SECONDARY_BUTTON_CLASS_NAME} px-2 py-1 text-xs`}
|
|
253
264
|
>
|
|
254
265
|
{isMigrating ? (
|
|
@@ -288,6 +299,7 @@ function ServersRegistryTableRow(props: ServersRegistryTableRowProps) {
|
|
|
288
299
|
export function ServersRegistryTable(props: ServersRegistryTableProps) {
|
|
289
300
|
const {
|
|
290
301
|
currentServerId,
|
|
302
|
+
canEdit,
|
|
291
303
|
isServerDraftDirty,
|
|
292
304
|
loading,
|
|
293
305
|
migratingServerId,
|
|
@@ -344,6 +356,7 @@ export function ServersRegistryTable(props: ServersRegistryTableProps) {
|
|
|
344
356
|
<ServersRegistryTableRow
|
|
345
357
|
key={server.id}
|
|
346
358
|
currentServerId={currentServerId}
|
|
359
|
+
canEdit={canEdit}
|
|
347
360
|
draft={serverDrafts[server.id]}
|
|
348
361
|
isDirty={isServerDraftDirty(server)}
|
|
349
362
|
isMigrating={migratingServerId === server.id}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
-
import {
|
|
2
|
+
import { isUserAdmin } from '../../../utils/isUserAdmin';
|
|
3
3
|
import { ServersClient } from './ServersClient';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Admin page for viewing same-instance registered servers.
|
|
7
7
|
*/
|
|
8
8
|
export default async function AdminServersPage() {
|
|
9
|
-
if (!(await
|
|
9
|
+
if (!(await isUserAdmin())) {
|
|
10
10
|
return <ForbiddenPage />;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -138,6 +138,11 @@ type DeleteCurrentServerConfirmationResult = {
|
|
|
138
138
|
* @private function of <ServersClient/>
|
|
139
139
|
*/
|
|
140
140
|
type UseServersRegistryStateResult = {
|
|
141
|
+
/**
|
|
142
|
+
* Whether the current viewer can mutate server rows.
|
|
143
|
+
*/
|
|
144
|
+
readonly canEdit: boolean;
|
|
145
|
+
|
|
141
146
|
/**
|
|
142
147
|
* Server resolved from the current request domain.
|
|
143
148
|
*/
|
|
@@ -429,12 +434,13 @@ function useServersRegistryDraftState(servers: ReadonlyArray<ManagedServerRow>)
|
|
|
429
434
|
*/
|
|
430
435
|
function useServersRegistryReloadAction(options: {
|
|
431
436
|
readonly replaceServerDrafts: (servers: ReadonlyArray<ManagedServerRow>) => void;
|
|
437
|
+
readonly setCanEdit: (canEdit: boolean) => void;
|
|
432
438
|
readonly setCurrentServerId: (currentServerId: number | null) => void;
|
|
433
439
|
readonly setError: (error: string | null) => void;
|
|
434
440
|
readonly setLoading: (isLoading: boolean) => void;
|
|
435
441
|
readonly setServers: (servers: ManagedServerRow[]) => void;
|
|
436
442
|
}) {
|
|
437
|
-
const { replaceServerDrafts, setCurrentServerId, setError, setLoading, setServers } = options;
|
|
443
|
+
const { replaceServerDrafts, setCanEdit, setCurrentServerId, setError, setLoading, setServers } = options;
|
|
438
444
|
|
|
439
445
|
return useCallback(async () => {
|
|
440
446
|
setLoading(true);
|
|
@@ -444,13 +450,14 @@ function useServersRegistryReloadAction(options: {
|
|
|
444
450
|
const payload = await ServersRegistryApi.fetchServers();
|
|
445
451
|
setServers([...payload.servers]);
|
|
446
452
|
setCurrentServerId(payload.currentServerId);
|
|
453
|
+
setCanEdit(payload.canEdit);
|
|
447
454
|
replaceServerDrafts(payload.servers);
|
|
448
455
|
} catch (loadError) {
|
|
449
456
|
setError(resolveServersRegistryActionErrorMessage(loadError, 'Failed to load servers.'));
|
|
450
457
|
} finally {
|
|
451
458
|
setLoading(false);
|
|
452
459
|
}
|
|
453
|
-
}, [replaceServerDrafts, setCurrentServerId, setError, setLoading, setServers]);
|
|
460
|
+
}, [replaceServerDrafts, setCanEdit, setCurrentServerId, setError, setLoading, setServers]);
|
|
454
461
|
}
|
|
455
462
|
|
|
456
463
|
/**
|
|
@@ -618,6 +625,7 @@ function useDeleteCurrentServerAction(options: {
|
|
|
618
625
|
*/
|
|
619
626
|
export function useServersRegistryState(): UseServersRegistryStateResult {
|
|
620
627
|
const [servers, setServers] = useState<ManagedServerRow[]>([]);
|
|
628
|
+
const [canEdit, setCanEdit] = useState(false);
|
|
621
629
|
const [loading, setLoading] = useState(true);
|
|
622
630
|
const [error, setError] = useState<string | null>(null);
|
|
623
631
|
const [currentServerId, setCurrentServerId] = useState<number | null>(null);
|
|
@@ -635,6 +643,7 @@ export function useServersRegistryState(): UseServersRegistryStateResult {
|
|
|
635
643
|
|
|
636
644
|
const reloadServers = useServersRegistryReloadAction({
|
|
637
645
|
replaceServerDrafts,
|
|
646
|
+
setCanEdit,
|
|
638
647
|
setCurrentServerId,
|
|
639
648
|
setError,
|
|
640
649
|
setLoading,
|
|
@@ -671,6 +680,7 @@ export function useServersRegistryState(): UseServersRegistryStateResult {
|
|
|
671
680
|
});
|
|
672
681
|
|
|
673
682
|
return {
|
|
683
|
+
canEdit,
|
|
674
684
|
currentServer,
|
|
675
685
|
currentServerId,
|
|
676
686
|
deletingServerId,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
|
|
5
|
+
import {
|
|
6
|
+
applyVpsCodeRunnerConfiguration,
|
|
7
|
+
listVpsEnvironmentVariables,
|
|
8
|
+
updateVpsEnvironmentVariables,
|
|
9
|
+
} from '@/src/utils/vpsConfiguration';
|
|
10
|
+
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads configured code-runner settings from the editable VPS environment.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET() {
|
|
17
|
+
if (!(await isUserGlobalAdmin())) {
|
|
18
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const snapshot = await listVpsEnvironmentVariables();
|
|
23
|
+
const environmentByKey = Object.fromEntries(
|
|
24
|
+
snapshot.variables.map((variable) => [variable.key, variable.value]),
|
|
25
|
+
) as Record<string, string>;
|
|
26
|
+
|
|
27
|
+
return NextResponse.json({
|
|
28
|
+
agent: environmentByKey.PTBK_AGENT || process.env.PTBK_AGENT || 'github-copilot',
|
|
29
|
+
model: environmentByKey.PTBK_MODEL || process.env.PTBK_MODEL || 'gpt-5.4',
|
|
30
|
+
thinkingLevel: environmentByKey.PTBK_THINKING_LEVEL || process.env.PTBK_THINKING_LEVEL || 'xhigh',
|
|
31
|
+
status: await resolveRunnerStatus(environmentByKey.PTBK_AGENT || process.env.PTBK_AGENT || 'github-copilot'),
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return NextResponse.json(
|
|
35
|
+
{ error: error instanceof Error ? error.message : 'Failed to load code-runner configuration.' },
|
|
36
|
+
{ status: 500 },
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Updates code-runner environment variables.
|
|
43
|
+
*/
|
|
44
|
+
export async function PATCH(request: Request) {
|
|
45
|
+
if (!(await isUserGlobalAdmin())) {
|
|
46
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const body = (await request.json().catch(() => null)) as
|
|
51
|
+
| {
|
|
52
|
+
readonly agent?: string;
|
|
53
|
+
readonly model?: string;
|
|
54
|
+
readonly thinkingLevel?: string;
|
|
55
|
+
readonly applyRuntimeConfiguration?: boolean;
|
|
56
|
+
}
|
|
57
|
+
| null;
|
|
58
|
+
|
|
59
|
+
if (!body) {
|
|
60
|
+
return NextResponse.json({ error: 'Code-runner payload is required.' }, { status: 400 });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await updateVpsEnvironmentVariables({
|
|
64
|
+
PTBK_AGENT: body.agent || '',
|
|
65
|
+
PTBK_MODEL: body.model || '',
|
|
66
|
+
PTBK_THINKING_LEVEL: body.thinkingLevel || '',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const response = await GET();
|
|
70
|
+
const payload = (await response.json()) as Record<string, unknown>;
|
|
71
|
+
|
|
72
|
+
return NextResponse.json({
|
|
73
|
+
...payload,
|
|
74
|
+
applyResult: body.applyRuntimeConfiguration ? await applyVpsCodeRunnerConfiguration() : null,
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return NextResponse.json(
|
|
78
|
+
{ error: error instanceof Error ? error.message : 'Failed to update code-runner configuration.' },
|
|
79
|
+
{ status: 500 },
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolves a short runner-authentication status for the configured runner.
|
|
86
|
+
*
|
|
87
|
+
* @param agent - Runner id.
|
|
88
|
+
* @returns Human-readable status.
|
|
89
|
+
*/
|
|
90
|
+
async function resolveRunnerStatus(agent: string): Promise<string> {
|
|
91
|
+
if (agent !== 'github-copilot') {
|
|
92
|
+
return 'Status check is currently available for GitHub Copilot CLI only.';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const { stdout, stderr } = await execFileAsync('copilot', ['auth', 'status'], {
|
|
97
|
+
timeout: 10_000,
|
|
98
|
+
maxBuffer: 128 * 1024,
|
|
99
|
+
});
|
|
100
|
+
return [stdout, stderr].filter(Boolean).join('\n').trim() || 'GitHub Copilot CLI returned no status output.';
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return error instanceof Error ? error.message : 'GitHub Copilot CLI status check failed.';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
applyVpsRuntimeConfiguration,
|
|
4
|
+
listVpsEnvironmentVariables,
|
|
5
|
+
updateVpsEnvironmentVariables,
|
|
6
|
+
} from '@/src/utils/vpsConfiguration';
|
|
7
|
+
import { isUserAdmin } from '@/src/utils/isUserAdmin';
|
|
8
|
+
import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Loads `.env` variables visible in the admin UI.
|
|
12
|
+
*/
|
|
13
|
+
export async function GET() {
|
|
14
|
+
if (!(await isUserAdmin())) {
|
|
15
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return NextResponse.json({
|
|
20
|
+
...(await listVpsEnvironmentVariables()),
|
|
21
|
+
canEdit: await isUserGlobalAdmin(),
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return NextResponse.json(
|
|
25
|
+
{ error: error instanceof Error ? error.message : 'Failed to load environment variables.' },
|
|
26
|
+
{ status: 500 },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Persists editable `.env` variables and optionally reapplies VPS runtime configuration.
|
|
33
|
+
*/
|
|
34
|
+
export async function PATCH(request: Request) {
|
|
35
|
+
if (!(await isUserGlobalAdmin())) {
|
|
36
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const body = (await request.json().catch(() => null)) as
|
|
41
|
+
| {
|
|
42
|
+
readonly variables?: Record<string, string>;
|
|
43
|
+
readonly applyRuntimeConfiguration?: boolean;
|
|
44
|
+
}
|
|
45
|
+
| null;
|
|
46
|
+
|
|
47
|
+
if (!body || typeof body.variables !== 'object' || body.variables === null) {
|
|
48
|
+
return NextResponse.json({ error: 'Variables payload is required.' }, { status: 400 });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const snapshot = await updateVpsEnvironmentVariables(body.variables);
|
|
52
|
+
const applyResult = body.applyRuntimeConfiguration ? await applyVpsRuntimeConfiguration() : null;
|
|
53
|
+
|
|
54
|
+
return NextResponse.json({
|
|
55
|
+
...snapshot,
|
|
56
|
+
canEdit: true,
|
|
57
|
+
applyResult,
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return NextResponse.json(
|
|
61
|
+
{ error: error instanceof Error ? error.message : 'Failed to update environment variables.' },
|
|
62
|
+
{ status: 500 },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
|
|
3
|
+
import { readVpsPm2Logs } from '@/src/utils/vpsConfiguration';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loads recent pm2 logs for the standalone Agents Server process.
|
|
7
|
+
*/
|
|
8
|
+
export async function GET(request: Request) {
|
|
9
|
+
if (!(await isUserGlobalAdmin())) {
|
|
10
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const rawLines = Number.parseInt(url.searchParams.get('lines') || '200', 10);
|
|
16
|
+
const lineCount = Number.isFinite(rawLines) ? Math.min(Math.max(rawLines, 20), 1000) : 200;
|
|
17
|
+
return NextResponse.json(await readVpsPm2Logs(lineCount));
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: error instanceof Error ? error.message : 'Failed to load pm2 logs.' },
|
|
21
|
+
{ status: 500 },
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
+
import { isAgentsServerSqliteMode } from '../../../../../database/agentsServerDatabaseMode';
|
|
2
3
|
import { resolveCurrentServerRegistryContext } from '../../../../../utils/currentServerRegistryContext';
|
|
3
4
|
import { isUserGlobalAdmin } from '../../../../../utils/isUserGlobalAdmin';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createServerPublicUrl,
|
|
7
|
+
listEnvironmentRegisteredServers,
|
|
8
|
+
normalizeServerDomain,
|
|
9
|
+
} from '../../../../../utils/serverRegistry';
|
|
5
10
|
import {
|
|
6
11
|
assertGlobalAdminAccess,
|
|
7
12
|
deleteManagedServer,
|
|
@@ -10,6 +15,11 @@ import {
|
|
|
10
15
|
updateManagedServer,
|
|
11
16
|
type UpdateServerInput,
|
|
12
17
|
} from '../../../../../utils/serverManagement';
|
|
18
|
+
import {
|
|
19
|
+
applyVpsRuntimeConfiguration,
|
|
20
|
+
listConfiguredVpsDomains,
|
|
21
|
+
updateConfiguredVpsDomains,
|
|
22
|
+
} from '../../../../../utils/vpsConfiguration';
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Updates editable `_Server` fields for one registered server.
|
|
@@ -24,8 +34,15 @@ export async function PATCH(request: Request, context: { params: Promise<{ serve
|
|
|
24
34
|
|
|
25
35
|
const { serverId } = await context.params;
|
|
26
36
|
const body = (await request.json()) as Omit<UpdateServerInput, 'id'>;
|
|
37
|
+
const parsedServerId = parseManagedServerId(serverId);
|
|
38
|
+
|
|
39
|
+
if (isAgentsServerSqliteMode()) {
|
|
40
|
+
const updatedServer = await updateStandaloneVpsServerDomain(parsedServerId, body.domain);
|
|
41
|
+
return NextResponse.json({ server: updatedServer });
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
const updatedServer = await updateManagedServer({
|
|
28
|
-
id:
|
|
45
|
+
id: parsedServerId,
|
|
29
46
|
...body,
|
|
30
47
|
});
|
|
31
48
|
|
|
@@ -52,9 +69,19 @@ export async function DELETE(_request: Request, context: { params: Promise<{ ser
|
|
|
52
69
|
assertGlobalAdminAccess(await isUserGlobalAdmin());
|
|
53
70
|
|
|
54
71
|
const { serverId } = await context.params;
|
|
72
|
+
const parsedServerId = parseManagedServerId(serverId);
|
|
73
|
+
|
|
74
|
+
if (isAgentsServerSqliteMode()) {
|
|
75
|
+
await deleteStandaloneVpsServerDomain(parsedServerId);
|
|
76
|
+
return NextResponse.json({
|
|
77
|
+
success: true,
|
|
78
|
+
redirectUrl: null,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
const currentContext = await resolveCurrentServerRegistryContext();
|
|
56
83
|
const nextServerId = await deleteManagedServer({
|
|
57
|
-
serverId:
|
|
84
|
+
serverId: parsedServerId,
|
|
58
85
|
currentServerId: currentContext.currentServer?.id ?? null,
|
|
59
86
|
});
|
|
60
87
|
const nextServer =
|
|
@@ -76,3 +103,52 @@ export async function DELETE(_request: Request, context: { params: Promise<{ ser
|
|
|
76
103
|
);
|
|
77
104
|
}
|
|
78
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Updates a virtual standalone VPS server by replacing its domain in `SERVERS`.
|
|
109
|
+
*
|
|
110
|
+
* @param serverId - Virtual server id.
|
|
111
|
+
* @param rawDomain - Replacement domain.
|
|
112
|
+
* @returns Updated virtual server row.
|
|
113
|
+
*/
|
|
114
|
+
async function updateStandaloneVpsServerDomain(serverId: number, rawDomain: string) {
|
|
115
|
+
const normalizedDomain = normalizeServerDomain(rawDomain);
|
|
116
|
+
if (!normalizedDomain) {
|
|
117
|
+
throw new Error('A valid domain is required.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const servers = listEnvironmentRegisteredServers();
|
|
121
|
+
const serverIndex = servers.findIndex((server) => server.id === serverId);
|
|
122
|
+
if (serverIndex === -1) {
|
|
123
|
+
throw new Error(`Standalone VPS server ${serverId} was not found.`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const domains = await listConfiguredVpsDomains();
|
|
127
|
+
const nextDomains = domains.map((domain, index) => (index === serverIndex ? normalizedDomain : domain));
|
|
128
|
+
await updateConfiguredVpsDomains(nextDomains);
|
|
129
|
+
await applyVpsRuntimeConfiguration();
|
|
130
|
+
|
|
131
|
+
const updatedServer = listEnvironmentRegisteredServers().find((server) => server.domain === normalizedDomain);
|
|
132
|
+
if (!updatedServer) {
|
|
133
|
+
throw new Error(`Standalone VPS server ${normalizedDomain} was not persisted.`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return updatedServer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Deletes a virtual standalone VPS server by removing its domain from `SERVERS`.
|
|
141
|
+
*
|
|
142
|
+
* @param serverId - Virtual server id.
|
|
143
|
+
*/
|
|
144
|
+
async function deleteStandaloneVpsServerDomain(serverId: number): Promise<void> {
|
|
145
|
+
const servers = listEnvironmentRegisteredServers();
|
|
146
|
+
const serverIndex = servers.findIndex((server) => server.id === serverId);
|
|
147
|
+
if (serverIndex === -1) {
|
|
148
|
+
throw new Error(`Standalone VPS server ${serverId} was not found.`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const domains = await listConfiguredVpsDomains();
|
|
152
|
+
await updateConfiguredVpsDomains(domains.filter((_domain, index) => index !== serverIndex));
|
|
153
|
+
await applyVpsRuntimeConfiguration();
|
|
154
|
+
}
|