Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ favorites.json
server/data/environment
server/data/wallpapers/
server/data/lib/
server/data/tunnels/
server/data/tools/
data/wallpapers/
data/lib/
data/tunnels/
data/tools/


dist/
Expand Down Expand Up @@ -144,4 +148,4 @@ src/public/1.png

.turbo
参考文件/
.报错.txt
.报错.txt
5 changes: 3 additions & 2 deletions client/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
HelpCircle,
CheckCircle,
Coffee,
Puzzle,
ChevronLeft,
ChevronRight
} from 'lucide-react'
Expand Down Expand Up @@ -67,7 +68,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
{ name: '定时任务', href: '/scheduled-tasks', icon: Clock },
{ name: '文件管理', href: '/files', icon: FolderOpen },
{ name: '环境管理', href: '/environment', icon: Coffee },
// { name: '插件', href: '/plugins', icon: Puzzle },
{ name: '插件', href: '/plugins', icon: Puzzle },
{ name: '设置', href: '/settings', icon: Settings },
{ name: '关于项目', href: '/about', icon: Info },
]
Expand Down Expand Up @@ -869,4 +870,4 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
)
}

export default Layout
export default Layout
47 changes: 36 additions & 11 deletions client/src/pages/GameDeploymentPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface GameInfo {
url: string
docs?: string
system?: string[]
login_anonymous?: boolean
supportedOnCurrentPlatform?: boolean
currentPlatform?: string
panelCompatibleOnCurrentPlatform?: boolean
Expand All @@ -59,6 +60,14 @@ interface Games {
[key: string]: GameInfo
}

const quoteSteamCMDArgument = (value: string, platform?: string): string => {
if (platform === 'Windows') {
return `'${value.replace(/'/g, "''")}'`
}

return `'${value.replace(/'/g, "'\\''")}'`
}

// 辅助函数:判断是否为 Windows 平台
const isWindowsPlatform = (systemInfo: any): boolean => {
// 优先使用 rawPlatform(原始平台标识)
Expand Down Expand Up @@ -2543,17 +2552,24 @@ const GameDeploymentPage: React.FC = () => {
useEffect(() => {
if (showInstallModal && selectedGame) {
const forceInstallDir = `force_install_dir "${installPath.trim()}"`
const steamLoginArgs = [
'login',
quoteSteamCMDArgument(steamUsername.trim(), selectedGame.info.currentPlatform),
...(steamPassword.trim()
? [quoteSteamCMDArgument(steamPassword.trim(), selectedGame.info.currentPlatform)]
: [])
]

const loginCommand = useAnonymous
? 'login anonymous'
: `login ${steamUsername.trim()} ${steamPassword.trim()}`
: steamLoginArgs.join(' ')

const appUpdateCommand = validateGameIntegrity
? `app_update ${selectedGame.info.appid} validate`
: `app_update ${selectedGame.info.appid}`

// force_install_dir 必须在 login 之前,否则 SteamCMD 会报错
const fullCommand = `steamcmd +${forceInstallDir} +${loginCommand} +${appUpdateCommand} +quit`
const fullCommand = `+${forceInstallDir} +${loginCommand} +${appUpdateCommand} +quit`
setSteamcmdCommand(fullCommand)
}
}, [showInstallModal, selectedGame, useAnonymous, steamUsername, steamPassword, validateGameIntegrity, installPath])
Expand Down Expand Up @@ -2628,6 +2644,12 @@ const GameDeploymentPage: React.FC = () => {
// 打开安装对话框的通用函数
const openInstallModal = async (gameKey: string, gameInfo: GameInfo) => {
const defaultInstanceName = gameInfo.game_nameCN
const shouldUseAnonymous = gameInfo.login_anonymous !== false
setUseAnonymous(shouldUseAnonymous)
if (shouldUseAnonymous) {
setSteamUsername('')
setSteamPassword('')
}

// 检查是否存在同名实例
try {
Expand Down Expand Up @@ -2909,11 +2931,11 @@ const GameDeploymentPage: React.FC = () => {
return
}

if (!useAnonymous && (!steamUsername.trim() || !steamPassword.trim())) {
if (!useAnonymous && !steamUsername.trim()) {
addNotification({
type: 'error',
title: '参数错误',
message: '请填写Steam账户信息'
message: '请填写Steam用户名'
})
return
}
Expand Down Expand Up @@ -2946,7 +2968,7 @@ const GameDeploymentPage: React.FC = () => {
instanceName: instanceName.trim(),
useAnonymous,
steamUsername: useAnonymous ? undefined : steamUsername.trim(),
steamPassword: useAnonymous ? undefined : steamPassword.trim(),
steamPassword: useAnonymous || !steamPassword.trim() ? undefined : steamPassword.trim(),
steamcmdCommand: steamcmdCommand.trim(),
existingInstanceId: currentExistingInstanceId || undefined,
updateInstanceInfo: currentUpdateInstanceInfo,
Expand Down Expand Up @@ -5763,15 +5785,18 @@ const GameDeploymentPage: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Steam密码
Steam密码(可选)
</label>
<input
type="password"
value={steamPassword}
onChange={(e) => setSteamPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="输入Steam密码"
placeholder="留空则在终端中输入"
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
留空时 SteamCMD 会在终端提示输入密码和 Steam Guard 码
</p>
</div>
</div>
)}
Expand Down Expand Up @@ -5844,18 +5869,18 @@ const GameDeploymentPage: React.FC = () => {

<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
SteamCMD 安装命令
SteamCMD 安装参数
</label>
<textarea
value={steamcmdCommand}
onChange={(e) => setSteamcmdCommand(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-sm"
placeholder="SteamCMD 命令将在这里显示,您可以修改后执行"
placeholder="SteamCMD 安装参数将在这里显示,您可以修改后执行"
rows={4}
readOnly={false}
/>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
您可以修改此命令来自定义安装参数,修改后的命令将用于实际安装
面板会自动调用已配置的 steamcmd.exe 或 steamcmd.sh,请只保留 +force_install_dir、+login、+app_update 等参数
</p>
</div>
</div>
Expand All @@ -5875,7 +5900,7 @@ const GameDeploymentPage: React.FC = () => {
disabled={
!installPath.trim() ||
!instanceName.trim() ||
(!useAnonymous && (!steamUsername.trim() || !steamPassword.trim()))
(!useAnonymous && !steamUsername.trim())
}
className="px-4 py-2 disabled:bg-gray-400 text-white rounded-lg transition-colors flex items-center space-x-2 bg-blue-600 hover:bg-blue-700"
>
Expand Down
29 changes: 21 additions & 8 deletions client/src/pages/InstanceManagerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1031,13 +1031,26 @@ const InstanceManagerPage: React.FC = () => {
}

// 打开文件目录
const handleOpenDirectory = (instance: Instance) => {
navigate(`/files?path=${encodeURIComponent(instance.workingDirectory)}`)
addNotification({
type: 'success',
title: '跳转成功',
message: `已打开 "${instance.name}" 的工作目录`
})
const handleOpenDirectory = async (instance: Instance) => {
try {
const response = await apiClient.resolveFilePath(instance.workingDirectory)
const targetPath = response.data?.resolvedPath || instance.workingDirectory
navigate(`/files?path=${encodeURIComponent(targetPath)}`)
addNotification({
type: response.data?.exists === false ? 'warning' : 'success',
title: response.data?.exists === false ? '路径不存在' : '跳转成功',
message: response.data?.exists === false
? `已跳转到解析后的路径,但目录不存在:${targetPath}`
: `已打开 "${instance.name}" 的工作目录`
})
} catch (error: any) {
navigate(`/files?path=${encodeURIComponent(instance.workingDirectory)}`)
addNotification({
type: 'warning',
title: '路径解析失败',
message: error.message || '已使用原始工作目录跳转'
})
}
}

// 重置表单
Expand Down Expand Up @@ -2513,4 +2526,4 @@ const InstanceManagerPage: React.FC = () => {
)
}

export default InstanceManagerPage
export default InstanceManagerPage
39 changes: 23 additions & 16 deletions client/src/pages/PluginsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,13 +502,13 @@ const PluginsPage: React.FC = () => {
</div>
</div>
</div>
<div className="flex items-center space-x-1">
{plugin.enabled ? (
<div className="w-2 h-2 bg-green-500 rounded-full" title="已启用" />
) : (
<div className="w-2 h-2 bg-gray-400 rounded-full" title="已禁用" />
)}
</div>
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
plugin.enabled
? 'bg-green-500/20 text-green-700 dark:text-green-300'
: 'bg-gray-500/20 text-gray-600 dark:text-gray-300'
}`}>
{plugin.enabled ? '已启用' : '已禁用'}
</span>
</div>

{/* 插件信息 */}
Expand All @@ -534,27 +534,34 @@ const PluginsPage: React.FC = () => {
<div className="flex items-center justify-between pt-4 border-t border-white/10 dark:border-gray-700/30">
<div className="flex items-center space-x-2">
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => handleTogglePlugin(plugin)}
className={`p-2 rounded-lg transition-colors ${
className={`inline-flex items-center space-x-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
plugin.enabled
? 'bg-green-500/20 text-green-600 hover:bg-green-500/30'
: 'bg-gray-500/20 text-gray-600 hover:bg-gray-500/30'
}`}
title={plugin.enabled ? '禁用插件' : '启用插件'}
>
{plugin.enabled ? <Power className="w-4 h-4" /> : <PowerOff className="w-4 h-4" />}
<span>{plugin.enabled ? '禁用' : '启用'}</span>
</motion.button>
{plugin.hasWebInterface && plugin.enabled && (
{plugin.hasWebInterface && (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
whileHover={{ scale: plugin.enabled ? 1.02 : 1 }}
whileTap={{ scale: plugin.enabled ? 0.98 : 1 }}
onClick={() => handleOpenPlugin(plugin)}
className="p-2 bg-blue-500/20 text-blue-600 rounded-lg hover:bg-blue-500/30 transition-colors"
title="打开插件"
disabled={!plugin.enabled}
className={`inline-flex items-center space-x-1.5 px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
plugin.enabled
? 'bg-blue-500/20 text-blue-600 hover:bg-blue-500/30'
: 'bg-gray-500/10 text-gray-400 cursor-not-allowed'
}`}
title={plugin.enabled ? '打开插件' : '启用后可打开插件'}
>
<ExternalLink className="w-4 h-4" />
<span>打开</span>
</motion.button>
)}
</div>
Expand Down Expand Up @@ -846,4 +853,4 @@ const PluginsPage: React.FC = () => {
)
}

export default PluginsPage
export default PluginsPage
10 changes: 10 additions & 0 deletions client/src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,16 @@ class ApiClient {
return this.get('/files', { params: { path } })
}

async resolveFilePath(path: string) {
return this.get<{
originalPath: string
resolvedPath: string
isAbsolute: boolean
exists: boolean
type?: 'file' | 'directory'
}>('/files/resolve-path', { params: { path } })
}

async uploadFile(file: File, path: string) {
const formData = new FormData()
formData.append('file', file)
Expand Down
Loading
Loading