@@ -31,6 +31,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
const activeServer = kind === "edit" && Boolean ( deployment ) ;
const isScum = selectedPlugin ? . id === "game.scum" ;
const needsTargetSelection = kind === "create" || ! initialForm . runEndpointId ;
const selectedTargetID = kind === "create" ? form.deploymentTargetId : form.runEndpointId ;
const workflowSteps = kind === "create"
? [ { label : "选择目标" , icon : Compass } , { label : "部署方式" , icon : ServerCog } , { label : "相关配置" , icon : FolderCog } , { label : "确认" , icon : Rocket } ]
: needsTargetSelection
@@ -64,7 +65,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
function updateBinding ( key : string , value : string ) { setForm ( ( current ) = > ( { . . . current , bindings : { . . . current . bindings , [ key ] : value } } ) ) ; }
function canContinue() {
if ( step === targetStep ) return kind === "create" ? Boolean ( form . pluginId && ( saveAsDraft || form . runEndpointId ) ) : Boolean ( form . runEndpointId ) ;
if ( step === targetStep ) return kind === "create" ? Boolean ( form . pluginId && ( saveAsDraft || form . deploymentTargetId ) ) : Boolean ( form . runEndpointId ) ;
if ( step === configurationStep ) {
if ( kind === "create" && ! form . name . trim ( ) ) return false ;
if ( isScum && form . deploymentMode === "guided-install" && ! form . serverRoot . trim ( ) && ! deployment ? . serverRootConfigured ) return false ;
@@ -78,19 +79,19 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
async function submit ( event : FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
if ( step < reviewStep ) { if ( canContinue ( ) ) setStep ( ( current ) = > current + 1 ) ; return ; }
await onSubmit ( { . . . form , runEndpointId : saveAsDraft ? "" : form . runEndpointId } , saveAsDraft ) ;
await onSubmit ( { . . . form , deploymentTargetId : saveAsDraft ? "" : form . deploymentTargetId , runEndpointId : saveAsDraft ? "" : form . runEndpointId } , saveAsDraft ) ;
}
const protectedState = ( nextValue : string , configured : boolean ) = > nextValue . trim ( ) ? "将替换" : configured ? "保持已配置" : "未配置" ;
const actionLabel = kind === "create" ? ( saveAsDraft ? "保存草稿" : "创建、部署并前往构建 Run") : "保存部署设置" ;
const actionLabel = kind === "create" ? "保存草稿并准备专属 Run" : "保存部署设置" ;
return < ManagementDialog open = { open } title = { kind === "create" ? "创建服务器" : "编辑部署" } description = { kind === "create" ? "按部署顺序完成设置;路径和命令始终受保护,不会在确认页或日志中回显。" : "仅停止中的服务器可以修改部署设置。受保护路径和命令留空会保持原值。" } wide onClose = { ( ) = > { if ( ! busy ) onClose ( ) ; } } >
< form className = "provider-form dialog-form server-deployment-workflow" onSubmit = { ( event ) = > void submit ( event ) } aria-label = { kind === "create" ? "创建服务器部署向导" : "编辑服务器部署向导" } >
< ol className = "deployment-workflow-steps" style = { { gridTemplateColumns : ` repeat( ${ workflowSteps . length } , minmax(0, 1fr)) ` } } aria-label = "部署步骤" > { workflowSteps . map ( ( item , index ) = > { const Icon = item . icon ; return < li key = { item . label } className = { cx ( index === step && "deployment-workflow-step-active" , index < step && "deployment-workflow-step-complete" ) } > < span > { index < step ? < CheckCircle2 size = { 15 } / > : < Icon size = { 15 } / > } < / span > < strong > { index + 1 } . { item . label } < / strong > < / li > ; } ) } < / ol >
{ step === targetStep && < div className = "deployment-workflow-body" >
{ kind === "create" ? < div className = "workflow-hint-grid" > < div className = "workflow-hint-card" > < strong > 选 择 游 戏 < / strong > < span > 插 件 决 定 新 建 时 需 要 的 游 戏 配 置 与 启 动 预 设 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 选 择 运 行 节 点 < / strong > < span > Run 在 目 标 主 机 预 检 并 执 行 ; 平 台 不 会 直 接 访 问 主 机 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 准 备 服 务 器 目 录 < / strong > < span > 下 一 步 选 择 新 建 、 接 管 或 自 定 义 方 式 , 再 填 写 对 应 目 录 。 < / span > < / div > < / div > : < div className = "form-guidance" > < strong > 这 个 草 稿 尚 未 绑 定 运 行 节 点 < / strong > < span > 只 需 在 这 里 补 选 一 次 。 已 绑 定 服 务 器 编 辑 时 会 直 接 进 入 相 关 配 置 , 不 会 重 复 要 求 选 择 目 标 。 < / span > < / div > }
< div className = "form-grid" > { kind === "create" && < label > 插 件 类 型 < select name = "pluginId" value = { form . pluginId } onChange = { updateForm } required > { plugins . map ( ( plugin ) = > < option key = { plugin . id } value = { plugin . id } > { pluginLabel ( plugin , plugin . id ) } < / option > ) } < / select > < / label > } < label > 运 行 节 点 < select name = "runEndpointId" value = { form . runEndpointId } onChange = { updateForm } disabled = { kind === "create" && saveAsDraft } required = { kind !== "create" || ! saveAsDraft } > < option value = "" > 请 选 择 运 行 节 点 < / option > { endpoints . map ( ( endpoint ) = > < option key = { endpoint . id } value = { endpoint . id } > { endpointLabel ( endpoint , endpoint . id ) } < / option > ) } < / select > < / label > < / div >
{ kind === "create" && < label className = "deployment-draft-choice" > < input type = "checkbox" checked = { saveAsDraft } onChange = { ( event ) = > setSaveAsDraft ( event . target . checked ) } / > < span > < strong > 仅 保 存 为 草 稿 < / strong > < small > 暂 不 指 定 运 行 节 点 ; 不 会 安 装 、 预 检 或 派 发 任 务 。 < / small > < / span > < / label > }
{ kind === "create" ? < div className = "workflow-hint-grid" > < div className = "workflow-hint-card" > < strong > 选 择 游 戏 < / strong > < span > 插 件 决 定 新 建 时 需 要 的 游 戏 配 置 与 启 动 预 设 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 选 择 部 署 目 标 < / strong > < span > 受 信 任 的 构 建 节 点 会 生 成 专 属 Run ; 它 不 是 这 台 服 务 器 的 Run 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 注 册 专 属 Run < / strong > < span > 草 稿 保 存 后 生 成 并 启 动 专 属 Run , 注 册 成 功 才 可 部 署 。 < / span > < / div > < / div > : < div className = "form-guidance" > < strong > 这 个 草 稿 尚 未 绑 定 运 行 节 点 < / strong > < span > 只 需 在 这 里 补 选 一 次 。 已 绑 定 服 务 器 编 辑 时 会 直 接 进 入 相 关 配 置 , 不 会 重 复 要 求 选 择 目 标 。 < / span > < / div > }
< div className = "form-grid" > { kind === "create" && < label > 插 件 类 型 < select name = "pluginId" value = { form . pluginId } onChange = { updateForm } required > { plugins . map ( ( plugin ) = > < option key = { plugin . id } value = { plugin . id } > { pluginLabel ( plugin , plugin . id ) } < / option > ) } < / select > < / label > } < label > { kind === "create" ? "部署目标" : "运行节点" } < select name = { kind === "create" ? "deploymentTargetId" : "runEndpointId" } value = { selectedTargetID } onChange = { updateForm } disabled = { kind === "create" && saveAsDraft } required = { kind !== "create" || ! saveAsDraft } > < option value = "" > { kind === "create" ? "请选择部署目标" : "请选择运行节点" } < / option > { endpoints . map ( ( endpoint ) = > < option key = { endpoint . id } value = { endpoint . id } > { endpointLabel ( endpoint , endpoint . id ) } < / option > ) } < / select > < / label > < / div >
{ kind === "create" && < label className = "deployment-draft-choice" > < input type = "checkbox" checked = { saveAsDraft } onChange = { ( event ) = > setSaveAsDraft ( event . target . checked ) } / > < span > < strong > 暂 不 指 定 部 署 目 标 < / strong > < small > 仅 保 存 服 务 器 草 稿 ; 稍 后 选 择 目 标 、 生 成 并 注 册 专 属 Run 。 < / small > < / span > < / label > }
< / div > }
{ step === modeStep && < div className = "deployment-workflow-body" > < p className = "section-copy" > 选 择 你 想 让 平 台 承 担 的 方 式 。 后 续 只 显 示 这 一 方 式 需 要 的 字 段 。 < / p > { isScum && < div className = "form-guidance" > < strong > SCUM 受 控 链 路 < / strong > < span > Run 会 按 预 检 → 安 装 或 扫 描 → 配 置 映 射 → 健 康 验 证 执 行 ; 目 录 本 身 不 代 表 安 装 完 成 。 < / span > < / div > } < div className = "deployment-mode-grid" >
< ModeOption active = { form . deploymentMode === "guided-install" } title = "新建并安装" copy = "按插件的推荐方案安装并写入游戏配置。适合绝大多数新服务器。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "guided-install" } ) ) } / >
@@ -117,7 +118,7 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
{ form . deploymentMode === "custom-command" && < details className = "provider-advanced-settings" open > < summary > 高 级 启 动 设 置 < / summary > < p className = "field-help" > 只 有 自 定 义 启 动 器 需 要 这 些 设 置 。 执 行 目 录 留 空 时 , 节 点 以 服 务 器 目 录 执 行 。 < / p > < div className = "form-grid" > < label > 启 动 命 令 < input name = "startCommand" value = { form . startCommand } onChange = { updateForm } placeholder = { deployment ? . startCommandConfigured ? "留空保持已配置启动命令" : "必填,例如 ./start-server" } autoComplete = "off" required = { ! deployment ? . startCommandConfigured } / > < / label > < label > 命 令 解 释 器 < select name = "shell" value = { form . shell } onChange = { updateForm } > < option value = "" > 直 接 argv ( 推 荐 ) < / option > < option value = "posix-sh" > POSIX sh < / option > < option value = "powershell" > PowerShell < / option > < option value = "cmd" > Windows cmd < / option > < / select > < / label > < label > 执 行 目 录 ( 可 选 ) < input name = "workingDirectory" value = { form . workingDirectory } onChange = { updateForm } placeholder = { deployment ? . workingDirectoryConfigured ? "留空保持已配置执行目录" : "默认使用服务器目录" } autoComplete = "off" / > < / label > < label > 安 装 命 令 ( 可 选 ) < input name = "installCommand" value = { form . installCommand } onChange = { updateForm } autoComplete = "off" placeholder = "留空保持原值或不使用" / > < / label > < label > 停 止 命 令 ( 可 选 ) < input name = "stopCommand" value = { form . stopCommand } onChange = { updateForm } autoComplete = "off" / > < / label > < label > 状 态 命 令 ( 可 选 ) < input name = "statusCommand" value = { form . statusCommand } onChange = { updateForm } autoComplete = "off" / > < / label > < / div > < / details > }
{ kind === "create" && bindingFields . length > 0 && < details className = "provider-advanced-settings" > < summary > 运 行 连 接 设 置 < / summary > < p className = "field-help" > 用 于 插 件 声 明 的 逻 辑 连 接 , 不 是 服 务 器 目 录 或 游 戏 配 置 。 < / p > < div className = "form-grid" > { bindingFields . map ( ( field ) = > < label key = { field . key } > { field . key } { field . required ? "(必填)" : "" } < input type = { field . sensitive ? "password" : "text" } autoComplete = "off" value = { form . bindings [ field . key ] ? ? "" } onChange = { ( event ) = > updateBinding ( field . key , event . target . value ) } placeholder = { field . sensitive ? "托管凭据引用" : "安全逻辑值" } required = { field . required } / > < / label > ) } < / div > < / details > }
< / div > }
{ step === reviewStep && < div className = "deployment-workflow-body" > < div className = "deployment-review" > < div > < span > 插 件 类 型 < / span > < strong > { pluginLabel ( selectedPlugin , form . pluginId ) } < / strong > < / div > < div > < span > 目 标 < / span > < strong > { saveAsDraft ? "保存为未绑定 草稿" : endpointLabel ( endpoints . find ( ( endpoint ) = > endpoint . id === form . runEndpointId ) , form . runEndpointId ) } < / strong > < / div > < div > < span > 部 署 方 式 < / span > < strong > { form . deploymentMode === "guided-install" ? "新建并安装" : form . deploymentMode === "existing-server" ? "接管已有服务器" : "自定义启动方式" } < / strong > < / div > < div > < span > { form . deploymentMode === "guided-install" ? "安装目录" : form . deploymentMode === "existing-server" ? "已有服务器目录" : "服务器目录" } < / span > < strong > { protectedState ( form . serverRoot , Boolean ( deployment ? . serverRootConfigured ) ) } < / strong > < / div > { form . deploymentMode === "custom-command" && < > < div > < span > 启 动 命 令 < / span > < strong > { protectedState ( form . startCommand , Boolean ( deployment ? . startCommandConfigured ) ) } < / strong > < / div > < div > < span > 执 行 目 录 < / span > < strong > { protectedState ( form . workingDirectory , Boolean ( deployment ? . workingDirectoryConfigured ) ) } < / strong > < / div > < / > } { form . deploymentMode === "guided-install" && < div > < span > 游 戏 配 置 < / span > < strong > { Object . keys ( form . createInputs ) . length ? ` ${ Object . keys ( form . createInputs ) . length } 项已准备 ` : "使用插件默认值" } < / strong > < / div > } { isScum && < div > < span > 完 成 条 件 < / span > < strong > 安 装 / 扫 描 、 映 射 、 验 证 全 部 通 过 < / strong > < / div > } < / div > < div className = "form-guidance" > < strong > { saveAsDraft ? "本次只 保存定义 " : activeServer ? "本次只保存部署设置" : "确认后将创建、部署并打开 Run 构建 " } < / strong > < span > { saveAsDraft ? "后续从服务器详情选择节点并 部署。" : form . deploymentMode === "existing-server" ? "Run 将先预检现有目录;不会重装或覆盖已有游戏配置。" : activeServer ? "保存后可在详情中明确发起部署;路径和命令不会显示原文。" : isScum ? "Run 只有在 SCUM 配置映射与健康验证通过后才会报告安装成功。" : "Run 会在领取任务后执行本机预检,再进行安装、配置与启动。" } < / span > < / div > < / div > }
{ step === reviewStep && < div className = "deployment-workflow-body" > < div className = "deployment-review" > < div > < span > 插 件 类 型 < / span > < strong > { pluginLabel ( selectedPlugin , form . pluginId ) } < / strong > < / div > < div > < span > { kind === "create" ? "部署目标" : "目标" } < / span > < strong > { saveAsDraft ? "保存为未指定目标的 草稿" : endpointLabel ( endpoints . find ( ( endpoint ) = > endpoint . id === selectedTargetID ) , selectedTargetID ) } < / strong > < / div > < div > < span > 部 署 方 式 < / span > < strong > { form . deploymentMode === "guided-install" ? "新建并安装" : form . deploymentMode === "existing-server" ? "接管已有服务器" : "自定义启动方式" } < / strong > < / div > < div > < span > { form . deploymentMode === "guided-install" ? "安装目录" : form . deploymentMode === "existing-server" ? "已有服务器目录" : "服务器目录" } < / span > < strong > { protectedState ( form . serverRoot , Boolean ( deployment ? . serverRootConfigured ) ) } < / strong > < / div > { form . deploymentMode === "custom-command" && < > < div > < span > 启 动 命 令 < / span > < strong > { protectedState ( form . startCommand , Boolean ( deployment ? . startCommandConfigured ) ) } < / strong > < / div > < div > < span > 执 行 目 录 < / span > < strong > { protectedState ( form . workingDirectory , Boolean ( deployment ? . workingDirectoryConfigured ) ) } < / strong > < / div > < / > } { form . deploymentMode === "guided-install" && < div > < span > 游 戏 配 置 < / span > < strong > { Object . keys ( form . createInputs ) . length ? ` ${ Object . keys ( form . createInputs ) . length } 项已准备 ` : "使用插件默认值" } < / strong > < / div > } { isScum && < div > < span > 完 成 条 件 < / span > < strong > 安 装 / 扫 描 、 映 射 、 验 证 全 部 通 过 < / strong > < / div > } < / div > < div className = "form-guidance" > < strong > { kind === "create" ? "本次保存草稿并保留专属 Run 身份 " : activeServer ? "本次只保存部署设置" : "本次只保存部署设置 " } < / strong > < span > { kind === "create" ? "随后生成并启动专属 Run;它完成注册后,才能明确发起 部署。" : form . deploymentMode === "existing-server" ? "Run 将先预检现有目录;不会重装或覆盖已有游戏配置。" : "保存后可在详情中明确发起部署;路径和命令不会显示原文。" } < / span > < / div > < / div > }
< div className = "confirm-actions" > < button type = "button" disabled = { busy } onClick = { ( ) = > step === 0 ? onClose ( ) : setStep ( ( current ) = > current - 1 ) } > { step === 0 ? "取消" : "上一步" } < / button > { step < reviewStep ? < button type = "submit" className = "confirm-primary" disabled = { busy || ! canContinue ( ) } > < CircleDashed size = { 16 } / > < span > 下 一 步 < / span > < / button > : < button type = "submit" className = "confirm-primary" disabled = { busy } > < Rocket size = { 16 } / > < span > { busy ? "保存中…" : actionLabel } < / span > < / button > } < / div >
< / form >
< / ManagementDialog > ;