├── .github └── workflows │ ├── docker-no-cache-publish.yml │ └── docker-publish.yml ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── index.html ├── package.json ├── src │ ├── App.css │ ├── App.tsx │ ├── api.ts │ ├── components │ │ ├── BetaNotice.tsx │ │ ├── ContainerInfo.tsx │ │ ├── FileManager.tsx │ │ ├── FileManagerHelpModal.tsx │ │ ├── FrpDocModal.tsx │ │ ├── FrpManager.tsx │ │ ├── Login.tsx │ │ ├── NotFound.tsx │ │ ├── ProtectedRoute.tsx │ │ ├── Register.tsx │ │ ├── ServerTerminal.tsx │ │ ├── SimpleServerTerminal.tsx │ │ ├── Terminal.tsx │ │ ├── TerminalExample.tsx │ │ ├── TerminalManager.tsx │ │ └── XTerminal.tsx │ ├── context │ │ └── AuthContext.tsx │ ├── hooks │ │ ├── useIsMobile.ts │ │ └── useTerminal.ts │ ├── index.css │ ├── index.html │ ├── main.js │ ├── main.tsx │ ├── monaco-config.ts │ ├── pages │ │ ├── About.tsx │ │ ├── Environment.tsx │ │ ├── ServerGuide.tsx │ │ └── Settings.tsx │ ├── services │ │ └── terminalService.ts │ └── types.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── docker-compose.yml ├── docs ├── assets │ ├── 08e8eabffd009b7e6283d505df989e30-20250527200418-mrhe5em.png │ ├── 68965b46d69230aa67360d4739fd33bc-20250527200021-v9j00xs.png │ ├── 8b119a50dd749371b96dc8d682a2a134-20250527194120-372sqw8.png │ ├── 91cc851dea80f19c7d948be1985eb2f6-20250527200351-rbxsund.png │ ├── QQ20250527-203735-20250527203749-def1cz4.png │ ├── cf13b683aa564ee8bcbf456204eb7f7f-20250527195825-6fg8gkr.png │ ├── image-20250527205016-ftlve2s.png │ ├── image-20250527205455-st3i6wo.png │ ├── image-20250527205703-y73278h.png │ ├── image-20250527210756-arh5p3u.png │ ├── image-20250527211647-ygyk68a.png │ ├── image-20250527211757-549t0nv.png │ └── image-20250527212021-6qk2z3s.png ├── 内网穿透的使用方法.md ├── 导入第三方游戏服务端.md ├── 常见问题.md └── 部署指南.md ├── frp ├── LoCyanFrp │ ├── frpc │ ├── frpc.ini │ └── frpc_full.ini ├── Sakura │ └── frpc ├── frpc │ ├── frpc │ └── frpc.toml ├── mefrp │ └── frpc └── npc │ └── frpc └── server ├── MCdownloads.py ├── api_server.py ├── auth_middleware.py ├── auth_service.py ├── config.py ├── direct_installer.py ├── game_installer.py ├── installgame.json ├── pty_manager.py ├── sponsor_validator.py └── start_web.sh /.github/workflows/docker-no-cache-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker_Push_no_cache 2 | 3 | on: 4 | workflow_dispatch: # 手动触发 5 | 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-latest 9 | steps: 10 | # 第一步:检出代码 11 | - name: 拉取仓库代码 12 | uses: actions/checkout@v3 13 | 14 | # 第二步:设置构建环境 15 | - name: 配置QEMU模拟器 16 | uses: docker/setup-qemu-action@v2 17 | 18 | - name: 配置Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | with: 21 | driver-opts: network=host 22 | 23 | # 第三步:登录所有镜像仓库 24 | - name: 登录Docker仓库 25 | uses: docker/login-action@v2 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | - name: 登录GitHub容器仓库 30 | uses: docker/login-action@v2 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.repository_owner }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | # 第四步:构建并推送镜像(不使用缓存) 37 | - name: 构建推送Docker镜像(无缓存) 38 | uses: docker/build-push-action@v4 39 | with: 40 | context: . 41 | push: true 42 | tags: | 43 | xiaozhu674/gameserver:0.5 44 | ghcr.io/${{ github.repository_owner }}/gameserver:0.5 45 | # 明确禁用缓存 46 | no-cache: true 47 | 48 | # 第五步:单独创建缓存供后续使用 49 | - name: 创建构建缓存 50 | uses: docker/build-push-action@v4 51 | with: 52 | context: . 53 | push: false # 不推送镜像 54 | tags: | 55 | xiaozhu674/gameserver:0.5 56 | cache-from: type=gha 57 | cache-to: type=gha,mode=max 58 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker_Push 2 | 3 | on: 4 | workflow_dispatch: # 手动触发 5 | 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-latest 9 | steps: 10 | # 第一步:检出代码 11 | - name: 拉取仓库代码 12 | uses: actions/checkout@v3 13 | 14 | # 第二步:设置构建环境 15 | - name: 配置QEMU模拟器 16 | uses: docker/setup-qemu-action@v2 17 | 18 | - name: 配置Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | with: 21 | driver-opts: network=host 22 | 23 | # 第三步:登录所有镜像仓库 24 | - name: 登录Docker仓库 25 | uses: docker/login-action@v2 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | - name: 登录GitHub容器仓库 30 | uses: docker/login-action@v2 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.repository_owner }} # 改为明确的用户名 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | # 第四步:构建并推送镜像(带缓存功能) 37 | - name: 缓存并构建推送DockerHub 38 | uses: docker/build-push-action@v4 39 | with: 40 | context: . # 构建上下文路径 41 | push: true # 构建后自动推送 42 | tags: | 43 | xiaozhu674/gameservermanager:2.0.3 44 | xiaozhu674/gameservermanager:latest 45 | ghcr.io/${{ github.repository_owner }}/gameservermanager:2.0.3 46 | ghcr.io/${{ github.repository_owner }}/gameservermanager:latest 47 | cache-from: type=gha # 使用GitHub缓存 48 | cache-to: type=gha,mode=max # 写入GitHub缓存 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive \ 4 | STEAM_USER=steam \ 5 | STEAM_HOME=/home/steam \ 6 | STEAMCMD_DIR=/home/steam/steamcmd \ 7 | GAMES_DIR=/home/steam/games 8 | 9 | # 将apt源改为中国镜像源(清华TUNA) 10 | RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ 11 | && sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list 12 | 13 | # 安装SteamCMD和常见依赖(包括32位库) 14 | RUN apt-get update && apt-get upgrade -y \ 15 | && dpkg --add-architecture i386 \ 16 | && apt-get update \ 17 | && apt-get install -y --no-install-recommends \ 18 | ca-certificates \ 19 | locales \ 20 | wget \ 21 | curl \ 22 | jq \ 23 | xdg-user-dirs \ 24 | libncurses5:i386 \ 25 | libbz2-1.0:i386 \ 26 | libicu67:i386 \ 27 | libxml2:i386 \ 28 | libstdc++6:i386 \ 29 | lib32gcc-s1 \ 30 | libc6-i386 \ 31 | lib32stdc++6 \ 32 | libcurl4-gnutls-dev:i386 \ 33 | libcurl4-gnutls-dev \ 34 | libgl1-mesa-glx:i386 \ 35 | gcc-10-base:i386 \ 36 | libssl1.1:i386 \ 37 | libopenal1:i386 \ 38 | libtinfo6:i386 \ 39 | libtcmalloc-minimal4:i386 \ 40 | # .NET和Mono相关依赖(ECO服务器等需要) 41 | libgdiplus \ 42 | libc6-dev \ 43 | libasound2 \ 44 | libpulse0 \ 45 | pulseaudio \ 46 | libpulse-dev \ 47 | libnss3 \ 48 | libgconf-2-4 \ 49 | libcap2 \ 50 | libatk1.0-0 \ 51 | libcairo2 \ 52 | libcups2 \ 53 | libgtk-3-0 \ 54 | libgdk-pixbuf2.0-0 \ 55 | libpango-1.0-0 \ 56 | libx11-6 \ 57 | libxt6 \ 58 | # Unity游戏服务端额外依赖(7日杀等) 59 | libsdl2-2.0-0:i386 \ 60 | libsdl2-2.0-0 \ 61 | libpulse0:i386 \ 62 | libfontconfig1:i386 \ 63 | libfontconfig1 \ 64 | libudev1:i386 \ 65 | libudev1 \ 66 | libpugixml1v5 \ 67 | libvulkan1 \ 68 | libvulkan1:i386 \ 69 | libgconf-2-4:i386 \ 70 | # 额外的Unity引擎依赖(特别针对7日杀) 71 | libatk1.0-0:i386 \ 72 | libxcomposite1 \ 73 | libxcomposite1:i386 \ 74 | libxcursor1 \ 75 | libxcursor1:i386 \ 76 | libxrandr2 \ 77 | libxrandr2:i386 \ 78 | libxss1 \ 79 | libxss1:i386 \ 80 | libxtst6 \ 81 | libxtst6:i386 \ 82 | libxi6 \ 83 | libxi6:i386 \ 84 | libxkbfile1 \ 85 | libxkbfile1:i386 \ 86 | libasound2:i386 \ 87 | libgtk-3-0:i386 \ 88 | libdbus-1-3 \ 89 | libdbus-1-3:i386 \ 90 | # ARK: Survival Evolved(方舟生存进化)服务器额外依赖 91 | libelf1 \ 92 | libelf1:i386 \ 93 | libatomic1 \ 94 | libatomic1:i386 \ 95 | nano \ 96 | net-tools \ 97 | netcat \ 98 | procps \ 99 | python3 \ 100 | python3-pip \ 101 | tar \ 102 | unzip \ 103 | bzip2 \ 104 | xz-utils \ 105 | zlib1g:i386 \ 106 | fonts-wqy-zenhei \ 107 | fonts-wqy-microhei \ 108 | libc6 \ 109 | libc6:i386 \ 110 | && rm -rf /var/lib/apt/lists/* 111 | 112 | # 安装Node.js (用于运行Web界面) 113 | RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ 114 | && apt-get update && apt-get install -y nodejs \ 115 | && rm -rf /var/lib/apt/lists/* 116 | 117 | # 配置npm使用淘宝源 118 | RUN npm config set registry https://registry.npmmirror.com 119 | 120 | # 设置 locales 121 | RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ 122 | && sed -i -e 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen \ 123 | && locale-gen 124 | ENV LANG=zh_CN.UTF-8 \ 125 | LANGUAGE=zh_CN:zh \ 126 | LC_ALL=zh_CN.UTF-8 127 | 128 | # 创建steam用户 129 | RUN useradd -m -s /bin/bash ${STEAM_USER} \ 130 | && mkdir -p ${STEAMCMD_DIR} ${GAMES_DIR} \ 131 | && chown -R ${STEAM_USER}:${STEAM_USER} ${STEAM_HOME} 132 | 133 | # 配置pip使用国内源 (这里放在创建用户之后) 134 | RUN mkdir -p /root/.pip /home/steam/.pip \ 135 | && echo '[global]\n\ 136 | index-url = https://pypi.tuna.tsinghua.edu.cn/simple\n\ 137 | trusted-host = pypi.tuna.tsinghua.edu.cn' > /root/.pip/pip.conf \ 138 | && cp /root/.pip/pip.conf /home/steam/.pip/pip.conf \ 139 | && chown -R ${STEAM_USER}:${STEAM_USER} /home/steam/.pip 140 | 141 | 142 | # 切换到root用户安装SteamCMD(确保有足够权限) 143 | USER root 144 | WORKDIR /home/steam 145 | 146 | # 下载并安装SteamCMD 147 | RUN mkdir -p ${STEAMCMD_DIR} \ 148 | && cd ${STEAMCMD_DIR} \ 149 | && (if curl -s --connect-timeout 3 http://192.168.10.23:7890 >/dev/null 2>&1 || wget -q --timeout=3 --tries=1 http://192.168.10.23:7890 -O /dev/null >/dev/null 2>&1; then \ 150 | echo "代理服务器可用,使用代理下载和初始化"; \ 151 | export http_proxy=http://192.168.10.23:7890; \ 152 | export https_proxy=http://192.168.10.23:7890; \ 153 | wget -t 5 --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -O steamcmd_linux.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \ 154 | || wget -t 5 --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -O steamcmd_linux.tar.gz https://media.steampowered.com/installer/steamcmd_linux.tar.gz; \ 155 | tar -xzvf steamcmd_linux.tar.gz; \ 156 | rm steamcmd_linux.tar.gz; \ 157 | chown -R ${STEAM_USER}:${STEAM_USER} ${STEAMCMD_DIR}; \ 158 | chmod +x ${STEAMCMD_DIR}/steamcmd.sh; \ 159 | su - ${STEAM_USER} -c "export http_proxy=http://192.168.10.23:7890 && export https_proxy=http://192.168.10.23:7890 && cd ${STEAMCMD_DIR} && ./steamcmd.sh +quit"; \ 160 | unset http_proxy https_proxy; \ 161 | else \ 162 | echo "代理服务器不可用,使用直接连接"; \ 163 | wget -t 5 --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -O steamcmd_linux.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \ 164 | || wget -t 5 --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -O steamcmd_linux.tar.gz https://media.steampowered.com/installer/steamcmd_linux.tar.gz; \ 165 | tar -xzvf steamcmd_linux.tar.gz; \ 166 | rm steamcmd_linux.tar.gz; \ 167 | chown -R ${STEAM_USER}:${STEAM_USER} ${STEAMCMD_DIR}; \ 168 | chmod +x ${STEAMCMD_DIR}/steamcmd.sh; \ 169 | su - ${STEAM_USER} -c "cd ${STEAMCMD_DIR} && ./steamcmd.sh +quit"; \ 170 | fi) \ 171 | # 创建steamclient.so符号链接 172 | && mkdir -p ${STEAM_HOME}/.steam/sdk32 ${STEAM_HOME}/.steam/sdk64 \ 173 | && ln -sf ${STEAMCMD_DIR}/linux32/steamclient.so ${STEAM_HOME}/.steam/sdk32/steamclient.so \ 174 | && ln -sf ${STEAMCMD_DIR}/linux64/steamclient.so ${STEAM_HOME}/.steam/sdk64/steamclient.so \ 175 | # 创建额外的游戏常用目录链接 176 | && mkdir -p ${STEAM_HOME}/.steam/sdk32/steamclient.so.dbg.sig ${STEAM_HOME}/.steam/sdk64/steamclient.so.dbg.sig \ 177 | && mkdir -p ${STEAM_HOME}/.steam/steam \ 178 | && ln -sf ${STEAMCMD_DIR}/linux32 ${STEAM_HOME}/.steam/steam/linux32 \ 179 | && ln -sf ${STEAMCMD_DIR}/linux64 ${STEAM_HOME}/.steam/steam/linux64 \ 180 | && ln -sf ${STEAMCMD_DIR}/steamcmd ${STEAM_HOME}/.steam/steam/steamcmd \ 181 | && chown -R ${STEAM_USER}:${STEAM_USER} ${STEAM_HOME}/.steam 182 | 183 | 184 | # 复制前端package.json并安装依赖 185 | COPY --chown=steam:steam ./app/package.json ./app/package-lock.json* /home/steam/app/ 186 | WORKDIR /home/steam/app 187 | RUN npm install --legacy-peer-deps --no-fund && \ 188 | npm install react-router-dom @types/react @types/react-dom react-dom @monaco-editor/react monaco-editor js-cookie @types/js-cookie 189 | 190 | # 安装后端依赖 191 | RUN pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple flask flask-cors gunicorn requests psutil PyJWT rarfile zstandard 192 | 193 | # 添加启动脚本 194 | RUN echo '#!/bin/bash\n\ 195 | echo "启动游戏服务器部署Web界面..."\n\ 196 | echo "请访问 http://[服务器IP]:5000 使用Web界面"\n\ 197 | \n\ 198 | # 确保start_web.sh有执行权限\n\ 199 | chmod +x /home/steam/server/start_web.sh\n\ 200 | \n\ 201 | # 启动API服务器\n\ 202 | cd /home/steam/server\n\ 203 | ./start_web.sh\n\ 204 | ' > /home/steam/start_web.sh \ 205 | && chmod +x /home/steam/start_web.sh 206 | 207 | # 创建目录用于挂载游戏数据 208 | VOLUME ["${GAMES_DIR}"] 209 | 210 | # 暴露API服务端口 - 对外网开放 211 | EXPOSE 5000 212 | # 暴露常用游戏端口 213 | EXPOSE 27015-27020/tcp 214 | EXPOSE 27015-27020/udp 215 | 216 | # 复制FRP文件 217 | COPY --chown=steam:steam ./frp/LoCyanFrp /home/steam/FRP/LoCyanFrp 218 | COPY --chown=steam:steam ./frp/frpc /home/steam/FRP/frpc 219 | COPY --chown=steam:steam ./frp/mefrp /home/steam/FRP/mefrp 220 | COPY --chown=steam:steam ./frp/Sakura /home/steam/FRP/Sakura 221 | COPY --chown=steam:steam ./frp/npc /home/steam/FRP/npc 222 | RUN chmod +x /home/steam/FRP/LoCyanFrp/frpc 223 | RUN chmod +x /home/steam/FRP/frpc/frpc 224 | RUN chmod +x /home/steam/FRP/mefrp/frpc 225 | RUN chmod +x /home/steam/FRP/Sakura/frpc 226 | RUN chmod +x /home/steam/FRP/npc/frpc 227 | 228 | # 最后一步:复制前端代码并构建 229 | COPY --chown=steam:steam ./app /home/steam/app 230 | WORKDIR /home/steam/app 231 | RUN npm run build && \ 232 | echo "前端构建完成" 233 | 234 | # 复制后端代码 235 | COPY --chown=steam:steam ./server /home/steam/server 236 | RUN chmod +x /home/steam/server/start_web.sh 237 | 238 | 239 | # 设置工作目录和启动命令 240 | WORKDIR /home/steam 241 | CMD ["/home/steam/start_web.sh"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目介绍 2 | GameServerManager 简称GSManager 让游戏服务器的部署、管理和维护变得简单高效。基于Docker技术,支持多款热门游戏,一键部署,轻松管理。\ 3 | 项目最大亮点是环境全部在Docker中运行,拥有环境兼容性最高的Docker镜像,能运行几乎所有支持Linux游戏服务端;并且任何人不受限制的使用面板上的所有功能,人人平等。 4 | 5 | ### [>>快速使用](https://github.com/yxsj245/GameServerManager/blob/2.0/docs/%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97.md) 6 | ### [>>文档站](http://blogpage.xiaozhuhouses.asia/html6/index.html#/) 7 | ### [>>官方网站](http://blogpage.xiaozhuhouses.asia/html5/index.html) 8 |
9 | 10 | ### [返回旧版分支](https://github.com/yxsj245/GameServerManager/tree/container_Shell) 11 |
12 | 13 | > 项目目前处于公测阶段,不代表最终成果,可能会遇到任何BUG以及安全性问题,建议在非生产环境中使用,若有任何问题请加QQ群1040201322 14 | 15 | # WEBUI介绍 16 | ## 简洁舒服的控制面板 17 | ![image](https://github.com/user-attachments/assets/c12b8b88-8658-4bb5-b8ed-e269c86b2c44) 18 | 从2.0大版本更新我引入了面板管理,极大程度提升萌新操作以及开服的便捷性 19 | ## SSE+PTY终端 20 | ![image](https://github.com/user-attachments/assets/424038cb-d18a-429e-8768-a837b51c4fed) 21 | 采用原生SSE通信最大亮点就是秒级连接,在网络不佳的场景下能够有效的确保连接稳定性并且原生支持重连。 22 | ## 内置代码编辑器+自动补全+错误高亮提示 23 | ![image](https://github.com/user-attachments/assets/040cd69e-9f25-412d-970c-1476b99fdeb8) 24 | 确保您在编辑文件时获得更加友好的视觉效果同时增加错误高亮防止引配置文件语法错误造成服务端无法启动 25 | ## 强大的文件解压缩功能 26 | ![image](https://github.com/user-attachments/assets/4386c268-7e1b-49a8-b8e4-07cd0a625625) 27 | 支持市面主流压缩格式,压缩率从低到高你一定能找到最适合你的压缩格式。麻麻再也不用担心文件太大了。 28 | ## 主流Steam游戏一键部署 29 | ![image](https://github.com/user-attachments/assets/679e6720-26f0-4eff-b5d8-f98b7414145d) 30 | 再也不需要去steamdb上查询游戏appid了,以及网上查询启动脚本等,在这里你只需要点下鼠标剩下交给后端程序完成自动下载服务端和创建启动脚本 31 | ## 超低占用 32 | ![image](https://github.com/user-attachments/assets/ef68d4a6-6bb4-4fde-8911-a5c35562e1ed) 33 | 最大程度将资源和性能留给游戏使用 34 | ## 对接FRP以及主流公益FRP 35 | ![image](https://github.com/user-attachments/assets/9bcc5cfb-2c73-42e3-9581-7890308a48b6) 36 | 你以为只能用自建??NO!格局打开兄弟,要的就是一个体验! 37 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GameServerManager 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "game-installer-frontend", 3 | "version": "1.0.0", 4 | "description": "游戏快速部署网页前端", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "build:strict": "tsc && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "axios": "^1.6.2", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "antd": "^4.24.14", 17 | "@monaco-editor/react": "^4.6.0", 18 | "monaco-editor": "^0.45.0", 19 | "js-cookie": "^3.0.5", 20 | "xterm": "^5.3.0", 21 | "xterm-addon-fit": "^0.8.0", 22 | "xterm-addon-web-links": "^0.9.0", 23 | "xterm-addon-search": "^0.13.0", 24 | "react-beautiful-dnd": "^13.1.1" 25 | }, 26 | "devDependencies": { 27 | "@types/react": "^18.2.37", 28 | "@types/react-dom": "^18.2.15", 29 | "@types/js-cookie": "^3.0.6", 30 | "@types/react-beautiful-dnd": "^13.1.4", 31 | "@vitejs/plugin-react": "^4.2.0", 32 | "typescript": "^5.2.2", 33 | "vite": "^5.0.0" 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { GameInfo, InstallEventData } from './types'; 3 | 4 | // 定义全局的logout回调函数 5 | let globalLogoutCallback: (() => void) | null = null; 6 | 7 | // 设置全局logout回调 8 | export const setGlobalLogoutCallback = (callback: () => void) => { 9 | globalLogoutCallback = callback; 10 | }; 11 | 12 | // 当通过外部IP访问时,动态获取当前域名和端口作为API基础URL 13 | const getApiBaseUrl = () => { 14 | // 如果是相对路径(通过同一服务器访问),使用相对路径 15 | if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { 16 | return '/api'; 17 | } 18 | // 否则使用完整的URL(通过外部IP访问) 19 | return `${window.location.protocol}//${window.location.host}/api`; 20 | }; 21 | 22 | const API_BASE_URL = getApiBaseUrl(); 23 | 24 | // 创建axios实例 25 | const api = axios.create({ 26 | baseURL: API_BASE_URL, 27 | }); 28 | 29 | // 添加请求拦截器,自动添加身份验证令牌 30 | api.interceptors.request.use( 31 | (config) => { 32 | const token = localStorage.getItem('auth_token'); 33 | if (token) { 34 | config.headers['Authorization'] = `Bearer ${token}`; 35 | } 36 | 37 | return config; 38 | }, 39 | (error) => { 40 | return Promise.reject(error); 41 | } 42 | ); 43 | 44 | // 添加响应拦截器,处理未授权错误 45 | api.interceptors.response.use( 46 | (response) => { 47 | return response; 48 | }, 49 | (error) => { 50 | // 如果是401未授权错误,自动退出登录 51 | if (error.response && error.response.status === 401) { 52 | console.log('检测到401未授权,自动退出登录'); 53 | 54 | // 如果有全局logout回调,调用它来正确清理状态 55 | if (globalLogoutCallback) { 56 | globalLogoutCallback(); 57 | } else { 58 | // 如果没有回调,直接清理本地存储并跳转 59 | localStorage.removeItem('auth_token'); 60 | localStorage.removeItem('username'); 61 | 62 | // 延迟跳转,避免循环重定向 63 | if (!window.location.pathname.includes('/login')) { 64 | setTimeout(() => { 65 | window.location.href = '/login'; 66 | }, 500); 67 | } 68 | } 69 | } 70 | 71 | return Promise.reject(error); 72 | } 73 | ); 74 | 75 | export const fetchGames = async (): Promise => { 76 | try { 77 | // 修正API响应类型 78 | interface GamesResponse { 79 | status: 'success' | 'error'; 80 | games: GameInfo[]; 81 | message?: string; 82 | } 83 | 84 | const response = await api.get('/games'); 85 | 86 | if (response.data.status === 'success' && response.data.games) { 87 | // 获取所有游戏列表 88 | const games = response.data.games; 89 | 90 | // 使用批量检查API检查所有游戏的安装状态 91 | if (games.length > 0) { 92 | try { 93 | const gameIds = games.map(game => game.id); 94 | const batchResponse = await api.post('/batch_check_installation', { 95 | game_ids: gameIds 96 | }); 97 | 98 | if (batchResponse.data.status === 'success') { 99 | const installations = batchResponse.data.installations; 100 | // 更新每个游戏的安装状态 101 | games.forEach(game => { 102 | game.installed = installations[game.id] || false; 103 | }); 104 | } 105 | } catch (error) { 106 | console.error('批量检查游戏安装状态失败:', error); 107 | // 如果批量检查失败,所有游戏都设为未安装 108 | games.forEach(game => { 109 | game.installed = false; 110 | }); 111 | } 112 | } 113 | 114 | return games; 115 | } else { 116 | throw new Error(response.data.message || '获取游戏列表失败'); 117 | } 118 | } catch (error) { 119 | // console.error('Error fetching games:', error); 120 | throw error; 121 | } 122 | }; 123 | 124 | // 批量检查游戏安装状态 125 | export const batchCheckInstallation = async (gameIds: string[]): Promise> => { 126 | try { 127 | const response = await api.post('/batch_check_installation', { 128 | game_ids: gameIds 129 | }); 130 | 131 | if (response.data.status === 'success') { 132 | return response.data.installations; 133 | } else { 134 | throw new Error(response.data.message || '批量检查安装状态失败'); 135 | } 136 | } catch (error) { 137 | console.error('批量检查游戏安装状态失败:', error); 138 | throw error; 139 | } 140 | }; 141 | 142 | // 检查单个游戏的安装状态 (保留原有函数以兼容旧代码) 143 | export const checkInstallation = async (gameId: string): Promise => { 144 | try { 145 | const response = await api.get(`/check_installation?game_id=${gameId}`); 146 | 147 | if (response.data.status === 'success') { 148 | return response.data.installed; 149 | } else { 150 | throw new Error(response.data.message || '检查安装状态失败'); 151 | } 152 | } catch (error) { 153 | console.error(`检查游戏 ${gameId} 安装状态失败:`, error); 154 | return false; 155 | } 156 | }; 157 | 158 | export const installGame = async ( 159 | gameId: string, 160 | onOutput: (line: string | { prompt: string }) => void, 161 | onComplete: () => void, 162 | onError: (error: string) => void, 163 | account?: string, 164 | password?: string 165 | ): Promise => { 166 | try { 167 | // 1. 先请求安装 168 | const installResp = await api.post('/install', { 169 | game_id: gameId, 170 | ...(account ? { account } : {}), 171 | ...(password ? { password } : {}) 172 | }); 173 | if (installResp.data.status !== 'success') { 174 | onError(installResp.data.message || '安装请求失败'); 175 | return null; 176 | } 177 | 178 | // 获取身份验证令牌 179 | const token = localStorage.getItem('auth_token'); 180 | 181 | // 2. 再连接SSE 182 | const sseUrl = `${API_BASE_URL}/install_stream?game_id=${gameId}${token ? `&token=${token}` : ''}`; 183 | const eventSource = new EventSource(sseUrl); 184 | eventSource.onmessage = (event) => { 185 | try { 186 | const data: InstallEventData = JSON.parse(event.data); 187 | if (data.line) { 188 | onOutput(data.line); 189 | } 190 | if (data.prompt) { 191 | onOutput({ prompt: data.prompt }); 192 | } 193 | if (data.complete) { 194 | eventSource.close(); 195 | if (data.status === 'success') { 196 | onComplete(); 197 | } else { 198 | onError(data.message || '安装过程发生错误'); 199 | } 200 | } 201 | } catch (error) { 202 | // console.error('Error parsing SSE data:', error); 203 | onOutput(`解析安装输出错误: ${error}`); 204 | } 205 | }; 206 | eventSource.onerror = () => { 207 | eventSource.close(); 208 | onError('与服务器的连接中断'); 209 | }; 210 | return eventSource; 211 | } catch (error: any) { 212 | onError(error?.message || '安装请求失败'); 213 | return null; 214 | } 215 | }; 216 | 217 | // 终止游戏安装 218 | export const terminateInstall = async (gameId: string): Promise => { 219 | try { 220 | // 确保使用正确的API路径 221 | const response = await api.post('/terminate_install', { 222 | game_id: gameId 223 | }); 224 | 225 | return response.data.status === 'success'; 226 | } catch (error) { 227 | // console.error('Error terminating installation:', error); 228 | throw error; 229 | } 230 | }; 231 | 232 | // 通过AppID安装游戏 233 | export const installByAppId = async ( 234 | appId: string, 235 | name: string, 236 | anonymous: boolean, 237 | onOutput: (line: string | { prompt: string }) => void, 238 | onComplete: () => void, 239 | onError: (error: string) => void, 240 | account?: string, 241 | password?: string 242 | ): Promise => { 243 | try { 244 | // 1. 先请求安装 245 | const installResp = await api.post('/install_by_appid', { 246 | appid: appId, 247 | name: name, 248 | anonymous: anonymous, 249 | ...(account ? { account } : {}), 250 | ...(password ? { password } : {}) 251 | }); 252 | 253 | if (installResp.data.status !== 'success') { 254 | onError(installResp.data.message || '安装请求失败'); 255 | return null; 256 | } 257 | 258 | // 获取身份验证令牌 259 | const token = localStorage.getItem('auth_token'); 260 | 261 | // 2. 再连接SSE,使用生成的game_id 262 | const gameId = `app_${appId}`; 263 | const sseUrl = `${API_BASE_URL}/install_stream?game_id=${gameId}${token ? `&token=${token}` : ''}`; 264 | const eventSource = new EventSource(sseUrl); 265 | 266 | eventSource.onmessage = (event) => { 267 | try { 268 | const data: InstallEventData = JSON.parse(event.data); 269 | if (data.line) { 270 | onOutput(data.line); 271 | } 272 | if (data.prompt) { 273 | onOutput({ prompt: data.prompt }); 274 | } 275 | if (data.complete) { 276 | eventSource.close(); 277 | if (data.status === 'success') { 278 | onComplete(); 279 | } else { 280 | onError(data.message || '安装过程发生错误'); 281 | } 282 | } 283 | } catch (error) { 284 | // console.error('Error parsing SSE data:', error); 285 | onOutput(`解析安装输出错误: ${error}`); 286 | } 287 | }; 288 | 289 | eventSource.onerror = () => { 290 | eventSource.close(); 291 | onError('与服务器的连接中断'); 292 | }; 293 | 294 | return eventSource; 295 | } catch (error: any) { 296 | onError(error?.message || '安装请求失败'); 297 | return null; 298 | } 299 | }; 300 | 301 | // 打开游戏文件夹 302 | export const openGameFolder = async (gameId: string): Promise => { 303 | try { 304 | const response = await api.post('/open_game_folder', { 305 | game_id: gameId 306 | }); 307 | 308 | return response.data.status === 'success'; 309 | } catch (error) { 310 | return false; 311 | } 312 | }; 313 | 314 | // 检查版本更新 315 | export const checkVersionUpdate = async (): Promise<{version: string, description: any} | null> => { 316 | try { 317 | const response = await api.get('/version/check'); 318 | 319 | if (response.data.status === 'success') { 320 | return { 321 | version: response.data.version, 322 | description: response.data.description 323 | }; 324 | } else { 325 | console.warn('版本检查失败:', response.data.message); 326 | return null; 327 | } 328 | } catch (error: any) { 329 | console.warn('版本检查请求失败:', error?.message || error); 330 | return null; 331 | } 332 | }; -------------------------------------------------------------------------------- /app/src/components/BetaNotice.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Modal, Button, Typography } from 'antd'; 3 | // 导入antd样式 4 | import 'antd/dist/antd.css'; 5 | import '../App.css'; 6 | import Cookies from 'js-cookie'; 7 | 8 | const { Title, Paragraph } = Typography; 9 | 10 | interface BetaNoticeProps { 11 | // 可以传入自定义的通知内容 12 | title?: string; 13 | content?: React.ReactNode; 14 | } 15 | 16 | const BetaNotice: React.FC = ({ 17 | title = "项目内测须知", 18 | content 19 | }) => { 20 | const [visible, setVisible] = useState(false); 21 | 22 | useEffect(() => { 23 | // 检查cookie是否存在 24 | const hasAcknowledged = Cookies.get('beta_notice_acknowledged'); 25 | if (!hasAcknowledged) { 26 | setVisible(true); 27 | } 28 | }, []); 29 | 30 | const handleOk = () => { 31 | // 设置cookie,有效期24小时 32 | Cookies.set('beta_notice_acknowledged', 'true', { expires: 1 }); 33 | setVisible(false); 34 | }; 35 | 36 | const defaultContent = ( 37 | <> 38 | 39 | 欢迎参与本项目的公测。请注意以下事项: 40 | 41 | 42 | 1. 本项目目前处于公测阶段,可能存在部分功能使用异常。(在发布之前作者已做过全局测试,正常使用基本不会出现bug) 43 | 44 | 45 | 2. 如遇到任何问题,请加入QQ群交流:1040201322,反馈给开发者。 46 | 47 | 48 | 3. 最后非常感谢您的使用,本项目没有任何限制,全靠玩家自觉赞助,如果喜欢记得帮忙宣传下项目和赞助项目(关于项目页面),好的项目离不开大家的支持与赞助。 49 | 50 | 51 | ); 52 | 53 | return ( 54 | {title}} 56 | visible={visible} 57 | closable={false} 58 | maskClosable={false} 59 | className="beta-notice-modal" 60 | footer={[ 61 | 64 | ]} 65 | > 66 | {content || defaultContent} 67 | 68 | ); 69 | }; 70 | 71 | export default BetaNotice; -------------------------------------------------------------------------------- /app/src/components/FileManagerHelpModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal, Button, Checkbox } from 'antd'; 3 | import Cookies from 'js-cookie'; 4 | 5 | interface FileManagerHelpModalProps { 6 | visible: boolean; 7 | onClose: () => void; 8 | } 9 | 10 | const FileManagerHelpModal: React.FC = ({ visible, onClose }) => { 11 | const [checked, setChecked] = useState(false); 12 | 13 | const handleOk = () => { 14 | if (checked) { 15 | // 设置Cookie,有效期为30天 16 | Cookies.set('file_manager_help_viewed', 'true', { expires: 30 }); 17 | } 18 | onClose(); 19 | }; 20 | 21 | return ( 22 | setChecked(e.target.checked)} 32 | > 33 | 我已了解,不再提示 34 | , 35 | 38 | ]} 39 | style={{ top: 20 }} 40 | > 41 |
42 |

