diff --git a/platform_web/components/ServerDeploymentWorkflow.test.tsx b/platform_web/components/ServerDeploymentWorkflow.test.tsx index 6f4e75c..5ff240c 100644 --- a/platform_web/components/ServerDeploymentWorkflow.test.tsx +++ b/platform_web/components/ServerDeploymentWorkflow.test.tsx @@ -74,6 +74,34 @@ describe("ServerDeploymentWorkflow", () => { expect(Array.from(container.querySelectorAll('input[type="number"]')).map((input) => input.value)).toEqual(["28000", "27015"]); }); + it("shows plugin startup inputs when editing an adopted SCUM server", async () => { + container = document.createElement("div"); + document.body.append(container); + root = createRoot(container); + const scumPlugin: GamePluginResponse = { ...plugin, id: "game.scum", name: "SCUM Server", serverType: "scum", serverDisplayName: "SCUM Dedicated Server", createFields: [{ key: "gamePort", label: "游戏端口", type: "port", required: true, defaultValue: "7779" }, { key: "queryPort", label: "查询端口", type: "port", required: true, defaultValue: "27015" }, { key: "maxPlayers", label: "最大玩家数", type: "number", required: true, defaultValue: "128" }] }; + const initialForm = { ...defaultServerCreateForm([scumPlugin]), name: "Moon Base", deploymentMode: "existing-server" as const, createInputs: { gamePort: "28888" } }; + + await act(async () => { + root?.render( + undefined} + onSubmit={async () => undefined} + /> + ); + }); + + expect(container.textContent).toContain("游戏端口"); + expect(container.textContent).toContain("查询端口"); + expect(container.textContent).toContain("最大玩家数"); + expect(container.textContent).toContain("接管流程不会把这些值写回或覆盖已有游戏配置"); + expect(Array.from(container.querySelectorAll('input[type="number"]')).map((input) => input.value)).toEqual(["28888", "27015", "128"]); + }); + it("normalizes the create wizard to canonical plugins with visible guided inputs", async () => { container = document.createElement("div"); document.body.append(container); @@ -191,6 +219,74 @@ describe("ServerDeploymentWorkflow", () => { expect(submitted?.deployment && "profileKey" in submitted.deployment).toBe(false); expect(submitted?.deployment && "runtimeBindings" in submitted.deployment).toBe(false); }); + + it("submits SCUM port and max player inputs from the existing-server create wizard", async () => { + container = document.createElement("div"); + document.body.append(container); + root = createRoot(container); + const scumPlugin: GamePluginResponse = { ...plugin, id: "game.scum", name: "SCUM Server", serverType: "scum", serverDisplayName: "SCUM Dedicated Server", createFields: [{ key: "gamePort", label: "游戏端口", type: "port", required: true, defaultValue: "7779" }, { key: "queryPort", label: "查询端口", type: "port", required: true, defaultValue: "27015" }, { key: "maxPlayers", label: "最大玩家数", type: "number", required: true, defaultValue: "128" }] }; + const initialForm = defaultServerCreateForm([scumPlugin]); + let submitted: ReturnType | undefined; + const onSubmit = vi.fn(async (form: typeof initialForm) => { + submitted = serverCreateRequestFromForm(form, 18); + }); + + await act(async () => { + root?.render( + undefined} + onSubmit={onSubmit} + /> + ); + }); + + const nameInput = container.querySelector('input[name="name"]'); + if (!nameInput) throw new Error("server name input not found"); + await act(async () => { + setInputValue(nameInput, "SCUM Existing Server"); + }); + + await submitWorkflow(container); + await clickButtonByText(container, "接管已有服务器"); + await submitWorkflow(container); + + expect(container.textContent).toContain("已有服务器目录"); + expect(container.textContent).toContain("游戏端口"); + expect(container.textContent).toContain("查询端口"); + expect(container.textContent).toContain("最大玩家数"); + expect(Array.from(container.querySelectorAll('input[type="number"]')).map((input) => input.value)).toEqual(["7779", "27015", "128"]); + + const rootInput = container.querySelector('input[name="serverRoot"]'); + const [gamePortInput, , maxPlayersInput] = Array.from(container.querySelectorAll('input[type="number"]')); + if (!rootInput || !gamePortInput || !maxPlayersInput) throw new Error("SCUM existing-server inputs not found"); + await act(async () => { + setInputValue(rootInput, "/srv/scum-existing"); + setInputValue(gamePortInput, "28001"); + setInputValue(maxPlayersInput, "64"); + }); + + await submitWorkflow(container); + expect(container.textContent).toContain("本次保存创建向导配置"); + expect(container.textContent).toContain("游戏配置"); + expect(container.textContent).toContain("3 项已准备"); + + await submitWorkflow(container); + expect(onSubmit).toHaveBeenCalledTimes(1); + expect(submitted).toMatchObject({ + id: "server-scum-existing-server-18", + pluginId: "game.scum", + name: "SCUM Existing Server", + deployment: { + mode: "existing-server", + serverRoot: "/srv/scum-existing", + createInputs: { gamePort: "28001", queryPort: "27015", maxPlayers: "64" } + } + }); + }); }); async function submitWorkflow(target: HTMLElement) { diff --git a/platform_web/components/ServerDeploymentWorkflow.tsx b/platform_web/components/ServerDeploymentWorkflow.tsx index ff83ad5..1e45c63 100644 --- a/platform_web/components/ServerDeploymentWorkflow.tsx +++ b/platform_web/components/ServerDeploymentWorkflow.tsx @@ -28,6 +28,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, initialForm, dep const selectablePlugins = useMemo(() => kind === "create" ? serverCreatePluginOptions(plugins) : plugins, [kind, plugins]); const selectedPlugin = useMemo(() => selectablePlugins.find((plugin) => plugin.id === form.pluginId), [form.pluginId, selectablePlugins]); const pluginFields = selectedPlugin?.createFields ?? []; + const showsPluginCreateFields = form.deploymentMode === "guided-install" || form.deploymentMode === "existing-server"; const workflowSteps = kind === "create" ? [{ label: "基本信息", icon: Compass }, { label: "部署方式", icon: ServerCog }, { label: "相关配置", icon: FolderCog }, { label: "确认", icon: Rocket }] : [{ label: "相关配置", icon: FolderCog }, { label: "确认", icon: Rocket }]; @@ -68,7 +69,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, initialForm, dep if (step === configurationStep) { 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 form.deploymentMode !== "guided-install" || pluginFields.filter((field) => field.required).every((field) => Boolean(form.createInputs[field.key]?.trim())); + return !showsPluginCreateFields || pluginFields.filter((field) => field.required).every((field) => Boolean(form.createInputs[field.key]?.trim())); } return true; } @@ -120,7 +121,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, initialForm, dep {form.deploymentMode === "guided-install" && } {form.deploymentMode === "existing-server" && } {form.deploymentMode === "custom-command" && } - {form.deploymentMode === "guided-install" && pluginFields.map((field) => ( + {showsPluginCreateFields && pluginFields.map((field) => ( ))} - {form.deploymentMode === "guided-install" && pluginFields.length === 0 &&
当前插件没有声明可填写的创建项可以返回选择其他插件,或改用自定义启动方式填写命令、目录和启动参数。
} + {showsPluginCreateFields && pluginFields.length === 0 &&
当前插件没有声明可填写的创建项可以返回选择其他插件,或改用自定义启动方式填写命令、目录和启动参数。
} + {form.deploymentMode === "existing-server" && pluginFields.length > 0 &&
插件启动项端口、人数等参数会交给插件生命周期用于后续启动和验证;接管流程不会把这些值写回或覆盖已有游戏配置。
} {form.deploymentMode === "guided-install" && } {form.deploymentMode === "existing-server" && } {form.deploymentMode === "custom-command" &&
高级启动设置

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

} } - {step === reviewStep &&
插件类型{pluginLabel(selectedPlugin, form.pluginId)}
{kind === "create" &&
服务器名称{form.name.trim() || "未填写"}
}
部署方式{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} 项已准备` : "使用插件默认值"}
}
完成条件按插件声明完成预检、执行与验证
{kind === "create" ? "本次保存创建向导配置" : "本次只保存部署设置"}{kind === "create" ? "Run 会自动识别并上报心跳;部署执行按这里的方式和启动项进行。" : form.deploymentMode === "existing-server" ? "Run 将自动识别并预检现有目录;不会重装或覆盖已有游戏配置。" : "保存后由平台保留受保护部署设置;路径和命令仅在本次显式展示后可见。"}
} + {step === reviewStep &&
插件类型{pluginLabel(selectedPlugin, form.pluginId)}
{kind === "create" &&
服务器名称{form.name.trim() || "未填写"}
}
部署方式{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))}
}{showsPluginCreateFields &&
游戏配置{Object.keys(form.createInputs).length ? `${Object.keys(form.createInputs).length} 项已准备` : "使用插件默认值"}
}
完成条件按插件声明完成预检、执行与验证
{kind === "create" ? "本次保存创建向导配置" : "本次只保存部署设置"}{kind === "create" ? "Run 会自动识别并上报心跳;部署执行按这里的方式和启动项进行。" : form.deploymentMode === "existing-server" ? "Run 将自动识别并预检现有目录;不会重装或覆盖已有游戏配置。" : "保存后由平台保留受保护部署设置;路径和命令仅在本次显式展示后可见。"}
}
{step < reviewStep ? : }
;