@@ -20,13 +20,6 @@ interface ServerDeploymentWorkflowProps {
onSubmit : ( form : ServerCreateFormState , saveAsDraft : boolean ) = > Promise < void > ;
onSubmit : ( form : ServerCreateFormState , saveAsDraft : boolean ) = > Promise < void > ;
}
}
const workflowSteps = [
{ label : "选择目标" , icon : Compass } ,
{ label : "部署方式" , icon : ServerCog } ,
{ label : "相关配置" , icon : FolderCog } ,
{ label : "确认" , icon : Rocket }
] ;
export function ServerDeploymentWorkflow ( { open , kind , plugins , endpoints , initialForm , deployment , busy = false , onClose , onSubmit } : ServerDeploymentWorkflowProps ) {
export function ServerDeploymentWorkflow ( { open , kind , plugins , endpoints , initialForm , deployment , busy = false , onClose , onSubmit } : ServerDeploymentWorkflowProps ) {
const [ step , setStep ] = useState ( 0 ) ;
const [ step , setStep ] = useState ( 0 ) ;
const [ form , setForm ] = useState < ServerCreateFormState > ( initialForm ) ;
const [ form , setForm ] = useState < ServerCreateFormState > ( initialForm ) ;
@@ -37,11 +30,21 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
const bindingFields = runtimeBindingFields ( selectedPlugin , form . profileKey ) ;
const bindingFields = runtimeBindingFields ( selectedPlugin , form . profileKey ) ;
const activeServer = kind === "edit" && Boolean ( deployment ) ;
const activeServer = kind === "edit" && Boolean ( deployment ) ;
const isScum = selectedPlugin ? . id === "game.scum" ;
const isScum = selectedPlugin ? . id === "game.scum" ;
const needsTargetSelection = kind === "create" || ! initialForm . runEndpointId ;
const workflowSteps = kind === "create"
? [ { label : "选择目标" , icon : Compass } , { label : "部署方式" , icon : ServerCog } , { label : "相关配置" , icon : FolderCog } , { label : "确认" , icon : Rocket } ]
: needsTargetSelection
? [ { label : "选择运行节点" , icon : Compass } , { label : "相关配置" , icon : FolderCog } , { label : "确认" , icon : Rocket } ]
: [ { label : "相关配置" , icon : FolderCog } , { label : "确认" , icon : Rocket } ] ;
const targetStep = needsTargetSelection ? 0 : - 1 ;
const modeStep = kind === "create" ? 1 : - 1 ;
const configurationStep = kind === "create" ? 2 : needsTargetSelection ? 1 : 0 ;
const reviewStep = workflowSteps . length - 1 ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
if ( ! open ) return ;
if ( ! open ) return ;
setStep ( 0 ) ;
setStep ( 0 ) ;
setSaveAsDraft ( ! initialForm . runEndpointId ) ;
setSaveAsDraft ( kind === "create" && ! initialForm . runEndpointId ) ;
setForm ( initialForm ) ;
setForm ( initialForm ) ;
} , [ initialForm , kind , open ] ) ;
} , [ initialForm , kind , open ] ) ;
@@ -61,8 +64,8 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
function updateBinding ( key : string , value : string ) { setForm ( ( current ) = > ( { . . . current , bindings : { . . . current . bindings , [ key ] : value } } ) ) ; }
function updateBinding ( key : string , value : string ) { setForm ( ( current ) = > ( { . . . current , bindings : { . . . current . bindings , [ key ] : value } } ) ) ; }
function canContinue() {
function canContinue() {
if ( step === 0 ) return Boolean ( form . pluginId && ( saveAsDraft || form . runEndpointId ) ) ;
if ( step === targetStep ) return kind === "create" ? Boolean ( form . pluginId && ( saveAsDraft || form . runEndpointId ) ) : Boolean ( form . runEndpointId ) ;
if ( step === 2 ) {
if ( step === configurationStep ) {
if ( kind === "create" && ! form . name . trim ( ) ) return false ;
if ( kind === "create" && ! form . name . trim ( ) ) return false ;
if ( isScum && form . deploymentMode === "guided-install" && ! form . serverRoot . trim ( ) && ! deployment ? . serverRootConfigured ) return false ;
if ( isScum && form . deploymentMode === "guided-install" && ! form . serverRoot . trim ( ) && ! deployment ? . serverRootConfigured ) return false ;
if ( form . deploymentMode === "existing-server" && ! form . serverRoot . trim ( ) && ! deployment ? . serverRootConfigured ) return false ;
if ( form . deploymentMode === "existing-server" && ! form . serverRoot . trim ( ) && ! deployment ? . serverRootConfigured ) return false ;
@@ -74,29 +77,30 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
async function submit ( event : FormEvent < HTMLFormElement > ) {
async function submit ( event : FormEvent < HTMLFormElement > ) {
event . preventDefault ( ) ;
event . preventDefault ( ) ;
if ( step < workflowSteps . length - 1 ) { if ( canContinue ( ) ) setStep ( ( current ) = > current + 1 ) ; return ; }
if ( step < reviewStep ) { if ( canContinue ( ) ) setStep ( ( current ) = > current + 1 ) ; return ; }
await onSubmit ( { . . . form , runEndpointId : saveAsDraft ? "" : form . runEndpointId } , saveAsDraft ) ;
await onSubmit ( { . . . form , runEndpointId : saveAsDraft ? "" : form . runEndpointId } , saveAsDraft ) ;
}
}
const protectedState = ( nextValue : string , configured : boolean ) = > nextValue . trim ( ) ? "将替换" : configured ? "保持已配置" : "未配置" ;
const protectedState = ( nextValue : string , configured : boolean ) = > nextValue . trim ( ) ? "将替换" : configured ? "保持已配置" : "未配置" ;
const actionLabel = kind === "create" ? ( saveAsDraft ? "保存草稿" : "创建并 部署" ) : ( saveAsDraft ? "保存草稿设置" : "保存部署设置" ) ;
const actionLabel = kind === "create" ? ( saveAsDraft ? "保存草稿" : "创建、 部署并前往构建 Run " ) : "保存部署设置" ;
return < ManagementDialog open = { open } title = { kind === "create" ? "创建服务器" : "编辑部署" } description = { kind === "create" ? "按部署顺序完成设置;路径和命令始终受保护,不会在确认页或日志中回显。" : "仅停止中的服务器可以修改部署设置。受保护路径和命令留空会保持原值。" } wide onClose = { ( ) = > { if ( ! busy ) onClose ( ) ; } } >
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" ? "创建服务器部署向导" : "编辑服务器部署向导" } >
< form className = "provider-form dialog-form server-deployment-workflow" onSubmit = { ( event ) = > void submit ( event ) } aria-label = { kind === "create" ? "创建服务器部署向导" : "编辑服务器部署向导" } >
< ol className = "deployment-workflow-steps" 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 >
< 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 === 0 && < div className = "deployment-workflow-body" >
{ step === targetStep && < div className = "deployment-workflow-body" >
< div className = "workflow-hint-grid" > < div className = "workflow-hint-card" > < strong > 先 确 定 游 戏 < / strong > < span > 插 件 决 定 可 填 写 的 游 戏 配 置 与 启 动 预 设 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 再 选 择 节 点 < / strong > < span > 节 点 负 责 预 检 和 执 行 , 平 台 不 会 直 接 访 问 主 机 。 < / span > < / div > < div className = "workflow-hint-card" > < strong > 也 可 存 为 草 稿 < / strong > < span > 草 稿 不 会 发 起 部 署 , 稍 后 可 从 服 务 器 详 情 继 续 。 < / span > < / div > < / div >
{ 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" > < 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 = { saveAsDraft } required = { ! saveAsDraft } > < option value = "" > 请 选 择 运 行 节 点 < / option > { endpoints . map ( ( endpoint ) = > < option key = { endpoint . id } value = { endpoint . id } > { endpointLabel ( endpoint , endpoint . id ) } < / option > ) } < / select > < / label > < / 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 >
< label className = "deployment-draft-choice" > < input type = "checkbox" checked = { saveAsDraft } onChange = { ( event ) = > setSaveAsDraft ( event . target . checked ) } / > < span > < strong > { kind === "create" ? "仅保存为草稿" : "保持为未绑定草稿" } < / strong > < small > 暂 不 指 定 运 行 节 点 ; 不 会 安 装 、 预 检 或 派 发 任 务 。 < / small > < / span > < / label >
{ 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 > }
< / div > }
< / div > }
{ step === 1 && < 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" >
{ 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" } ) ) } / >
< ModeOption active = { form . deploymentMode === "guided-install" } title = "新建并安装" copy = "按插件的推荐方案安装并写入游戏配置。适合绝大多数新服务器。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "guided-install" } ) ) } / >
< ModeOption active = { form . deploymentMode === "existing-server" } title = "接管已有服务器" copy = "预检指定目录并接入已有实例;不会把它当作一次新安装。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "existing-server" } ) ) } / >
< ModeOption active = { form . deploymentMode === "existing-server" } title = "接管已有服务器" copy = "预检指定目录并接入已有实例;不会把它当作一次新安装。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "existing-server" } ) ) } / >
< ModeOption active = { form . deploymentMode === "custom-command" } title = "自定义启动方式" copy = "用于非标准启动器或脚本;需由节点策略允许。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "custom-command" } ) ) } / >
< ModeOption active = { form . deploymentMode === "custom-command" } title = "自定义启动方式" copy = "用于非标准启动器或脚本;需由节点策略允许。" onClick = { ( ) = > setForm ( ( current ) = > ( { . . . current , deploymentMode : "custom-command" } ) ) } / >
< / div > < / div > }
< / div > < / div > }
{ step === 2 && < div className = "deployment-workflow-body" > < div className = "form-grid" >
{ step === configurationStep && < div className = "deployment-workflow-body" > < div className = "form-grid" >
{ kind === "create" && < label > 服 务 器 名 称 < input name = "name" value = { form . name } onChange = { updateForm } placeholder = "Example Survival #3" required / > < / label > }
{ kind === "create" && < label > 服 务 器 名 称 < input name = "name" value = { form . name } onChange = { updateForm } placeholder = "Example Survival #3" required / > < / label > }
{ kind === "create" && < label > 运 行 预 设 ( 可 选 ) < select name = "profileKey" value = { form . profileKey } onChange = { updateForm } > < option value = "" > 使 用 插 件 默 认 预 设 < / option > { profileOptions . map ( ( profile ) = > < option key = { profile . key } value = { profile . key } > { profile . key } · { profile . mode } < / option > ) } < / select > < small className = "field-help" > 仅 在 插 件 提 供 多 个 兼 容 启 动 预 设 时 选 择 。 < / small > < / label > }
{ kind === "create" && < label > 运 行 预 设 ( 可 选 ) < select name = "profileKey" value = { form . profileKey } onChange = { updateForm } > < option value = "" > 使 用 插 件 默 认 预 设 < / option > { profileOptions . map ( ( profile ) = > < option key = { profile . key } value = { profile . key } > { profile . key } · { profile . mode } < / option > ) } < / select > < small className = "field-help" > 仅 在 插 件 提 供 多 个 兼 容 启 动 预 设 时 选 择 。 < / small > < / label > }
{ kind === "edit" && < label > 部 署 方 式 < select name = "deploymentMode" value = { form . deploymentMode } onChange = { updateForm } > < option value = "guided-install" > 新 建 并 安 装 < / option > < option value = "existing-server" > 接 管 已 有 服 务 器 < / option > < option value = "custom-command" > 自 定 义 启 动 方 式 < / option > < / select > < small className = "field-help" > 可 在 此 调 整 部 署 方 式 ; 不 会 重 复 要 求 选 择 已 绑 定 的 运 行 节 点 。 < / small > < / label > }
{ form . deploymentMode === "guided-install" && < label > 安 装 目 录 { isScum ? "(必填)" : "(可选)" } < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已配置安装目录" : "完整绝对路径" } autoComplete = "off" required = { isScum && ! deployment ? . serverRootConfigured } / > < small className = "field-help" > 新 建 服 务 器 的 安 装 目 标 ; SCUM 受 控 模 板 必 须 明 确 安 装 目 录 。 < / small > < / label > }
{ form . deploymentMode === "guided-install" && < label > 安 装 目 录 { isScum ? "(必填)" : "(可选)" } < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已配置安装目录" : "完整绝对路径" } autoComplete = "off" required = { isScum && ! deployment ? . serverRootConfigured } / > < small className = "field-help" > 新 建 服 务 器 的 安 装 目 标 ; SCUM 受 控 模 板 必 须 明 确 安 装 目 录 。 < / small > < / label > }
{ form . deploymentMode === "existing-server" && < label > 已 有 服 务 器 目 录 < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已接管目录" : "完整绝对路径" } autoComplete = "off" required = { ! deployment ? . serverRootConfigured } / > < small className = "field-help" > Run 会 先 预 检 目 录 、 插 件 与 端 口 ; 不 会 重 装 或 覆 盖 现 有 游 戏 配 置 。 < / small > < / label > }
{ form . deploymentMode === "existing-server" && < label > 已 有 服 务 器 目 录 < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已接管目录" : "完整绝对路径" } autoComplete = "off" required = { ! deployment ? . serverRootConfigured } / > < small className = "field-help" > Run 会 先 预 检 目 录 、 插 件 与 端 口 ; 不 会 重 装 或 覆 盖 现 有 游 戏 配 置 。 < / small > < / label > }
{ form . deploymentMode === "custom-command" && < label > 服 务 器 目 录 < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已配置目录" : "完整绝对路径" } autoComplete = "off" / > < small className = "field-help" > 服 务 器 文 件 、 数 据 与 配 置 的 主 目 录 。 < / small > < / label > }
{ form . deploymentMode === "custom-command" && < label > 服 务 器 目 录 < input name = "serverRoot" value = { form . serverRoot } onChange = { updateForm } placeholder = { deployment ? . serverRootConfigured ? "留空保持已配置目录" : "完整绝对路径" } autoComplete = "off" / > < small className = "field-help" > 服 务 器 文 件 、 数 据 与 配 置 的 主 目 录 。 < / small > < / label > }
@@ -109,12 +113,12 @@ export function ServerDeploymentWorkflow({ open, kind, plugins, endpoints, initi
) ) }
) ) }
< / div >
< / div >
{ form . deploymentMode === "guided-install" && < GuidedInstallPlan pluginName = { pluginLabel ( selectedPlugin , form . pluginId ) } isScum = { isScum } / > }
{ form . deploymentMode === "guided-install" && < GuidedInstallPlan pluginName = { pluginLabel ( selectedPlugin , form . pluginId ) } isScum = { isScum } / > }
{ form . deploymentMode === "existing-server" && < div className = "form-guidance" > < strong > 接 管 只 做 预 检 与 绑 定 < / strong > < span > 不 会 执 行 安 装 命 令 , 也 不 会 把 本 页 的 默 认 游 戏 参 数 写 进 已 有 服 务 器 。 < / span > < / div > }
{ form . deploymentMode === "existing-server" && < ExistingServerAdoptionPlan pluginName = { pluginLabel ( selectedPlugin , form . pluginId ) } isScum = { isScum } / > }
{ 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 > }
{ 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 > }
{ 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 > }
< / div > }
{ step === 3 && < 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 ? "本次只保存部署设置" : "确认后将创建并派发部署 " } < / 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 > 目 标 < / 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 > }
< div className = "confirm-actions" > < button type = "button" disabled = { busy } onClick = { ( ) = > step === 0 ? onClose ( ) : setStep ( ( current ) = > current - 1 ) } > { step === 0 ? "取消" : "上一步" } < / button > { step < workflowSteps . length - 1 ? < 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 >
< 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 >
< / form >
< / ManagementDialog > ;
< / ManagementDialog > ;
}
}
@@ -136,3 +140,21 @@ function GuidedInstallPlan({ pluginName, isScum }: { pluginName: string; isScum:
return < section className = "guided-install-plan" aria-label = "新建并安装执行流程" > < div className = "guided-install-plan-heading" > < div > < strong > 确 认 后 , { pluginName } 会 这 样 安 装 < / strong > < span > “ 安 装 目 录 ” 就 是 游 戏 服 务 端 、 数 据 和 配 置 将 落 地 的 位 置 ; 它 不 是 命 令 执 行 目 录 , 也 不 会 在 日 志 中 回 显 。 < / span > < / div > < small > { isScum ? "全部 4 步通过才算安装成功" : "节点按插件契约执行" } < / small > < / div > < ol > { steps . map ( ( { icon : Icon , title , copy } , index ) = > < li key = { title } > < span > < Icon size = { 16 } / > < / span > < div > < strong > { index + 1 } . { title } < / strong > < small > { copy } < / small > < / div > < / li > ) } < / ol > < p > < strong > 不 会 做 : < / strong > { isScum ? "不会跳过验证就标记成功;失败时不会暴露你的目录、命令或凭据。" : "不会把受保护的路径、命令或凭据回显给浏览器。" } < / p > < / section > ;
return < section className = "guided-install-plan" aria-label = "新建并安装执行流程" > < div className = "guided-install-plan-heading" > < div > < strong > 确 认 后 , { pluginName } 会 这 样 安 装 < / strong > < span > “ 安 装 目 录 ” 就 是 游 戏 服 务 端 、 数 据 和 配 置 将 落 地 的 位 置 ; 它 不 是 命 令 执 行 目 录 , 也 不 会 在 日 志 中 回 显 。 < / span > < / div > < small > { isScum ? "全部 4 步通过才算安装成功" : "节点按插件契约执行" } < / small > < / div > < ol > { steps . map ( ( { icon : Icon , title , copy } , index ) = > < li key = { title } > < span > < Icon size = { 16 } / > < / span > < div > < strong > { index + 1 } . { title } < / strong > < small > { copy } < / small > < / div > < / li > ) } < / ol > < p > < strong > 不 会 做 : < / strong > { isScum ? "不会跳过验证就标记成功;失败时不会暴露你的目录、命令或凭据。" : "不会把受保护的路径、命令或凭据回显给浏览器。" } < / p > < / section > ;
}
}
function ExistingServerAdoptionPlan ( { pluginName , isScum } : { pluginName : string ; isScum : boolean } ) {
const steps = isScum ? [
{ icon : FolderCog , title : "定位服务端根目录" , copy : "填写包含 SCUM 服务端文件、数据与配置的目录,不是 Steam 库或 SteamCMD 目录。" } ,
{ icon : ScanSearch , title : "Run 本机预检" , copy : "检查目录权限、可执行文件、版本、Steam App 标记和所需端口。" } ,
{ icon : SlidersHorizontal , title : "只读扫描配置" , copy : "识别 ServerSettings.ini 与现有参数;接管不会写入或覆盖它们。" } ,
{ icon : ServerCog , title : "绑定受控生命周期" , copy : "记录这台实例由哪个 Run 管理,后续启动、停止和日志仍走受控通道。" } ,
{ icon : HeartPulse , title : "健康验证" , copy : "确认端口、进程与配置可读后,才标记为接管成功。" }
] : [
{ icon : FolderCog , title : "定位服务端根目录" , copy : "填写已有服务端文件、数据与配置所在的主目录。" } ,
{ icon : ScanSearch , title : "Run 本机预检" , copy : "检查目录权限、插件识别和端口是否可用。" } ,
{ icon : SlidersHorizontal , title : "只读扫描配置" , copy : "读取插件需要的现有状态,不把新建默认值写进服务器。" } ,
{ icon : ServerCog , title : "绑定受控生命周期" , copy : "后续运行操作由绑定的 Run 通过平台通道执行。" } ,
{ icon : HeartPulse , title : "健康验证" , copy : "验证通过后才标记为接管成功。" }
] ;
return < section className = "guided-install-plan" aria-label = "接管已有服务器执行流程" > < div className = "guided-install-plan-heading" > < div > < strong > 确 认 后 , { pluginName } 会 这 样 接 管 < / strong > < span > 目 录 只 会 交 给 目 标 Run 在 本 机 使 用 ; 平 台 、 浏 览 器 和 日 志 都 不 会 显 示 原 始 路 径 。 < / span > < / div > < small > 先 扫 描 , 后 绑 定 < / small > < / div > < ol > { steps . map ( ( { icon : Icon , title , copy } , index ) = > < li key = { title } > < span > < Icon size = { 16 } / > < / span > < div > < strong > { index + 1 } . { title } < / strong > < small > { copy } < / small > < / div > < / li > ) } < / ol > { isScum ? < p > < strong > SCUM 与 SteamCMD : < / strong > 接 管 只 需 要 服 务 端 根 目 录 , 不 需 要 填 写 SteamCMD 目 录 。 Run 可 能 按 本 机 策 略 检 查 SteamCMD 是 否 可 用 , 但 它 不 是 接 管 输 入 。 < br / > < strong > 升 级 : < / strong > 接 管 不 会 升 级 游 戏 ; 当 前 平 台 尚 未 提 供 SCUM 服 务 端 的 受 控 升 级 任 务 , 不 能 承 诺 自 动 升 级 。 升 级 能 力 需 要 单 独 的 SteamCMD 更 新 任 务 与 备 份 / 健 康 验 证 流 程 。 < / p > : < p > < strong > 不 会 做 : < / strong > 不 会 重 新 安 装 、 覆 盖 已 有 游 戏 配 置 , 或 把 受 保 护 路 径 回 显 给 浏 览 器 。 < / p > } < / section > ;
}