@opengeni/api-router 0.5.2 → 0.5.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/app.js +1 -1
- package/dist/{chunk-YY6OAEL6.js → chunk-DO2G3JSB.js} +5333 -2205
- package/dist/chunk-DO2G3JSB.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +297 -54
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
- package/src/app.ts +415 -147
- package/src/auth/managed-auth.ts +32 -16
- package/src/http/auth.ts +8 -1
- package/src/http/common.ts +6 -2
- package/src/http/sse.ts +27 -6
- package/src/index.ts +196 -74
- package/src/integrations/oauth-client.ts +592 -131
- package/src/integrations/provider-domain.ts +4 -1
- package/src/mcp/documents.ts +173 -94
- package/src/mcp/server.ts +1517 -692
- package/src/mcp/session-view.ts +8 -2
- package/src/mcp/toolspace.ts +175 -84
- package/src/observability.ts +7 -1
- package/src/routes/api-keys.ts +39 -23
- package/src/routes/billing.ts +180 -65
- package/src/routes/capabilities.ts +17 -8
- package/src/routes/catalog-assets.ts +5 -2
- package/src/routes/codex.ts +244 -63
- package/src/routes/connections.ts +72 -34
- package/src/routes/documents.ts +242 -92
- package/src/routes/enrollments.ts +100 -70
- package/src/routes/environments.ts +205 -136
- package/src/routes/files.ts +164 -39
- package/src/routes/github.ts +123 -50
- package/src/routes/install.ts +9 -2
- package/src/routes/machines.ts +9 -8
- package/src/routes/packs.ts +141 -89
- package/src/routes/rigs.ts +189 -0
- package/src/routes/scheduled-tasks.ts +51 -9
- package/src/routes/sessions.ts +839 -329
- package/src/routes/social.ts +50 -38
- package/src/routes/workspace-capture.ts +238 -0
- package/src/routes/workspaces.ts +159 -13
- package/src/sandbox/access.ts +11 -3
- package/src/sandbox/auth-callout.ts +5 -1
- package/src/sandbox/channel-a.ts +104 -27
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +68 -59
- package/src/sandbox/metrics-ingestion.ts +238 -17
- package/src/sandbox/viewer.ts +172 -46
- package/dist/chunk-YY6OAEL6.js.map +0 -1
package/src/routes/github.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { GitHubAppManifestCreate } from "@opengeni/contracts";
|
|
2
|
-
import {
|
|
3
|
-
listGitHubInstallationIdsForWorkspace,
|
|
4
|
-
upsertGitHubInstallation,
|
|
5
|
-
} from "@opengeni/db";
|
|
2
|
+
import { listGitHubInstallationIdsForWorkspace, upsertGitHubInstallation } from "@opengeni/db";
|
|
6
3
|
import {
|
|
7
4
|
buildGitHubAppManifest,
|
|
8
5
|
convertGitHubAppManifest,
|
|
@@ -29,7 +26,7 @@ import type { ApiRouteDeps } from "@opengeni/core";
|
|
|
29
26
|
const githubStateCookie = "opengeni_github_state";
|
|
30
27
|
|
|
31
28
|
export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
32
|
-
const {
|
|
29
|
+
const { settings, githubStateSecret } = deps;
|
|
33
30
|
|
|
34
31
|
app.get("/v1/workspaces/:workspaceId/github/app", async (c) => {
|
|
35
32
|
const workspaceId = c.req.param("workspaceId");
|
|
@@ -46,7 +43,9 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
46
43
|
appId: settings.githubAppId ?? null,
|
|
47
44
|
clientId: settings.githubClientId ?? null,
|
|
48
45
|
appSlug: slug,
|
|
49
|
-
installUrl: slug
|
|
46
|
+
installUrl: slug
|
|
47
|
+
? `https://github.com/apps/${slug}/installations/new?state=${encodeURIComponent(state)}`
|
|
48
|
+
: null,
|
|
50
49
|
missing,
|
|
51
50
|
});
|
|
52
51
|
});
|
|
@@ -70,10 +69,17 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
70
69
|
}
|
|
71
70
|
const slug = settings.githubAppSlug?.trim();
|
|
72
71
|
if (!slug) {
|
|
73
|
-
throw new HTTPException(409, {
|
|
72
|
+
throw new HTTPException(409, {
|
|
73
|
+
message: JSON.stringify({
|
|
74
|
+
message: "GitHub App is not configured",
|
|
75
|
+
missing: githubAppMissingSettings(settings),
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
74
78
|
}
|
|
75
79
|
setGitHubStateCookie(c, deps, state);
|
|
76
|
-
return c.redirect(
|
|
80
|
+
return c.redirect(
|
|
81
|
+
`https://github.com/apps/${slug}/installations/new?state=${encodeURIComponent(state)}`,
|
|
82
|
+
);
|
|
77
83
|
});
|
|
78
84
|
|
|
79
85
|
app.get("/v1/workspaces/:workspaceId/github/repositories", async (c) => {
|
|
@@ -83,9 +89,13 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
83
89
|
return c.json({ repositories: await listWorkspaceGitHubRepositories(deps, workspaceId) });
|
|
84
90
|
} catch (error) {
|
|
85
91
|
if (error instanceof GitHubAppConfigurationError) {
|
|
86
|
-
throw new HTTPException(409, {
|
|
92
|
+
throw new HTTPException(409, {
|
|
93
|
+
message: JSON.stringify({ message: error.message, missing: error.missing }),
|
|
94
|
+
});
|
|
87
95
|
}
|
|
88
|
-
throw new HTTPException(502, {
|
|
96
|
+
throw new HTTPException(502, {
|
|
97
|
+
message: error instanceof Error ? error.message : String(error),
|
|
98
|
+
});
|
|
89
99
|
}
|
|
90
100
|
});
|
|
91
101
|
|
|
@@ -96,9 +106,13 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
96
106
|
return c.json({ repositories: await listWorkspaceGitHubRepositories(deps, workspaceId) });
|
|
97
107
|
} catch (error) {
|
|
98
108
|
if (error instanceof GitHubAppConfigurationError) {
|
|
99
|
-
throw new HTTPException(409, {
|
|
109
|
+
throw new HTTPException(409, {
|
|
110
|
+
message: JSON.stringify({ message: error.message, missing: error.missing }),
|
|
111
|
+
});
|
|
100
112
|
}
|
|
101
|
-
throw new HTTPException(502, {
|
|
113
|
+
throw new HTTPException(502, {
|
|
114
|
+
message: error instanceof Error ? error.message : String(error),
|
|
115
|
+
});
|
|
102
116
|
}
|
|
103
117
|
});
|
|
104
118
|
|
|
@@ -106,7 +120,10 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
106
120
|
const workspaceId = c.req.param("workspaceId");
|
|
107
121
|
const grant = await requireAccessGrant(c, deps, workspaceId, "github:manage");
|
|
108
122
|
const payload = GitHubAppManifestCreate.parse(await c.req.json());
|
|
109
|
-
const baseUrl = (settings.githubAppManifestBaseUrl ?? new URL(c.req.url).origin).replace(
|
|
123
|
+
const baseUrl = (settings.githubAppManifestBaseUrl ?? new URL(c.req.url).origin).replace(
|
|
124
|
+
/\/+$/,
|
|
125
|
+
"",
|
|
126
|
+
);
|
|
110
127
|
const state = createSignedState(githubStateSecret, {
|
|
111
128
|
accountId: grant.accountId,
|
|
112
129
|
workspaceId: grant.workspaceId,
|
|
@@ -122,7 +139,9 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
122
139
|
});
|
|
123
140
|
const organization = payload.organization?.trim();
|
|
124
141
|
return c.json({
|
|
125
|
-
actionUrl: organization
|
|
142
|
+
actionUrl: organization
|
|
143
|
+
? organizationAppManifestUrl(organization, state)
|
|
144
|
+
: personalAppManifestUrl(state),
|
|
126
145
|
state,
|
|
127
146
|
manifest,
|
|
128
147
|
});
|
|
@@ -141,7 +160,9 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
141
160
|
const conversion = await convertGitHubAppManifest(code);
|
|
142
161
|
const envLines = envLinesFromGitHubManifestConversion(conversion);
|
|
143
162
|
const slug = String(conversion.slug ?? "");
|
|
144
|
-
const installUrl = slug
|
|
163
|
+
const installUrl = slug
|
|
164
|
+
? `https://github.com/apps/${slug}/installations/new?state=${encodeURIComponent(state)}`
|
|
165
|
+
: "";
|
|
145
166
|
setGitHubStateCookie(c, deps, state);
|
|
146
167
|
return c.html(githubSuccessHtml(envLines, installUrl));
|
|
147
168
|
} catch (error) {
|
|
@@ -159,13 +180,19 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
159
180
|
throw new HTTPException(400, { message: "missing GitHub installation state" });
|
|
160
181
|
}
|
|
161
182
|
const statePayload = readSignedState(state, githubStateSecret);
|
|
162
|
-
if (
|
|
183
|
+
if (
|
|
184
|
+
!statePayload ||
|
|
185
|
+
typeof statePayload.accountId !== "string" ||
|
|
186
|
+
typeof statePayload.workspaceId !== "string"
|
|
187
|
+
) {
|
|
163
188
|
throw new HTTPException(400, { message: "invalid or expired GitHub installation state" });
|
|
164
189
|
}
|
|
165
190
|
requireGitHubStateCookie(c, state);
|
|
166
191
|
const grant = await requireAccessGrant(c, deps, statePayload.workspaceId, "github:manage");
|
|
167
192
|
if (grant.accountId !== statePayload.accountId) {
|
|
168
|
-
throw new HTTPException(403, {
|
|
193
|
+
throw new HTTPException(403, {
|
|
194
|
+
message: "GitHub installation state does not match this workspace",
|
|
195
|
+
});
|
|
169
196
|
}
|
|
170
197
|
if (setupAction === "request" && !installationIdRaw) {
|
|
171
198
|
return c.html(githubSetupPendingHtml());
|
|
@@ -177,20 +204,31 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
177
204
|
if (!code) {
|
|
178
205
|
const clientId = settings.githubClientId?.trim();
|
|
179
206
|
if (!clientId) {
|
|
180
|
-
throw new HTTPException(409, {
|
|
207
|
+
throw new HTTPException(409, {
|
|
208
|
+
message: JSON.stringify({
|
|
209
|
+
message: "GitHub App is not configured",
|
|
210
|
+
missing: ["OPENGENI_GITHUB_CLIENT_ID"],
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
181
213
|
}
|
|
182
214
|
const oauthState = createSignedState(githubStateSecret, {
|
|
183
215
|
accountId: grant.accountId,
|
|
184
216
|
workspaceId: grant.workspaceId,
|
|
185
217
|
installationId,
|
|
186
218
|
});
|
|
187
|
-
const baseUrl = (
|
|
219
|
+
const baseUrl = (
|
|
220
|
+
settings.githubAppManifestBaseUrl ??
|
|
221
|
+
settings.publicBaseUrl ??
|
|
222
|
+
new URL(c.req.url).origin
|
|
223
|
+
).replace(/\/+$/, "");
|
|
188
224
|
setGitHubStateCookie(c, deps, oauthState);
|
|
189
|
-
return c.redirect(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
225
|
+
return c.redirect(
|
|
226
|
+
githubOAuthAuthorizeUrl({
|
|
227
|
+
clientId,
|
|
228
|
+
state: oauthState,
|
|
229
|
+
redirectUri: `${baseUrl}/v1/github/oauth/callback`,
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
194
232
|
}
|
|
195
233
|
return await completeGitHubInstallationBinding(deps, c, {
|
|
196
234
|
code,
|
|
@@ -213,7 +251,12 @@ export function registerGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
213
251
|
}
|
|
214
252
|
const statePayload = readSignedState(state, githubStateSecret);
|
|
215
253
|
const installationId = parsePositiveInteger(String(statePayload?.installationId ?? ""));
|
|
216
|
-
if (
|
|
254
|
+
if (
|
|
255
|
+
!statePayload ||
|
|
256
|
+
typeof statePayload.accountId !== "string" ||
|
|
257
|
+
typeof statePayload.workspaceId !== "string" ||
|
|
258
|
+
installationId === null
|
|
259
|
+
) {
|
|
217
260
|
throw new HTTPException(400, { message: "invalid or expired GitHub OAuth state" });
|
|
218
261
|
}
|
|
219
262
|
requireGitHubStateCookie(c, state);
|
|
@@ -240,12 +283,19 @@ async function completeGitHubInstallationBinding(
|
|
|
240
283
|
}
|
|
241
284
|
const grant = await requireAccessGrant(c, deps, input.statePayload.workspaceId, "github:manage");
|
|
242
285
|
if (grant.accountId !== input.statePayload.accountId) {
|
|
243
|
-
throw new HTTPException(403, {
|
|
286
|
+
throw new HTTPException(403, {
|
|
287
|
+
message: "GitHub installation state does not match this workspace",
|
|
288
|
+
});
|
|
244
289
|
}
|
|
245
290
|
try {
|
|
246
|
-
const installation = await verifyGitHubInstallationAccessForUser(settings, {
|
|
291
|
+
const installation = await verifyGitHubInstallationAccessForUser(settings, {
|
|
292
|
+
code: input.code,
|
|
293
|
+
installationId: input.installationId,
|
|
294
|
+
});
|
|
247
295
|
if (!installation) {
|
|
248
|
-
throw new HTTPException(404, {
|
|
296
|
+
throw new HTTPException(404, {
|
|
297
|
+
message: "GitHub App installation was not found for this app",
|
|
298
|
+
});
|
|
249
299
|
}
|
|
250
300
|
if (installation.suspended) {
|
|
251
301
|
throw new HTTPException(409, { message: "GitHub App installation is suspended" });
|
|
@@ -259,16 +309,25 @@ async function completeGitHubInstallationBinding(
|
|
|
259
309
|
});
|
|
260
310
|
const returnUrl = openGeniReturnUrl(settings, c, input.statePayload.workspaceId);
|
|
261
311
|
deleteCookie(c, githubStateCookie, { path: "/v1/github" });
|
|
262
|
-
return c.html(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
throw
|
|
312
|
+
return c.html(
|
|
313
|
+
githubSetupSuccessHtml(
|
|
314
|
+
installation.accountLogin ?? `installation ${input.installationId}`,
|
|
315
|
+
returnUrl,
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error instanceof HTTPException) {
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
if (error instanceof GitHubAppConfigurationError) {
|
|
323
|
+
throw new HTTPException(409, {
|
|
324
|
+
message: JSON.stringify({ message: error.message, missing: error.missing }),
|
|
325
|
+
});
|
|
271
326
|
}
|
|
327
|
+
throw new HTTPException(502, {
|
|
328
|
+
message: error instanceof Error ? error.message : String(error),
|
|
329
|
+
});
|
|
330
|
+
}
|
|
272
331
|
}
|
|
273
332
|
|
|
274
333
|
function setGitHubStateCookie(c: Context, deps: ApiRouteDeps, state: string): void {
|
|
@@ -283,14 +342,18 @@ function setGitHubStateCookie(c: Context, deps: ApiRouteDeps, state: string): vo
|
|
|
283
342
|
|
|
284
343
|
function requireGitHubStateCookie(c: Context, state: string): void {
|
|
285
344
|
if (getCookie(c, githubStateCookie) !== state) {
|
|
286
|
-
throw new HTTPException(400, {
|
|
345
|
+
throw new HTTPException(400, {
|
|
346
|
+
message: "invalid or expired GitHub installation browser state",
|
|
347
|
+
});
|
|
287
348
|
}
|
|
288
349
|
}
|
|
289
350
|
|
|
290
351
|
function isSecureRequest(c: Context, deps: ApiRouteDeps): boolean {
|
|
291
|
-
return
|
|
292
|
-
|
|
293
|
-
|
|
352
|
+
return (
|
|
353
|
+
deps.settings.publicBaseUrl?.startsWith("https://") ||
|
|
354
|
+
c.req.header("x-forwarded-proto") === "https" ||
|
|
355
|
+
new URL(c.req.url).protocol === "https:"
|
|
356
|
+
);
|
|
294
357
|
}
|
|
295
358
|
|
|
296
359
|
export async function listWorkspaceGitHubRepositories(deps: ApiRouteDeps, workspaceId: string) {
|
|
@@ -301,7 +364,9 @@ export async function listWorkspaceGitHubRepositories(deps: ApiRouteDeps, worksp
|
|
|
301
364
|
function githubSuccessHtml(envLines: string[], installUrl: string): string {
|
|
302
365
|
const envText = envLines.join("\n");
|
|
303
366
|
const escaped = escapeHtml(envText);
|
|
304
|
-
const install = installUrl
|
|
367
|
+
const install = installUrl
|
|
368
|
+
? `<a class="button secondary" href="${escapeHtml(installUrl)}">Install on repositories</a>`
|
|
369
|
+
: "";
|
|
305
370
|
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Created</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(760px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.env-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:22px 0 8px}.env-header h2{margin:0;font-size:13px;line-height:1.2;text-transform:uppercase;letter-spacing:.08em;color:#a1a1aa}pre{white-space:pre-wrap;word-break:break-word;max-height:380px;overflow:auto;background:#09090b;border:1px solid #27272a;border-radius:8px;padding:16px;font-size:13px;line-height:1.5}.actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:18px}.button,button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none;cursor:pointer}.button.secondary{background:transparent;color:#fafafa}.button.secondary:hover,button.secondary:hover{background:#27272a}button:disabled{cursor:not-allowed;opacity:.7}</style></head><body><main><h1>GitHub App created</h1><p>Add these values to .env, then restart API and worker.</p><div class="env-header"><h2>Environment variables</h2><button id="copy-env" type="button">Copy env</button></div><pre id="env-lines">${escaped}</pre><div class="actions">${install}</div><script>(()=>{const button=document.getElementById("copy-env");const env=document.getElementById("env-lines");async function copyText(text){if(navigator.clipboard&&window.isSecureContext){await navigator.clipboard.writeText(text);return;}const area=document.createElement("textarea");area.value=text;area.setAttribute("readonly","");area.style.position="fixed";area.style.inset="-9999px";document.body.append(area);area.select();document.execCommand("copy");area.remove();}button?.addEventListener("click",async()=>{try{await copyText(env?.textContent||"");button.textContent="Copied";setTimeout(()=>button.textContent="Copy env",1600);}catch{button.textContent="Copy failed";setTimeout(()=>button.textContent="Copy env",2200);}});})();</script></main></body></html>`;
|
|
306
371
|
}
|
|
307
372
|
|
|
@@ -322,16 +387,24 @@ function parsePositiveInteger(value: string | undefined | null): number | null {
|
|
|
322
387
|
}
|
|
323
388
|
|
|
324
389
|
function escapeHtml(value: string): string {
|
|
325
|
-
return value.replace(
|
|
326
|
-
"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
390
|
+
return value.replace(
|
|
391
|
+
/[&<>"']/g,
|
|
392
|
+
(char) =>
|
|
393
|
+
({
|
|
394
|
+
"&": "&",
|
|
395
|
+
"<": "<",
|
|
396
|
+
">": ">",
|
|
397
|
+
'"': """,
|
|
398
|
+
"'": "'",
|
|
399
|
+
})[char] ?? char,
|
|
400
|
+
);
|
|
332
401
|
}
|
|
333
402
|
|
|
334
|
-
function openGeniReturnUrl(
|
|
403
|
+
function openGeniReturnUrl(
|
|
404
|
+
settings: ApiRouteDeps["settings"],
|
|
405
|
+
c: Context,
|
|
406
|
+
workspaceId: string | undefined,
|
|
407
|
+
): string {
|
|
335
408
|
const base = (settings.publicBaseUrl ?? new URL(c.req.url).origin).replace(/\/+$/, "");
|
|
336
409
|
const url = new URL(base || new URL(c.req.url).origin);
|
|
337
410
|
if (workspaceId) {
|
package/src/routes/install.ts
CHANGED
|
@@ -41,7 +41,10 @@ const TEXT_ASSETS: Record<string, { file: string; contentType: string }> = {
|
|
|
41
41
|
"/install.sh": { file: "install.sh", contentType: "text/x-shellscript; charset=utf-8" },
|
|
42
42
|
"/install.ps1": { file: "install.ps1", contentType: "text/plain; charset=utf-8" },
|
|
43
43
|
"/uninstall.sh": { file: "uninstall.sh", contentType: "text/x-shellscript; charset=utf-8" },
|
|
44
|
-
"/opengeni-agent-minisign.pub": {
|
|
44
|
+
"/opengeni-agent-minisign.pub": {
|
|
45
|
+
file: "opengeni-agent-minisign.pub",
|
|
46
|
+
contentType: "text/plain; charset=utf-8",
|
|
47
|
+
},
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
const assetCache = new Map<string, string>();
|
|
@@ -84,7 +87,11 @@ const DEFAULT_BASE_REWRITES: Record<string, (base: string) => { from: string; to
|
|
|
84
87
|
// archive default stands), when the URL is not absolute http(s) (never serve a
|
|
85
88
|
// script with a broken base), or when the file has no marker (script drift — fail
|
|
86
89
|
// safe to serving it verbatim rather than crashing the install surface).
|
|
87
|
-
function rewriteDefaultBaseUrl(
|
|
90
|
+
function rewriteDefaultBaseUrl(
|
|
91
|
+
file: string,
|
|
92
|
+
body: string,
|
|
93
|
+
publicBaseUrl: string | undefined,
|
|
94
|
+
): string {
|
|
88
95
|
if (!publicBaseUrl || !/^https?:\/\//.test(publicBaseUrl)) {
|
|
89
96
|
return body;
|
|
90
97
|
}
|
package/src/routes/machines.ts
CHANGED
|
@@ -19,10 +19,7 @@ import {
|
|
|
19
19
|
SwapActiveSandboxRequest,
|
|
20
20
|
SwapActiveSandboxResponse,
|
|
21
21
|
} from "@opengeni/contracts";
|
|
22
|
-
import {
|
|
23
|
-
getEnrollment,
|
|
24
|
-
readMachineMetricsSeries,
|
|
25
|
-
} from "@opengeni/db";
|
|
22
|
+
import { getEnrollment, readMachineMetricsSeries } from "@opengeni/db";
|
|
26
23
|
import type { Hono } from "hono";
|
|
27
24
|
import { HTTPException } from "hono/http-exception";
|
|
28
25
|
import { requireAccessGrant } from "@opengeni/core";
|
|
@@ -48,7 +45,9 @@ export function registerMachineRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
48
45
|
// invisible while disabled — it does not exist for this deployment yet.
|
|
49
46
|
function assertSelfhostedEnabled(): void {
|
|
50
47
|
if (!settings.sandboxSelfhostedEnabled) {
|
|
51
|
-
throw new HTTPException(404, {
|
|
48
|
+
throw new HTTPException(404, {
|
|
49
|
+
message: "selfhosted machines are not enabled for this deployment",
|
|
50
|
+
});
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -79,9 +78,11 @@ export function registerMachineRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
|
79
78
|
const windowMs = SERIES_WINDOWS_MS[c.req.query("window") ?? ""] ?? DEFAULT_SERIES_WINDOW_MS;
|
|
80
79
|
const since = new Date(Date.now() - windowMs);
|
|
81
80
|
const rows = await readMachineMetricsSeries(db, { workspaceId, enrollmentId, since });
|
|
82
|
-
return c.json(
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
return c.json(
|
|
82
|
+
MachineMetricsSeriesResponse.parse({
|
|
83
|
+
samples: rows.map(metricRowToSample),
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
// ── POST /workspaces/:ws/sessions/:sessionId/active-sandbox (swap) ───────────
|