|
onOpen(node)} openLabel={t("Open {{name}}", { name })} idLabel={t("Node ID")} />
|
- |
+ {diagnostic ? : null} |
{suspends ? {node.active} / {node.max_active} | : null}
{suspends ? {suspendedSandboxes(node)} | : null}
diff --git a/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx b/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx
index ab457ab5f..218b6db26 100644
--- a/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx
+++ b/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx
@@ -2,10 +2,15 @@ import { useState } from "react";
import type { InitializeSandboxDeployment, SandboxDeployment } from "@agents-core-web/agents-client";
import { useTranslation } from "react-i18next";
import { HelpTip } from "../../components/console-ui";
-import { formatBytes, MISSING } from "../../lib/format";
+import { formatBytes, formatPeriod, MISSING } from "../../lib/format";
+import type { MessageKey } from "../../lib/locale-strings";
import { sandboxProviderLabel } from "../../lib/sandbox-labels";
+import { sandboxSize, templateBuildSize, templateBuildStatus } from "./deployment-specification";
import { SandboxSetupWizard } from "./SandboxSetupWizard";
+const MIB = 2 ** 20;
+const buildStatusLabel: Record, MessageKey> = { ready: "Ready", notReady: "Not ready", unknown: "Unknown state" };
+
export function SandboxDeploymentSettings({ deployment, disabled, fresh, onMaintenance, onUpdate, onRefresh }: {
deployment: SandboxDeployment;
disabled: boolean;
@@ -18,6 +23,11 @@ export function SandboxDeploymentSettings({ deployment, disabled, fresh, onMaint
const locale = i18n.resolvedLanguage?.startsWith("zh") ? "zh" : "en";
const [changing, setChanging] = useState(false);
const spec = deployment.specification;
+ // An E2B selection may adopt its template build's size instead of saving one.
+ const size = sandboxSize(deployment);
+ const build = deployment.e2b?.template_build;
+ const buildSize = templateBuildSize(deployment);
+ const sizeLabel = (value: { cpus: number; memory_mib: number }) => t("{{cpus}} CPU · {{memory}}", { cpus: value.cpus, memory: formatBytes(value.memory_mib * MIB) });
const clean = fresh && deployment.resources?.allocations === 0 && deployment.resources?.pending === 0;
return
@@ -29,13 +39,25 @@ export function SandboxDeploymentSettings({ deployment, disabled, fresh, onMaint
- {t("Provider")}
- {sandboxProviderLabel(deployment.provider, locale)}
- - {t("Each sandbox")}
- {spec ? `${t("{{cpus}} CPU · {{memory}}", { cpus: spec.resources.cpus, memory: formatBytes(spec.resources.memory_mib * 2 ** 20) })}${spec.resources.root_disk_mib ? ` · ${t("Root disk {{root}} · data disk {{data}}", { root: formatBytes(spec.resources.root_disk_mib * 2 ** 20), data: formatBytes((spec.resources.environment_disk_mib ?? 0) * 2 ** 20) })}` : ""}` : MISSING}
+ - {t("Each sandbox")}
- {size ? `${sizeLabel(size)}${size.root_disk_mib ? ` · ${t("Root disk {{root}} · data disk {{data}}", { root: formatBytes(size.root_disk_mib * MIB), data: formatBytes((size.environment_disk_mib ?? 0) * MIB) })}` : ""}` : MISSING}
{spec?.runtime ? - {t("Runtime")}
{spec.runtime.source_commit.slice(0, 12)} : null}
+ {deployment.suspension ? - {t("Idle suspension")}
- {t("After {{idle}} · kept {{retention}}", { idle: formatPeriod(deployment.suspension.idle_seconds, i18n.resolvedLanguage), retention: formatPeriod(deployment.suspension.retention_seconds, i18n.resolvedLanguage) })}
: null}
- {t("Allocated resources")}
- {deployment.resources?.allocations ?? t("Unknown state")}
- {t("Pending environments")}
- {deployment.resources?.pending ?? t("Unknown state")}
{deployment.provider === "e2b" ?
- - {t("Immutable Runtime template")}
- {deployment.e2b?.template || t("Unknown state")}
- {t("E2B credential")}
- {t(deployment.e2b?.credential_configured ? "Configured" : "Not configured")}
+
+ - {t("Immutable Runtime template")}
- {deployment.e2b?.template || t("Unknown state")}
+
+ - {t("Template build")}
+ - {[
+ t(buildStatusLabel[templateBuildStatus(build)]),
+ ...(buildSize ? [sizeLabel(buildSize)] : []),
+ ...(build?.resources.root_disk_mib != null ? [t("{{disk}} disk", { disk: formatBytes(build.resources.root_disk_mib * MIB) })] : []),
+ ].join(" · ")}
+
+ - {t("E2B credential")}
- {t(deployment.e2b?.credential_configured ? "Configured" : "Not configured")}
+
: null}
{!deployment.maintenance ? : <>
diff --git a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx
index 58da18da2..1322e994a 100644
--- a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx
+++ b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx
@@ -60,8 +60,9 @@ function presetOf(provider: SandboxProvider, resources: SandboxResources): Prese
/**
* Hosted sandbox setup as pages, one decision each: where sandboxes run,
- * which backend (own machines) or the E2B account, how big each sandbox is,
- * then a review. Advanced settings hold the complete form. The Runtime
+ * which backend (own machines) or the E2B account, how big each sandbox is
+ * (own machines only: E2B sandboxes take the template build's size), then a
+ * review. Advanced settings hold the complete form. The Runtime
* release comes from this console's distribution manifest when it serves one.
* `current` pre-selects the saved choices when a deployment changes. Keeping
* the backend keeps its saved size and Runtime; another backend starts from its
@@ -102,10 +103,12 @@ export function SandboxSetupWizard({ initialCoreUrl, current, disabled, switchin
const runtimeReady = !needsRuntime || isRuntimeRelease(release);
// Core keeps no key across a change: E2B always needs one.
const e2bReady = provider !== "e2b" || (apiKey.trim().length > 0 && validTemplate(template.trim()));
- const sizeReady = provider !== null && validSandboxResources(provider, resources);
+ // Core sizes E2B sandboxes from the template build, so E2B sends no resources.
+ const sized = provider !== null && provider !== "e2b";
+ const sizeReady = provider !== null && (!sized || validSandboxResources(provider, resources));
const ready = provider !== null && origin !== null && runtimeReady && e2bReady && sizeReady && !disabled && !busy;
- const order: Step[] = where === "direct" ? ["where", "e2b", "size", "review"] : ["where", "backend", "size", "review"];
+ const order: Step[] = where === "direct" ? ["where", "e2b", "review"] : ["where", "backend", "size", "review"];
const index = Math.max(0, order.indexOf(step === "advanced" ? "review" : step));
const back = () => setStep(step === "advanced" ? "review" : order[Math.max(0, index - 1)]!);
@@ -128,7 +131,7 @@ export function SandboxSetupWizard({ initialCoreUrl, current, disabled, switchin
await onSubmit({
provider,
core_url: origin!,
- resources,
+ ...(sized ? { resources } : {}),
...(needsRuntime ? { runtime: release as SandboxRuntimeRelease } : {}),
...(provider === "e2b" ? { e2b: { api_key: apiKey.trim(), template: template.trim() } } : {}),
});
@@ -168,11 +171,11 @@ export function SandboxSetupWizard({ initialCoreUrl, current, disabled, switchin
setApiKey(event.target.value)} />
-
+
setTemplate(event.target.value)} placeholder="parsar-runtime:0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b" autoComplete="off" spellCheck={false} aria-invalid={Boolean(template && !validTemplate(template.trim()))} />
- |