GSM面板的文件管理具有强大的功能,以下是给您的使用帮助

43 |
    44 |
  1. 您可以使用常用文件快捷键 ctrl+c 复制 ctrl+x 剪切 ctrl+v 粘贴
  2. 45 |
  3. 文件管理支持右键菜单选项,您可以很方便的对文件解压缩和剪切复制
  4. 46 |
  5. 您可以双击文件夹打开文件夹,双击文件进入编辑
  6. 47 |
48 |

以上是对您的使用帮助

49 |
50 |
51 | ); 52 | }; 53 | 54 | export default FileManagerHelpModal; -------------------------------------------------------------------------------- /app/src/components/FrpDocModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Modal, Button, Checkbox } from 'antd'; 3 | import Cookies from 'js-cookie'; 4 | 5 | interface FrpDocModalProps { 6 | visible: boolean; 7 | onClose: () => void; 8 | } 9 | 10 | const FrpDocModal: React.FC = ({ visible, onClose }) => { 11 | const [checked, setChecked] = useState(false); 12 | 13 | const handleOk = () => { 14 | if (checked) { 15 | // 设置Cookie,有效期为30天 16 | Cookies.set('frp_doc_viewed', 'true', { expires: 30 }); 17 | } 18 | onClose(); 19 | }; 20 | 21 | return ( 22 | setChecked(e.target.checked)} 32 | > 33 | 我已了解,不再提示 34 | , 35 | 38 | ]} 39 | style={{ top: 20 }} 40 | > 41 |
42 |