From 1905d7ea42a825cc50aa3160bd6002108b2cac93 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 24 Sep 2026 23:03:24 +0800 Subject: [PATCH] fix(web): clarify and recheck API key management availability --- apps/web/e2e/first-run.spec.ts | 35 ++++++++++++++++++- .../web/src/features/api-keys/ApiKeyPanel.tsx | 8 +++-- apps/web/src/features/api-keys/api-keys.css | 16 ++++++--- apps/web/src/lib/api-key-strings.ts | 6 ++-- docs/getting-started/install.md | 5 +++ 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/apps/web/e2e/first-run.spec.ts b/apps/web/e2e/first-run.spec.ts index ec78fdc80..74c1459c2 100644 --- a/apps/web/e2e/first-run.spec.ts +++ b/apps/web/e2e/first-run.spec.ts @@ -338,7 +338,7 @@ test("lets a web-only console use an existing project connection without key man return route.fulfill({ status: 503, json: { error: { message: "Key management is not paired." } } }); }); await page.goto("/"); - await expect(page.getByRole("heading", { name: "Use an existing Agent API key.", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "API key management is not enabled", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Create API key", exact: true })).toHaveCount(0); await page.getByRole("button", { name: "I've saved it. Continue", exact: true }).click(); await expect(page.getByRole("heading", { name: "Connect your own machine.", exact: true })).toBeVisible(); @@ -353,3 +353,36 @@ test("lets a web-only console use an existing project connection without key man expect(writes[0]?.body).toMatchObject({ model: "fixture/web-only-model" }); expect(keyRequests).toBe(0); }); + +test("rechecks key management without sending key requests while disabled", async ({ page }) => { + await mockAccount(page, { mode: "authenticated", username }); + let enabled = false; + let keyRequests = 0; + await page.route("**/console/config", (route) => route.fulfill({ json: { api_keys: enabled, sandbox_admin: enabled, node_installer: false } })); + await page.route("**/console/api-keys", (route) => { + keyRequests++; + return route.fulfill({ json: { data: [] } }); + }); + await page.goto("/"); + await expect(page.getByRole("heading", { name: "API key management is not enabled", exact: true })).toBeVisible(); + await page.getByRole("button", { name: "Check again", exact: true }).click(); + await expect(page.getByRole("heading", { name: "API key management is not enabled", exact: true })).toBeVisible(); + expect(keyRequests).toBe(0); + enabled = true; + await page.getByRole("button", { name: "Check again", exact: true }).click(); + await expect(page.getByRole("button", { name: "Create API key", exact: true })).toBeEnabled(); + await expect(page.getByRole("button", { name: "I've saved it. Continue", exact: true })).toBeDisabled(); + expect(keyRequests).toBe(1); +}); + +test("keeps the API key management page separate from introduction guidance", async ({ page }) => { + await mockAccount(page, { mode: "authenticated", username }); + await page.route("**/console/config", (route) => route.fulfill({ json: { api_keys: false, sandbox_admin: false, node_installer: false } })); + await page.goto("/#api-keys"); + await expect(page.getByRole("heading", { name: "API key management is not enabled", exact: true })).toBeVisible(); + await expect(page.getByText("You can continue the introduction and use your signed-in console connection to create an Agent.", { exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Check again", exact: true })).toBeVisible(); + await page.setViewportSize({ width: 390, height: 844 }); + await expect(page.getByRole("button", { name: "Check again", exact: true })).toBeInViewport(); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); +}); diff --git a/apps/web/src/features/api-keys/ApiKeyPanel.tsx b/apps/web/src/features/api-keys/ApiKeyPanel.tsx index cd9796b5c..d12cc4e25 100644 --- a/apps/web/src/features/api-keys/ApiKeyPanel.tsx +++ b/apps/web/src/features/api-keys/ApiKeyPanel.tsx @@ -23,9 +23,11 @@ export function ApiKeyPanel({ onReady }: { onReady?: (ready: boolean) => void }) if (capability === "available") return ; return
{capability === "unavailable" ? <> -

{t("Use an existing Agent API key.")}

-

{t("This console cannot create API keys. Use a key supplied by your Core administrator for requests from your machine or application.")}

-

{t("You can continue the introduction and use your signed-in console connection to create an Agent.")}

+

{t("API key management is not enabled")}

+

{t("A deployment administrator needs to enable key management for this console. You can then create, view and revoke keys here.")}

+

{t("If you already have an Agent API key, you can keep using it for requests from your machine or application.")}

+ {onReady ?

{t("You can continue the introduction and use your signed-in console connection to create an Agent.")}

: null} + : capability === "loading" ?

{t("Checking API key management…")}

: <>

{t("Could not check API key management. Try again.")}

diff --git a/apps/web/src/features/api-keys/api-keys.css b/apps/web/src/features/api-keys/api-keys.css index 8aaa83caf..3098ea969 100644 --- a/apps/web/src/features/api-keys/api-keys.css +++ b/apps/web/src/features/api-keys/api-keys.css @@ -1,20 +1,26 @@ -.api-key-panel { padding: 26px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); } +.api-key-panel { min-width: 0; padding: 20px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); } .api-key-heading { display: flex; align-items: center; gap: 10px; } .api-key-heading h3 { font-size: 16px; font-weight: 550; letter-spacing: -.02em; margin: 0; } .api-key-panel header p, .api-key-caption { font-size: 12px; line-height: 1.7; color: var(--fg-muted); } .api-key-create { display: flex; gap: 12px; align-items: end; margin: 22px 0; } -.api-key-create .field { flex: 1; } +.api-key-create .field { flex: 1; min-width: 0; } .api-key-create .field span { font-size: 12px; } .api-key-create input { min-height: 36px; } .api-key-actions { display: flex; flex-wrap: wrap; gap: 8px; } -.api-key-secret { display: grid; gap: 14px; padding: 20px; margin: 20px 0; background: var(--surface-subtle); border: 1px solid var(--line-strong); border-radius: 10px; } +.api-key-secret { display: grid; min-width: 0; gap: 14px; padding: 16px; margin: 20px 0; background: var(--surface-subtle); border: 1px solid var(--line-strong); border-radius: 6px; } .api-key-secret strong { font-size: 13px; font-weight: 500; } .api-key-secret input { width: 100%; padding: 12px; border: 1px solid var(--line); border-radius: 6px; font-family: var(--font-mono); font-size: 12px; color: var(--fg); background: var(--surface); } .api-key-list-heading { display: flex; align-items: center; justify-content: space-between; font-size: 12px; margin-top: 20px; color: var(--fg-muted); } .api-key-list { padding: 0; margin: 0; list-style: none; } .api-key-list > li { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; padding: 14px 0; border-top: 1px solid var(--line); } -.api-key-list strong { font-size: 13px; display: block; font-weight: 500; } +.api-key-list strong { font-size: 13px; display: block; font-weight: 500; overflow-wrap: anywhere; } .api-key-list span { display: block; font-size: 11px; color: var(--fg-muted); margin-top: 5px; } .api-key-confirm { flex-basis: 100%; font-size: 12px; } .api-key-error { color: var(--danger); font-size: 12px; line-height: 1.7; } -.api-keys-page { max-width: 920px; margin: 0 auto; } +.api-keys-page { width: 100%; overflow: auto; } +.api-keys-page > .api-key-panel { margin: 24px; } +@media (max-width: 640px) { + .api-key-panel { padding: 16px; } + .api-keys-page > .api-key-panel { margin: 16px; } + .api-key-create { flex-direction: column; align-items: stretch; } +} diff --git a/apps/web/src/lib/api-key-strings.ts b/apps/web/src/lib/api-key-strings.ts index dbaf7d33f..9df85a69b 100644 --- a/apps/web/src/lib/api-key-strings.ts +++ b/apps/web/src/lib/api-key-strings.ts @@ -1,7 +1,9 @@ export const apiKeyChinese = { "My API key": "我的 API 密钥", - "Use an existing Agent API key.": "使用已有的 Agent API 密钥。", - "This console cannot create API keys. Use a key supplied by your Core administrator for requests from your machine or application.": "此控制台无法创建 API 密钥。从本机或应用发起请求时,请使用 Core 管理员提供的密钥。", + "API key management is not enabled": "API 密钥管理尚未启用", + "A deployment administrator needs to enable key management for this console. You can then create, view and revoke keys here.": "需要部署管理员为此控制台启用密钥管理,之后即可在这里创建、查看和撤销密钥。", + "If you already have an Agent API key, you can keep using it for requests from your machine or application.": "如果你已有 Agent API 密钥,仍可使用它从本机或应用发起请求。", + "Check again": "重新检查", "You can continue the introduction and use your signed-in console connection to create an Agent.": "你可以继续导览,并通过已登录的控制台连接创建 Agent。", "Checking API key management…": "正在检查 API 密钥管理功能…", "Could not check API key management. Try again.": "暂时无法检查 API 密钥管理功能,请重试。", diff --git a/docs/getting-started/install.md b/docs/getting-started/install.md index fd7d1d232..cbafdcf63 100644 --- a/docs/getting-started/install.md +++ b/docs/getting-started/install.md @@ -86,6 +86,11 @@ Neither is the administrator password. Examples read environment variables or as for keys privately in the terminal, and reject HTTP redirects. Keep the generated Core API key for later requests from your own machine. A Web-only installation without paired key management guides you to use an existing Core API key and still allows the request workbench. +If **API key management is not enabled** appears, configure the paired +administrator access described below, restart the console, sign in again if +prompted, and select **Check again**. The administrator account alone does not +enable deployment management. Existing caller keys remain valid; the page never +asks you to paste a deployment administrator key into the browser. Creating a saved Agent stores its configuration; it does not start a Session or call the model. The full protocol surface and execution support are documented in the [API guide](./quickstart.md).