From 6cfd929f7f8d12bb80bac82691a10c0ff99ede95 Mon Sep 17 00:00:00 2001 From: npc0-hue Date: Fri, 24 Jul 2026 19:38:46 +0800 Subject: [PATCH] fix: separate server deployment mode inputs --- .../redesign-server-deployment-workflow/design.md | 2 +- .../specs/server-deployment-console-workflow/spec.md | 4 ++++ .../redesign-server-deployment-workflow/tasks.md | 1 + platform_web/components/ServerDeploymentWorkflow.tsx | 10 +++++++--- platform_web/pages/ConsolePages.test.tsx | 3 +++ platform_web/theme/base-css.test.js | 7 +++++++ platform_web/theme/base.css | 2 +- 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/openspec/changes/redesign-server-deployment-workflow/design.md b/openspec/changes/redesign-server-deployment-workflow/design.md index 6ad8b85..b0a90dd 100644 --- a/openspec/changes/redesign-server-deployment-workflow/design.md +++ b/openspec/changes/redesign-server-deployment-workflow/design.md @@ -31,7 +31,7 @@ The alternative—incrementally rearranging the two current forms—would keep d The component has four steps: target (plugin plus Run or explicit draft), deployment method, configuration, and review. Forward navigation validates only fields required by the selected mode. Back navigation preserves typed values. The final review is the only submit point. -`guided-install` shows plugin game inputs and a primary “server directory”; `existing-server` shows the existing server directory; `custom-command` adds launch data. `workingDirectory` is an advanced custom-command input labelled as an execution directory and defaults logically to `serverRoot` when omitted. This preserves the current protocol distinction without demanding it from normal users. +`guided-install` shows plugin game inputs and an optional “installation directory”; `existing-server` requires an “existing server directory” and explicitly states that it will not reinstall or overwrite game configuration; `custom-command` adds launch data. `workingDirectory` is an advanced custom-command input labelled as an execution directory and defaults logically to `serverRoot` when omitted. This preserves the current protocol distinction without demanding it from normal users. ### 3. Draft is a deliberate outcome diff --git a/openspec/changes/redesign-server-deployment-workflow/specs/server-deployment-console-workflow/spec.md b/openspec/changes/redesign-server-deployment-workflow/specs/server-deployment-console-workflow/spec.md index 7d8dad0..231440e 100644 --- a/openspec/changes/redesign-server-deployment-workflow/specs/server-deployment-console-workflow/spec.md +++ b/openspec/changes/redesign-server-deployment-workflow/specs/server-deployment-console-workflow/spec.md @@ -21,6 +21,10 @@ The management console SHALL show only the deployment inputs needed by the selec - **WHEN** an operator chooses existing-server deployment - **THEN** the console requests the existing server directory and does not render installation or custom command inputs +#### Scenario: Configure a new installation +- **WHEN** an operator chooses guided-install deployment +- **THEN** the console labels the optional target as an installation directory, renders the plugin's game configuration fields, and does not present it as an existing-server adoption path + #### Scenario: Configure a custom launcher - **WHEN** an operator chooses custom-command deployment - **THEN** the console renders the required startup command and optional advanced execution directory, shell, install, stop, and status inputs diff --git a/openspec/changes/redesign-server-deployment-workflow/tasks.md b/openspec/changes/redesign-server-deployment-workflow/tasks.md index 88ddfd5..52a211c 100644 --- a/openspec/changes/redesign-server-deployment-workflow/tasks.md +++ b/openspec/changes/redesign-server-deployment-workflow/tasks.md @@ -14,3 +14,4 @@ - [x] 3.1 Add compact shared-theme workflow layout styles and remove obsolete one-page deployment form presentation. - [x] 3.2 Add focused contracts/page tests for step order, explicit drafts, field visibility, safe review, and edit availability. - [x] 3.3 Run focused frontend checks, `scripts/check-structure.sh`, and `openspec validate redesign-server-deployment-workflow --strict`; mark tasks complete only after evidence exists. +- [x] 3.4 Differentiate guided-install and existing-server configuration fields and correct single-field workflow layout stretching. diff --git a/platform_web/components/ServerDeploymentWorkflow.tsx b/platform_web/components/ServerDeploymentWorkflow.tsx index 4461fcc..8e387bd 100644 --- a/platform_web/components/ServerDeploymentWorkflow.tsx +++ b/platform_web/components/ServerDeploymentWorkflow.tsx @@ -63,6 +63,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi if (step === 0) return Boolean(form.pluginId && (saveAsDraft || form.runEndpointId)); if (step === 2) { if (kind === "create" && !form.name.trim()) return false; + if (form.deploymentMode === "existing-server" && !form.serverRoot.trim() && !deployment?.serverRootConfigured) return false; if (form.deploymentMode === "custom-command" && !form.startCommand.trim() && !deployment?.startCommandConfigured) return false; return pluginFields.filter((field) => field.required).every((field) => Boolean(form.createInputs[field.key]?.trim())); } @@ -94,8 +95,10 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi {step === 2 &&
{kind === "create" && } {kind === "create" && } - - {pluginFields.map((field) => ( + {form.deploymentMode === "guided-install" && } + {form.deploymentMode === "existing-server" && } + {form.deploymentMode === "custom-command" && } + {form.deploymentMode === "guided-install" && pluginFields.map((field) => ( ))}
+ {form.deploymentMode === "existing-server" &&
接管只做预检与绑定不会执行安装命令,也不会把本页的默认游戏参数写进已有服务器。
} {form.deploymentMode === "custom-command" &&
高级启动设置

只有自定义启动器需要这些设置。执行目录留空时,节点以服务器目录执行。

} {kind === "create" && bindingFields.length > 0 &&
运行连接设置

用于插件声明的逻辑连接,不是服务器目录或游戏配置。

{bindingFields.map((field) => )}
}
} - {step === 3 &&
插件类型{pluginLabel(selectedPlugin, form.pluginId)}
目标{saveAsDraft ? "保存为未绑定草稿" : endpointLabel(endpoints.find((endpoint) => endpoint.id === form.runEndpointId), form.runEndpointId)}
部署方式{form.deploymentMode === "guided-install" ? "新建并安装" : form.deploymentMode === "existing-server" ? "接管已有服务器" : "自定义启动方式"}
服务器目录{protectedState(form.serverRoot, Boolean(deployment?.serverRootConfigured))}
{form.deploymentMode === "custom-command" && <>
启动命令{protectedState(form.startCommand, Boolean(deployment?.startCommandConfigured))}
执行目录{protectedState(form.workingDirectory, Boolean(deployment?.workingDirectoryConfigured))}
}
游戏配置{Object.keys(form.createInputs).length ? `${Object.keys(form.createInputs).length} 项已准备` : "使用插件默认值"}
{saveAsDraft ? "本次只保存定义" : activeServer ? "本次只保存部署设置" : "确认后将创建并派发部署"}{saveAsDraft ? "后续从服务器详情选择节点并部署。" : activeServer ? "保存后可在详情中明确发起部署;路径和命令不会显示原文。" : "Run 会在领取任务后执行本机预检,再进行安装、配置与启动。"}
} + {step === 3 &&
插件类型{pluginLabel(selectedPlugin, form.pluginId)}
目标{saveAsDraft ? "保存为未绑定草稿" : endpointLabel(endpoints.find((endpoint) => endpoint.id === form.runEndpointId), form.runEndpointId)}
部署方式{form.deploymentMode === "guided-install" ? "新建并安装" : form.deploymentMode === "existing-server" ? "接管已有服务器" : "自定义启动方式"}
{form.deploymentMode === "guided-install" ? "安装目录" : form.deploymentMode === "existing-server" ? "已有服务器目录" : "服务器目录"}{protectedState(form.serverRoot, Boolean(deployment?.serverRootConfigured))}
{form.deploymentMode === "custom-command" && <>
启动命令{protectedState(form.startCommand, Boolean(deployment?.startCommandConfigured))}
执行目录{protectedState(form.workingDirectory, Boolean(deployment?.workingDirectoryConfigured))}
}{form.deploymentMode === "guided-install" &&
游戏配置{Object.keys(form.createInputs).length ? `${Object.keys(form.createInputs).length} 项已准备` : "使用插件默认值"}
}
{saveAsDraft ? "本次只保存定义" : activeServer ? "本次只保存部署设置" : "确认后将创建并派发部署"}{saveAsDraft ? "后续从服务器详情选择节点并部署。" : form.deploymentMode === "existing-server" ? "Run 将先预检现有目录;不会重装或覆盖已有游戏配置。" : activeServer ? "保存后可在详情中明确发起部署;路径和命令不会显示原文。" : "Run 会在领取任务后执行本机预检,再进行安装、配置与启动。"}
}
{step < workflowSteps.length - 1 ? : }
; diff --git a/platform_web/pages/ConsolePages.test.tsx b/platform_web/pages/ConsolePages.test.tsx index 9a5c2b4..7efe489 100644 --- a/platform_web/pages/ConsolePages.test.tsx +++ b/platform_web/pages/ConsolePages.test.tsx @@ -179,6 +179,9 @@ describe("first-party console pages", () => { expect(serverDeploymentWorkflowSource).toContain("仅保存为草稿"); expect(serverDeploymentWorkflowSource).toContain("执行目录(可选)"); expect(serverDeploymentWorkflowSource).toContain("默认使用服务器目录"); + expect(serverDeploymentWorkflowSource).toContain("安装目录(可选)"); + expect(serverDeploymentWorkflowSource).toContain("已有服务器目录"); + expect(serverDeploymentWorkflowSource).toContain("不会重装或覆盖现有游戏配置"); expect(serverDeploymentWorkflowSource).toContain("运行连接设置"); expect(serverDeploymentWorkflowSource).toContain('type={field.sensitive ? "password" : "text"}'); expect(serversPageSource).toContain("serverCreateRequestFromForm(nextForm)"); diff --git a/platform_web/theme/base-css.test.js b/platform_web/theme/base-css.test.js index 0d25941..4afe6bb 100644 --- a/platform_web/theme/base-css.test.js +++ b/platform_web/theme/base-css.test.js @@ -165,6 +165,13 @@ describe("platform web shared theme CSS", () => { expect(css).toContain("max-height:calc(100dvh-20px)"); }); + it("keeps deployment workflow fields compact when a mode has one input", () => { + const css = compact(readThemeCss()); + + expect(css).toContain(".deployment-workflow-body{display:grid;align-content:start;gap:12px;min-height:0}"); + expect(css).toContain(".deployment-workflow-body.form-grid{align-content:start;align-items:start;grid-auto-rows:min-content}"); + }); + it("keeps magical card decoration from becoming square background patches", () => { const css = compact(readThemeCss()); diff --git a/platform_web/theme/base.css b/platform_web/theme/base.css index 863a42e..728d448 100644 --- a/platform_web/theme/base.css +++ b/platform_web/theme/base.css @@ -495,7 +495,7 @@ to{transform:translate(-50%,-50%) rotate(calc(var(--construct-drift) + 360deg))} .management-dialog-panel .management-form,.management-dialog-panel .provider-form{border:0;padding:0;background:0 0;box-shadow:none;backdrop-filter:none} .management-dialog-panel .management-form{grid-template-columns:repeat(2,minmax(0,1fr))} .management-dialog-panel .dialog-form .confirm-actions,.management-dialog-panel .dialog-form .role-selector{grid-column:1/-1} -.server-deployment-workflow{display:grid;gap:14px}.deployment-workflow-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0;padding:0;list-style:none}.deployment-workflow-steps li{display:flex;align-items:center;gap:6px;min-height:38px;padding:8px;border:1px solid var(--line);border-radius:8px;background:var(--control-surface);color:var(--ink-faint);font-size:12px}.deployment-workflow-steps li>span{display:inline-flex;align-items:center;justify-content:center;color:var(--ink-soft)}.deployment-workflow-steps strong{font-size:12px}.deployment-workflow-step-active{border-color:var(--accent)!important;background:var(--primary-command-surface)!important;color:#fff!important;box-shadow:0 0 16px var(--accent-soft)}.deployment-workflow-step-complete{border-color:color-mix(in srgb,var(--teal) 68%,var(--line));color:var(--ink)!important}.deployment-workflow-body{display:grid;gap:12px;min-height:286px}.deployment-draft-choice{grid-template-columns:auto minmax(0,1fr);align-items:start;padding:10px 12px;border:1px dashed var(--line-strong);border-radius:8px;background:color-mix(in srgb,var(--surface-solid) 62%,transparent)}.deployment-draft-choice input{width:auto!important;min-height:auto!important;margin-top:3px}.deployment-draft-choice span{display:grid;gap:3px}.deployment-draft-choice small{color:var(--ink-faint);font-weight:600;line-height:1.4}.deployment-mode-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.deployment-mode-option{display:grid;align-content:start;gap:5px;min-height:130px;padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--control-surface);color:var(--ink-soft);cursor:pointer;text-align:left}.deployment-mode-option strong{color:var(--ink);font-size:14px}.deployment-mode-option span{font-size:12px;line-height:1.45}.deployment-mode-option:hover,.deployment-mode-option:focus-visible,.deployment-mode-option-active{border-color:var(--accent);outline:0;box-shadow:inset 0 1px 0 var(--crystal-rim),0 0 0 2px var(--accent-soft);background:var(--menu-item-active-bg)}.deployment-review{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.deployment-review>div{display:grid;gap:3px;padding:10px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--surface-solid) 64%,transparent)}.deployment-review span{color:var(--ink-faint);font-size:11.5px;font-weight:700}.deployment-review strong{color:var(--ink);font-size:13px;overflow-wrap:anywhere} +.server-deployment-workflow{display:grid;gap:14px}.deployment-workflow-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:0;padding:0;list-style:none}.deployment-workflow-steps li{display:flex;align-items:center;gap:6px;min-height:38px;padding:8px;border:1px solid var(--line);border-radius:8px;background:var(--control-surface);color:var(--ink-faint);font-size:12px}.deployment-workflow-steps li>span{display:inline-flex;align-items:center;justify-content:center;color:var(--ink-soft)}.deployment-workflow-steps strong{font-size:12px}.deployment-workflow-step-active{border-color:var(--accent)!important;background:var(--primary-command-surface)!important;color:#fff!important;box-shadow:0 0 16px var(--accent-soft)}.deployment-workflow-step-complete{border-color:color-mix(in srgb,var(--teal) 68%,var(--line));color:var(--ink)!important}.deployment-workflow-body{display:grid;align-content:start;gap:12px;min-height:0}.deployment-workflow-body .form-grid{align-content:start;align-items:start;grid-auto-rows:min-content}.deployment-workflow-body .form-grid>label{align-content:start}.deployment-draft-choice{grid-template-columns:auto minmax(0,1fr);align-items:start;padding:10px 12px;border:1px dashed var(--line-strong);border-radius:8px;background:color-mix(in srgb,var(--surface-solid) 62%,transparent)}.deployment-draft-choice input{width:auto!important;min-height:auto!important;margin-top:3px}.deployment-draft-choice span{display:grid;gap:3px}.deployment-draft-choice small{color:var(--ink-faint);font-weight:600;line-height:1.4}.deployment-mode-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.deployment-mode-option{display:grid;align-content:start;gap:5px;min-height:130px;padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--control-surface);color:var(--ink-soft);cursor:pointer;text-align:left}.deployment-mode-option strong{color:var(--ink);font-size:14px}.deployment-mode-option span{font-size:12px;line-height:1.45}.deployment-mode-option:hover,.deployment-mode-option:focus-visible,.deployment-mode-option-active{border-color:var(--accent);outline:0;box-shadow:inset 0 1px 0 var(--crystal-rim),0 0 0 2px var(--accent-soft);background:var(--menu-item-active-bg)}.deployment-review{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.deployment-review>div{display:grid;gap:3px;padding:10px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--surface-solid) 64%,transparent)}.deployment-review span{color:var(--ink-faint);font-size:11.5px;font-weight:700}.deployment-review strong{color:var(--ink);font-size:13px;overflow-wrap:anywhere} .management-dialog-panel .plugin-detail-panel,.management-dialog-panel .plugin-group{border:0;padding:0;background:0 0;box-shadow:none;clip-path:none} .management-dialog-panel .plugin-group::after,.management-dialog-panel .plugin-group::before{content:none} .plugin-detail-actions{align-items:stretch}