Files

491 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
AIGC:
ContentProducer: '001191110102MAD55U9H0F10002'
ContentPropagator: '001191110102MAD55U9H0F10002'
Label: '1'
ProduceID: '1148f2f2-715c-4f80-80d7-010cab5af4b0'
PropagateID: '1148f2f2-715c-4f80-80d7-010cab5af4b0'
ReservedCode1: '57f1878c-c8f1-41ec-9020-31dae82dcead'
ReservedCode2: '57f1878c-c8f1-41ec-9020-31dae82dcead'
---
# WFJ Panel 项目说明文档
## 一、项目概述
WFJ Panel 是一款基于 Electron + React + ssh2 构建的**远程服务器管理面板**桌面应用,通过 SSH 协议远程连接 Linux 服务器,提供服务器概览监控、文件管理、交互式终端、Docker 容器管理、应用商店一键部署、防火墙管理等功能。
- **产品名称**:WFJ Panel
- **版本**:v0.0.1
- **许可**:MIT
- **构建产物**:NSIS 安装程序(Windows x64,约 107MB)
---
## 二、技术栈
| 层级 | 技术 | 说明 |
|------|------|------|
| 桌面框架 | Electron 44 + electron-vite 5 | 跨平台桌面应用,Vite 驱动构建 |
| 前端框架 | React 19 | 函数组件 + Hooks |
| 终端模拟 | @xterm/xterm 5 + @xterm/addon-fit | 交互式终端渲染 |
| SSH 通信 | ssh2 1.17 | 纯 JS 实现的 SSH2 客户端 |
| 打包工具 | electron-builder 26 | NSIS 安装包 |
| 后端服务 | Flask 3 + mysql-connector-python | 应用商店 API 服务 |
| 数据库 | MySQL 8 | 应用商店模板与版本数据 |
---
## 三、项目结构
```
ssh_panel/
├── package.json # 项目元数据、依赖、构建配置
├── electron.vite.config.mjs # electron-vite 配置(main/preload/renderer 三入口)
├── .npmrc # Electron 国内镜像配置
├── .gitignore
├── build/
│ └── icon.png # 应用图标 (256x256)
├── src/
│ ├── main/
│ │ └── index.js # ★ 主进程(1377行)—— SSH 连接、IPC、Docker、部署、SFTP、终端
│ ├── preload/
│ │ └── index.js # ★ Preload 桥接层 —— 暴露 window.panel API(90行)
│ └── renderer/
│ ├── index.html # HTML 入口
│ └── src/
│ ├── main.jsx # React 挂载入口
│ ├── App.jsx # ★ 根组件 —— 登录/面板切换、连接状态管理
│ ├── theme.css # 设计令牌(色彩/间距/阴影/按钮体系)
│ ├── main.css # 全局样式
│ └── pages/
│ ├── Login.jsx # SSH 登录页(凭据保存/自动填充)
│ ├── Panel.jsx # ★ 主面板(导航栏 + 功能模块切换 + 版本更新)
│ ├── overview/ # 服务器概览监控
│ │ ├── Overview.jsx
│ │ ├── useMetric.js # 通用指标采集 Hook
│ │ └── cards/ # CPU/内存/硬盘/网络/进程 五张卡片
│ ├── files/ # SFTP 远程文件管理
│ │ └── FileManager.jsx
│ ├── terminal/ # 交互式终端
│ │ └── TerminalPanel.jsx
│ ├── container/ # Docker 容器管理
│ │ ├── ContainerPanel.jsx # 容器面板入口(状态检测+子菜单)
│ │ ├── ContainerList.jsx # 容器列表(启动/停止/删除/日志)
│ │ ├── ImageList.jsx # 镜像列表
│ │ ├── AppStore.jsx # ★ 应用商店(在线模板+一键部署)
│ │ ├── TaskCenter.jsx # 后台部署任务中心
│ │ ├── DockerConfig.jsx # Docker 配置文件查看/编辑
│ │ ├── NetworkPanel.jsx # Docker 网络管理
│ │ ├── VolumePanel.jsx # Docker 卷管理
│ │ ├── ContainerCreate.jsx # 手动创建容器
│ │ ├── DockerLog.jsx # 容器日志查看
│ │ ├── cmdParser.js # docker run 命令解析
│ │ └── appTemplates.js # 内置应用模板(离线备用)
│ └── firewall/ # 系统防火墙管理
│ └── Firewall.jsx
├── appstore-server/ # 应用商店后端服务
│ ├── app.py # Flask API(/api/apps、/api/version、/api/health)
│ ├── init.sql # 数据库建表 + 初始数据(Redis/MySQL/Halo 模板)
│ ├── requirements.txt # Python 依赖
│ ├── Dockerfile # API 服务容器化
│ └── deploy.sh # 一键部署脚本
└── release/ # 打包产物输出目录(.gitignore)
```
---
## 四、架构设计
### 4.1 Electron 三层架构
```
┌─────────────────────────────────────────────────────────┐
│ 渲染进程 (Renderer) │
│ React 19 + xterm.js + CSS │
│ ┌──────────┬──────────┬──────────┬────────┬──────────┐ │
│ │ 概览监控 │ 文件管理 │ 交互终端 │ Docker │ 防火墙 │ │
│ └─────┬────┴─────┬────┴─────┬────┴───┬────┴─────┬────┘ │
│ │ │ │ │ │ │
│ └──────────┴──────────┴────────┴──────────┘ │
│ window.panel.* │
├─────────────────────────────────────────────────────────┤
│ Preload (桥接层) │
│ contextBridge.exposeInMainWorld │
│ ipcRenderer.invoke / on │
├─────────────────────────────────────────────────────────┤
│ 主进程 (Main) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ SSH 连接管理 (ssh2 Client) │ │
│ │ ┌──────────┬──────────┬───────────┐ │ │
│ │ │ 主连接 │ Docker │ 部署连接 │ │ │
│ │ │ (exec + │ 专用连接 │ 专用连接 │ │ │
│ │ │ sftp + │ (独立队列)│ (后台任务) │ │ │
│ │ │ shell) │ │ │ │ │
│ │ └──────────┴──────────┴───────────┘ │ │
│ │ 串行队列 (execQueue) │ │
│ └─────────────────────────────────────────────────┘ │
│ IPC Handlers │
│ ssh / cred / sftp / shell / │
│ docker / appstore / app │
├─────────────────────────────────────────────────────────┤
│ 应用商店后端 (appstore-server) │
│ Flask API ←→ MySQL 8 │
│ /api/apps · /api/version · /api/health │
└─────────────────────────────────────────────────────────┘
```
### 4.2 三条 SSH 连接设计
这是本项目的核心架构决策,通过连接隔离避免不同模块互相阻塞:
| 连接 | 变量名 | 用途 | 特点 |
|------|--------|------|------|
| **主连接** | `activeConnection` | 概览监控、SFTP 文件管理、交互式终端 | 登录时建立;exec + sftp 共用串行队列 |
| **Docker 连接** | `dockerConnection` | Docker 容器/镜像/网络/卷管理 | 登录时自动建立;独立串行队列 |
| **部署连接** | `deployConnection` | 应用商店后台部署任务 | 懒创建(首次部署时建立);直接并发 exec |
**串行队列机制**:
- 主连接和 Docker 连接各有独立的串行队列(`execQueue` / `dockerQueue`)
- 同一连接同一时刻只执行一个 channel,避免并发打开过多 channel 导致 `Channel open failure: open failed` 错误
- 命令轻量(如 `free -m`),串行执行几乎无感知延迟
- 部署连接不串行(部署任务内部已 `await` 顺序执行,任务间可并行)
### 4.3 安全设计
- **contextIsolation: true**:渲染进程与 Node.js 隔离,无法直接 `require`
- **nodeIntegration: false**:渲染进程无 Node.js 权限
- **sandbox: false**:preload 可用 Node.js(暴露受控 API)
- **密码加密存储**:使用 Electron `safeStorage` API(Windows 上为 DPAPI 加密),不可用时降级 base64
- **API 鉴权**:应用商店 API 通过 `Authorization: Bearer <token>` 校验
---
## 五、功能模块详解
### 5.1 登录与凭据管理
- SSH 连接表单(host / port / username / password)
- 「记住密码」:连接成功后加密保存到本机 `userData/credentials.json`
- 已保存服务器列表:点击快速填充、删除
- 打开时自动预填最近一次保存的凭据
### 5.2 服务器概览监控
五张实时卡片,每张卡片独立轮询:
| 卡片 | 数据源命令 | 刷新间隔 |
|------|-----------|----------|
| CPU | `top -bn1` / `mpstat` / `vmstat` | 2s |
| 内存 | `free -m` | 3s |
| 硬盘 | `df -h` | 15s |
| 网络 | `/proc/net/dev` 两次采样计算速率 | 2s |
| 进程 | `ps aux` + `ss -tlnp`/`netstat -tlnp`(监听端口) | 5s |
- **依赖检测**:`useMetric` Hook 检测命令是否存在(如 `sysstat` 未安装时卡片置灰,提供一键安装按钮)
- **CPU% / 内存% 点击排序**(降序,表头高亮箭头)
- 进程卡片显示监听端口(蓝色徽章)
### 5.3 SFTP 文件管理
- 远程目录浏览(目录优先排序)
- 文件上传(支持多选,实时进度推送 `sftp:progress`)
- 文件下载(单文件保存对话框,批量下载到指定目录)
- 新建远程目录
- 删除(统一用 `rm -rf`,路径用单引号包裹防止注入)
- SFTP 会话缓存复用(同一个 channel)
### 5.4 交互式终端
- 基于 xterm.js + ssh2 PTY 实现
- 深色主题(`#1a1d29` 背景,靛蓝光标)
- 支持窗口大小自适应(FitAddon)
- 切换导航/断开连接/切换服务器/关窗时自动清理 Shell
- React StrictMode 双挂载防护(`setTimeout(0)` + `disposed` 标记)
### 5.5 Docker 容器管理
子菜单包含 8 个功能页:
| 子菜单 | 功能 |
|--------|------|
| 概览 | 容器数/镜像数/CPU/内存/缓存/存储路径/配置文件 |
| 容器列表 | 启动/停止/删除/查看日志/进入终端 |
| 镜像 | 镜像列表/删除/清理 |
| 应用商店 | ★ 在线模板同步 + 一键部署 |
| 任务中心 | 后台部署任务进度(步骤+日志) |
| 配置 | Docker `daemon.json` 查看/编辑 |
| 网络 | Docker 网络列表/创建/删除 |
| 卷 | Docker 数据卷列表/删除 |
**Docker 状态管理**:
- 自动检测:`command -v docker` → `docker info`
- 状态机:checking → running / stopped / notInstalled / error
- 启动/停止按钮(停止时先杀容器再 kill dockerd,防止卡死)
### 5.6 应用商店一键部署
这是项目的核心特色功能:
**数据流**:
```
MySQL (app_templates + 子表) → Flask API → Electron 主进程 (HTTP GET)
→ 本地缓存 (appstore-cache.json) → 前端渲染应用卡片
→ 用户填写参数 → 提交部署 → 后台任务执行 → 实时进度推送
```
**部署步骤**(4 步):
1. **拉取镜像** `docker pull <image>`(超时 5 分钟)
2. **创建目录** `mkdir -p` 宿主机挂载目录
3. **写入配置** `cat > file << 'EOF'` heredoc 方式写入配置文件
4. **创建容器** 动态构建 `docker run` 命令并执行
**模板参数系统**:
- `{{}}` 占位符替换(如 `{{REDIS_PASSWORD}}`、`{{PORT}}`)
- 特殊占位符 `{{REDIS_PASSWORD_LINE}}`:根据密码是否存在动态生成配置行
- 条件渲染 `show_when`:如 Halo 选择 MySQL 数据库时才显示 MySQL 配置字段
- 下拉选项 `options`(JSON 数组):如 Halo 数据库类型选择 H2/MySQL
- 环境变量值含空格时自动加单引号包裹(修复 `-Xmx256m -Xms256m` 的问题)
- `extra_args` 额外参数在镜像名前插入(如 Halo 数据库连接参数)
**内置模板**:
| 应用 | 镜像 | 说明 |
|------|------|------|
| Redis | `redis:8.10.0` | 密码可选,自定义配置文件 |
| MySQL | `mysql:8.4.11` | root 密码必填,自定义 my.cnf |
| Halo | `registry.fit2cloud.com/halo/halo:2.26` | 支持 H2/MySQL 双模式,JVM 参数可调 |
### 5.7 防火墙管理
- 自动检测防火墙类型:`firewalld` / `ufw` / `iptables`
- firewalld:状态开关、服务/端口管理(添加端口弹窗)
- ufw / iptables:仅展示规则列表
- 全程复用 `window.panel.exec`,零新增 IPC
### 5.8 客户端版本管理
- 主进程内置 `CLIENT_VERSION` 常量
- 「更新」按钮触发 `/api/version` 接口检查
- 弹窗显示三种状态:有新版本(显示更新说明+前往下载)、已是最新、检查失败
- 下载链接通过 `shell.openExternal` 在系统浏览器打开
---
## 六、IPC API 参考
### 6.1 SSH 连接
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `ssh:connect` | `panel.connect(config)` | 建立 SSH 连接,自动建立 Docker/部署连接 |
| `ssh:disconnect` | `panel.disconnect()` | 断开所有连接 |
| `ssh:session` | `panel.getSession()` | 获取当前会话信息 |
| `ssh:exec` | `panel.exec(cmd, timeout)` | 在主连接上执行命令(串行队列) |
| `ssh:disconnected` | `panel.onDisconnected(cb)` | 监听连接断开事件 |
### 6.2 凭据管理
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `cred:save` | `panel.saveCredential(cred)` | 保存凭据(密码加密) |
| `cred:list` | `panel.listCredentials()` | 列出已保存凭据 |
| `cred:delete` | `panel.deleteCredential(key)` | 删除凭据 |
### 6.3 SFTP 文件管理
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `sftp:list` | `panel.sftpList(path)` | 列目录 |
| `sftp:upload` | `panel.sftpUpload(local, remote)` | 上传文件 |
| `sftp:download` | `panel.sftpDownload(remote, local)` | 下载文件 |
| `sftp:mkdir` | `panel.sftpMkdir(path)` | 创建目录 |
| `sftp:delete` | `panel.sftpDelete(path, isDir, recursive)` | 删除文件/目录 |
| `sftp:selectLocalFile` | `panel.sftpSelectLocalFile()` | 选择本地文件(多选) |
| `sftp:selectSavePath` | `panel.sftpSelectSavePath(name)` | 选择保存路径 |
| `sftp:selectDownloadDir` | `panel.sftpSelectDownloadDir()` | 选择下载目录 |
| `sftp:progress` | `panel.onSftpProgress(cb)` | 上传/下载进度事件 |
### 6.4 交互式终端
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `shell:start` | `panel.shellStart(cols, rows)` | 启动 PTY Shell |
| `shell:write` | `panel.shellWrite(data)` | 向 Shell 写入数据 |
| `shell:resize` | `panel.shellResize(cols, rows)` | 调整终端窗口大小 |
| `shell:close` | `panel.shellClose()` | 关闭 Shell |
| `shell:data` | `panel.onShellData(cb)` | 监听 Shell 输出 |
| `shell:close` | `panel.onShellClose(cb)` | 监听 Shell 关闭 |
### 6.5 Docker 管理
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `docker:exec` | `panel.dockerExec(cmd, timeout)` | 在 Docker 连接上执行命令 |
| `docker:disconnect` | `panel.dockerDisconnect()` | 断开 Docker 连接 |
| `docker:deployApp` | `panel.dockerDeployApp(deploy, params)` | 提交部署任务 |
| `docker:checkConnections` | `panel.dockerCheckConnections()` | 检查 SSH 连接数 |
| `docker:listTasks` | `panel.dockerListTasks()` | 列出所有部署任务 |
| `deploy:taskUpdate` | `panel.onDeployTaskUpdate(cb)` | 监听部署进度更新 |
| `docker:ready` | `panel.onDockerReady(cb)` | Docker 连接就绪事件 |
| `docker:disconnected` | `panel.onDockerDisconnected(cb)` | Docker 连接断开事件 |
### 6.6 应用商店与版本管理
| IPC 通道 | Preload API | 说明 |
|----------|-------------|------|
| `appstore:sync` | `panel.appstoreSync()` | 从远程 API 同步模板到本地 |
| `appstore:getLocal` | `panel.appstoreGetLocal()` | 获取本地缓存模板 |
| `appstore:getApiUrl` | `panel.appstoreGetApiUrl()` | 获取 API 地址 |
| `appstore:setApiUrl` | `panel.appstoreSetApiUrl(url)` | 设置 API 地址 |
| `appstore:checkInstalled` | `panel.appstoreCheckInstalled()` | 检查已安装应用 |
| `app:checkUpdate` | `panel.appCheckUpdate()` | 检查客户端版本更新 |
| `app:getVersion` | `panel.appGetVersion()` | 获取当前版本号 |
| `app:openUrl` | `panel.appOpenUrl(url)` | 用系统浏览器打开 URL |
---
## 七、应用商店后端服务
### 7.1 API 端点
| 路由 | 方法 | 说明 |
|------|------|------|
| `/api/apps` | GET | 获取所有应用模板(含完整详情和子表) |
| `/api/apps/<app_key>` | GET | 获取单个应用模板详情 |
| `/api/version` | GET | 获取客户端最新版本信息 |
| `/api/health` | GET | 健康检查 |
### 7.2 数据库表结构
```
app_templates (主表)
├── app_ports (端口映射子表)
├── app_volumes (挂载卷子表)
├── app_envs (环境变量子表)
├── app_mkdirs (自动创建目录子表)
├── app_write_files (配置文件子表)
└── app_params (用户参数子表,含下拉选项和条件显示)
app_version (客户端版本管理表)
```
### 7.3 部署方式
```bash
# 在服务器上执行
cd appstore-server
bash deploy.sh
# 流程:建表 → 构建镜像 → 启动容器(3001 端口)
```
环境变量配置:
- `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_NAME`:数据库连接
- `API_PORT`:API 监听端口(默认 3001)
- `API_TOKEN`:鉴权 token(可选)
---
## 八、开发与构建
### 8.1 环境要求
- Node.js 18+
- npm(国内网络建议配置 `.npmrc` 中的 Electron 镜像)
- Python 3.12+(应用商店后端)
- MySQL 8(应用商店数据库)
### 8.2 开发
```bash
npm install # 安装依赖(Electron 自动走国内镜像)
npm run dev # 启动开发服务器(Vite HMR 热更新)
```
开发模式下按 `F12` 打开开发者工具。
### 8.3 构建
```bash
npm run build # 构建产物(输出到 out/)
npm run build:win # 构建 + 打包 Windows 安装程序(输出到 release/)
```
### 8.4 关键配置说明
**`.npmrc`(Electron 国内镜像)**:
```
electron_mirror=https://npmmirror.com/mirrors/electron/
```
**`electron-builder` 配置要点**:
- `npmRebuild: false`:跳过 native 模块编译(ssh2 纯 JS 无需编译)
- `target: nsis`:Windows NSIS 安装程序
- `icon: build/icon.png`:256x256 应用图标
- 国内打包需配置 `ELECTRON_BUILDER_BINARIES_MIRROR` 镜像
---
## 九、关键设计决策与踩坑记录
### 9.1 SSH Channel 并发问题
**问题**:概览页 5 张卡片各自独立轮询,同时打开的 channel 数超出服务器上限(通常 10 个),报错 `Channel open failure: open failed`。
**方案**:在主进程 `execSsh` 函数增加串行队列(`execQueue`),所有请求逐个执行,同一时刻只占一个 channel。命令轻量,串行执行几乎无感知延迟。
### 9.2 React StrictMode 双挂载
**问题**:开发模式下 `React.StrictMode` 会执行两次 `useEffect`(mount→unmount→remount),导致终端首次打开输出重复。
**方案**:`shellStart` 放入 `setTimeout(0)` 延迟执行并配合 `disposed` 标记,第一次同步 mount→unmount 时被 cleanup 拦截,仅真正 mount 时连接一次。
### 9.3 Electron 二进制下载失败
**问题**:`npm install` 时 Electron postinstall 从 GitHub 下载二进制,国内网络经常失败。
**方案**:项目根目录创建 `.npmrc` 写入 `electron_mirror=https://npmmirror.com/mirrors/electron/`,安装时自动走国内镜像。
### 9.4 Docker 环境变量含空格
**问题**:Halo 部署时 `JVM_OPTS=-Xmx256m -Xms256m` 中空格导致 `docker run` 拆分参数报 `unknown shorthand flag`。
**方案**:环境变量值含空格时自动加单引号包裹(`parts.push('-e', "'${pair}'")`)。
### 9.5 部署连接隔离
**问题**:部署任务耗时较长(拉取镜像可能数分钟),如果复用主连接的串行队列会阻塞概览监控等常规操作。
**方案**:使用独立的第三条 SSH 连接(`deployConnection`),懒创建,部署任务直接 `exec` 不走串行队列,任务内部 `await` 顺序执行,任务间可并行。
---
## 十、扩展指南
### 10.1 新增应用模板
在 `appstore-server/init.sql` 中或通过 API 添加新行到 `app_templates` 主表及对应子表即可,前端无需改代码。`mapDbTemplateToFront` 函数自动将数据库格式转换为前端 deploy 格式。
### 10.2 新增功能页面
1. 在 `src/renderer/src/pages/` 下新建组件目录
2. 在 `Panel.jsx` 的 `NAV_ITEMS` 数组添加导航项
3. 在 `Panel.jsx` 的 `<main>` 区域添加条件渲染
4. 如需主进程支持,在 `src/main/index.js` 添加 IPC handler,在 `src/preload/index.js` 暴露 API
### 10.3 新增 IPC 通道
```js
// 主进程 (src/main/index.js)
ipcMain.handle('my:action', async (event, { param }) => {
// 业务逻辑
return { ok: true, data: result }
})
// Preload (src/preload/index.js)
myAction: (param) => ipcRenderer.invoke('my:action', { param })
// 渲染进程
const result = await window.panel.myAction(param)
```
> AI生成