release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Changelog — 告警推送:浏览器仅状态 + Telegram 通知
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**SSOT**: `docs/project/alert-push-policy.md`
|
||||
|
||||
## 产品决策
|
||||
|
||||
| 通道 | 行为 |
|
||||
|------|------|
|
||||
| Telegram | 告警/恢复/系统主动通知(5 分钟防抖) |
|
||||
| 浏览器 | 只显示统计与运行状态;无声音、无告警列表 |
|
||||
| WebSocket | 触发 `loadDashboard()`,不触发 `loadSyncs()` |
|
||||
|
||||
## 代码
|
||||
|
||||
- `web/app/index.html` — 运行状态面板;移除 `_playAlertSound`、实时告警横幅
|
||||
- `server/api/websocket.py` — 文档注释对齐
|
||||
- `deploy/nginx_https.conf` — `/ws/` 独立 access_log,不记录 query(JWT 不进日志)
|
||||
|
||||
## 部署
|
||||
|
||||
生产 Nginx 若已上线,合并 `location /ws/` 的 `log_format nexus_ws` 与 `access_log ... nexus_ws` 后 reload。
|
||||
|
||||
## 阈值对齐(第 5 项)
|
||||
|
||||
- 中心:`settings` / MySQL `cpu_alert_threshold` 等
|
||||
- 子机:`config.json` → `alert_thresholds`(见 `config.example.json`)
|
||||
- 修改中心阈值后,请同步更新各 Agent 的 `alert_thresholds`
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Phase 2 审计待办修复(P1/P2 批次)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 已修复
|
||||
|
||||
| ID | 说明 | 变更 |
|
||||
|----|------|------|
|
||||
| F2h-03 | Agent `/exec/kill` 不得对未登记 pid 调用 `os.kill` | `web/agent/agent.py` 仅杀 `_running_procs` 内进程,否则 404 |
|
||||
| F2h-04 | `/exec/wait` 超时未清理 `_running_procs` | 超时分支 `pop` + `kill`/`wait` |
|
||||
| F2a-08 | 安装后安装向导仍可 POST | `install.py` 统一 `_reject_if_installed()` → 403(除 `GET /status`) |
|
||||
| F2g-03 | `apply_update` 中 `json.loads(server_ids)` 无防护 | `_parse_server_ids_json()` |
|
||||
| F2i-01 | `update_status` 无记录时 flush 异常 | 返回 `Optional`;`flush_to_mysql` 跳过并出队 |
|
||||
| F2i-02 | 死模型 `AgentExec` | 已从 `schemas.py` 删除 |
|
||||
| F2g-05 / F2h-01 | `credential_id` 未贯通 | 模型列 + schema + API + 存库 + retry 继承 |
|
||||
| F2b-05 | `duration_seconds` 用 `.seconds` | `sync_engine_v2.py` → `total_seconds()` |
|
||||
| F2a-13 | Dashboard `stats.online` 用 MySQL | `servers.py` 优先 Redis `heartbeat:*` |
|
||||
| F2g-02(部分) | 脚本回调无限流 | `script_callback_rate.py` + `agent.py` 429 |
|
||||
|
||||
## 第二批(2026-05-22 续)
|
||||
|
||||
| ID | 说明 | 变更 |
|
||||
|----|------|------|
|
||||
| F2d-03 | 安装模式仍可连 WS | `main.py` 安装模式 `/ws/` → 503 |
|
||||
| F2d-04 | MySQL 异常原文进 Telegram | `self_monitor.py` 通用文案 |
|
||||
| F2g-02 | 回调可能 HTTP | `master_callback_url()` 非 localhost 强制 HTTPS |
|
||||
| F2g-06 / F2h-02 | script-callback 无限流 | 见上(已完成) |
|
||||
| F2c-05 | 迁移失败仅 log | `migrations.py` 非重复列错误 re-raise |
|
||||
| F2f-05 | CREATE DATABASE f-string | `bootstrap_database.py` 库名白名单 |
|
||||
| F2f-07 | wsl 脚本硬编码路径 | `wsl_mcp_mysql.sh` 相对仓库根目录 |
|
||||
| F2g-04(文档) | 重试新 id | API `parent_execution_id` + SSOT 文档 |
|
||||
| F2f-01 / F2g-01(文档) | shell 执行面 | SSOT §4.1 + Agent WARNING 日志 |
|
||||
| — | 前端 credential | `scripts.html` DB 凭据下拉 |
|
||||
| F2f-06(部分) | P0 回归测试 | `tests/test_security_unit.py` |
|
||||
|
||||
## 未改(需架构/运维)
|
||||
|
||||
- F2f-01 / F2g-01:`subprocess` shell 执行(文档/隔离)
|
||||
- F2g-04:重试生成新 execution id(产品设计)
|
||||
- F2a-10 / F2d-02:全局 JWT 收紧
|
||||
- F2e-03 / F2a-18:告警 WS — **已讨论接受**,不做换票
|
||||
- F2f-04:轮换曾泄露 API Key(运维)
|
||||
|
||||
## 部署注意
|
||||
|
||||
启动时会执行 schema migration:`script_executions.credential_id`。已有库无需手工 SQL。
|
||||
@@ -0,0 +1,37 @@
|
||||
# Changelog — 本地 .env 远程 MySQL 配置
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 配置(仅本机,未提交 git)
|
||||
|
||||
## 变更摘要
|
||||
|
||||
为 worktree 创建项目根 `.env`,指向远程 MySQL `172.31.170.47`,并运行 `scripts/sync_mysql_mcp_env.py` 写入 MCP 用 `MYSQL_*` 变量。
|
||||
|
||||
## 配置项(不含密码)
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| `NEXUS_DATABASE_URL` | `mysql+aiomysql://Nexus@172.31.170.47:3306/nexus` |
|
||||
| `MYSQL_READONLY` | `true` |
|
||||
| `MYSQL_DATABASE_ALLOWLIST` | `nexus` |
|
||||
| 本地密钥 | 已生成 `NEXUS_SECRET_KEY` / `API_KEY` / `ENCRYPTION_KEY`(供后端启动) |
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `.env`(gitignore,仅本机)
|
||||
- `scripts/sync_mysql_mcp_env.py`(已执行)
|
||||
|
||||
## 连通性
|
||||
|
||||
从当前开发机测试:**无法连接** `172.31.170.47:3306`(`Can't connect to MySQL server`)。
|
||||
`172.31.x` 多为内网/VPC 地址,需 VPN、跳板机或同网段环境后 MCP/后端方可连库。
|
||||
|
||||
## 用户操作
|
||||
|
||||
1. 确保能访问 `172.31.170.47:3306`(防火墙/安全组放行本机 IP)
|
||||
2. Cursor → Settings → MCP → 启用 `mysql-mcp` → Reload Window
|
||||
3. 对话中测试 `test_connection`
|
||||
|
||||
## 安全提醒
|
||||
|
||||
数据库密码仅存在于本地 `.env`;若在聊天中暴露,建议在数据库侧轮换密码并重新执行 `sync_mysql_mcp_env.py`。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 前端 CDN 自托管(F2e-06)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 变更
|
||||
|
||||
- 新增 `web/app/vendor/`:Tailwind Browser 4.0.6、Alpine 3.14.8、xterm 5.3.0、qrious 4.0.2 等固定版本。
|
||||
- 新增 `scripts/sync_frontend_vendor.py`:下载 vendor 并批量将 16 个 HTML 的 CDN URL 替换为 `/app/vendor/*`。
|
||||
- `web/app/vendor/manifest.json`:记录版本、源 URL、`sha384` 完整性(供日后 Subresource Integrity 或审计)。
|
||||
|
||||
## 升级 vendor
|
||||
|
||||
```bash
|
||||
# 1. 修改 scripts/sync_frontend_vendor.py 中 VENDOR_MANIFEST 版本
|
||||
# 2. 执行
|
||||
python scripts/sync_frontend_vendor.py
|
||||
# 3. 提交 vendor/ + HTML + manifest.json
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_security_unit.py::test_html_pages_do_not_load_external_cdn -q
|
||||
```
|
||||
|
||||
浏览器 Network 面板应无 `cdn.jsdelivr.net` 请求。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 全局 JWT 中间件(F2a-10 / F2d-02)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 变更
|
||||
|
||||
- `server/api/auth_jwt.py`:新增 `JwtAuthMiddleware`,对所有 `/api/*` 请求在路由处理前校验 Bearer JWT;公开前缀见 `PUBLIC_PREFIXES`(含 `/api/install/`、`/api/auth/logout`)。
|
||||
- `server/main.py`:在 `DbSessionMiddleware` 内层注册中间件(保证 `request.state.db` 可用)。
|
||||
- `get_current_admin`:复用 `request.state.admin`,避免重复查库。
|
||||
- 安装模式(无 `.env`)跳过 JWT 中间件,由 `InstallModeMiddleware` 返回 503。
|
||||
|
||||
## 纵深防御
|
||||
|
||||
路由层 `Depends(get_current_admin)` 保留;即使新路由漏挂 Depends,中间件仍会返回 401。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_step0_infrastructure.py::TestJWTAuth tests/test_nexus_v6.py::TestJWTAuth tests/test_security_unit.py -q
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
# Changelog — 完美实现与文档纪律全局规则
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 流程 / 规范
|
||||
**动机**: 将「完美实现、禁止降级/妥协/技术债、文档先行、每次修改留 changelog」固化为项目全局约束,供 Cursor 与所有 Agent 会话强制执行。
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 新增 Cursor 全局规则 `.cursor/rules/perfect-implementation.mdc`(`alwaysApply: true`)
|
||||
2. 在 `CLAUDE.md`、`AGENTS.md` 增加「实现原则」章节,与规则文件交叉引用
|
||||
3. 明确文档路径与执行顺序:设计文档 → 技术文档 → 实现 → changelog
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 操作 |
|
||||
|------|------|
|
||||
| `.cursor/rules/perfect-implementation.mdc` | 新增 |
|
||||
| `CLAUDE.md` | 增补实现原则与文档纪律 |
|
||||
| `AGENTS.md` | 增补实现原则表格 |
|
||||
| `docs/changelog/2026-05-22-implementation-standards-rule.md` | 新增(本文件) |
|
||||
|
||||
## 规则要点
|
||||
|
||||
- 禁止:为实现而实现、降级实现、妥协实现、留技术债
|
||||
- 无法完美实现:必须停下与用户沟通后才能换方案
|
||||
- 设计前:`docs/design/specs/YYYY-MM-DD-*-design.md`
|
||||
- 开发前:`docs/design/plans/` 或 `docs/reports/` 技术文档;缺文档模块改动时补全
|
||||
- 每次修改:`docs/changelog/YYYY-MM-DD-*.md`
|
||||
|
||||
## 验证
|
||||
|
||||
- [x] `.cursor/rules/perfect-implementation.mdc` 存在且 `alwaysApply: true`
|
||||
- [x] `CLAUDE.md` / `AGENTS.md` 已同步
|
||||
- [ ] 主仓库 `Nexus/CLAUDE.md` 若与 worktree 分叉,合并时需同步本节
|
||||
|
||||
## 部署/迁移
|
||||
|
||||
无需重启服务;规则在下次 Cursor 会话中自动生效。
|
||||
@@ -0,0 +1,42 @@
|
||||
# Changelog — 逐行走读审计标准与 Cursor 规则
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 文档 / 流程
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 新增 `docs/project/line-walk-audit-standard.md` — Phase 2 逐行走读 SSOT(8 步、规则表、DoD、八类、批次模板)
|
||||
2. 新增 `.cursor/rules/audit-line-review.mdc` — 审计任务时强制 closure 流程,禁止抽查冒充全量
|
||||
3. 计划 `.cursor/plans/nexus_全面代码审计_a33e6fc2.plan.md` §11 与上述文件对齐
|
||||
|
||||
## 背景
|
||||
|
||||
Phase 1 攻击面抽查漏报 `sync_engine_v2.py` sysctl 命令注入(S-19/S-20)。用户要求可复查的逐行走读标准。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 操作 |
|
||||
|------|------|
|
||||
| `docs/project/line-walk-audit-standard.md` | 新增 |
|
||||
| `.cursor/rules/audit-line-review.mdc` | 新增 |
|
||||
| `docs/changelog/2026-05-22-line-walk-audit-standard.md` | 新增 |
|
||||
|
||||
## 使用方式
|
||||
|
||||
- 审计:Agent 模式 + Prompt 见标准 §11
|
||||
- 自动约束:编辑 `server/`、`web/app/` 等路径时加载 `audit-line-review.mdc`
|
||||
- Phase 2b 报告:`docs/reports/audit-phase-2b-line-walk.md`(`server/application/services/`)
|
||||
- Phase 2a 报告:`docs/reports/audit-phase-2a-line-walk.md`(`server/api/`,16 文件)
|
||||
- 安全修复:`docs/changelog/2026-05-22-security-fixes-p0.md`、`2026-05-22-security-fixes-p1.md`
|
||||
- Phase 2c 报告:`docs/reports/audit-phase-2c-line-walk.md`(infrastructure 21 文件)
|
||||
- Phase 2d 报告:`docs/reports/audit-phase-2d-line-walk.md`(main + config + background)
|
||||
- Phase 2e 报告:`docs/reports/audit-phase-2e-line-walk.md`(`web/app/` 16 文件)
|
||||
- Phase 2f 报告:`docs/reports/audit-phase-2f-line-walk.md`(agent + tests + scripts,20 文件)
|
||||
- Phase 2g 报告:`docs/reports/audit-phase-2g-line-walk.md`(脚本执行平台 5 文件,+6 FINDING)
|
||||
- Phase 2h 报告:`docs/reports/audit-phase-2h-line-walk.md`(API/前端/子机 Agent 5 文件,+5 FINDING)
|
||||
- Phase 2i/2j:`audit-phase-2i-line-walk.md`、`audit-phase-2j-line-walk.md`
|
||||
- **Phase 2 闭环**:`docs/reports/audit-phase-2-walk-closure.md` — 可进入一并修复
|
||||
- **全量汇总表**:`docs/reports/audit-phase-2-findings-matrix.md`(50 条 FINDING + 修复队列)
|
||||
- 汇总表队列修复:`docs/changelog/2026-05-22-security-fixes-p2-queue.md`(F2f-04、F2e-01/02/07、F2f-02/03、F2a-12)
|
||||
- **脚本批量执行平台**:`docs/project/script-execution.md`(SSOT)
|
||||
- 交付记录:`docs/changelog/2026-05-22-script-execution-platform.md`(含分批 / 回调子文档索引)
|
||||
@@ -0,0 +1,48 @@
|
||||
# Changelog — 本地库最大权限 + WSL Node 20 安装修复
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 配置 / Bugfix / 工具链
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 修复 `scripts/wsl_install_node20.sh`:清理损坏代理、`curl --noproxy`、npmmirror 回退、`.cache` 离线安装
|
||||
2. 新增 `scripts/cache-node20-windows.ps1`(WSL DNS 失败时由 Windows 下载)
|
||||
3. `.env` 设置 `MYSQL_READONLY=false`;`sync_mysql_mcp_env.py` 增加 `--writable` / `--readonly`
|
||||
4. 新增 `scripts/grant_nexus_local.sql`(MySQL 侧 GRANT ALL)
|
||||
5. Node 20 已安装至 WSL `~/.local/node-v20.19.2/`;MCP 启动显示 `readonly: false`
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 操作 |
|
||||
|------|------|
|
||||
| `scripts/wsl_install_node20.sh` | 代理修复 + 镜像 + 离线 cache |
|
||||
| `scripts/cache-node20-windows.ps1` | 新增 |
|
||||
| `scripts/sync_mysql_mcp_env.py` | `--writable` / `--readonly` |
|
||||
| `scripts/grant_nexus_local.sql` | 新增 |
|
||||
| `.env` | `MYSQL_READONLY=false` |
|
||||
| `.env.example` | 注释本地可写 |
|
||||
| `.gitignore` | 忽略 `.cache/` |
|
||||
| `docs/project/mysql-mcp-setup.md` | 更新 |
|
||||
|
||||
## 用户待办
|
||||
|
||||
```bash
|
||||
# 1. Node 20(若 WSL curl 失败,先在 Windows 执行)
|
||||
pwsh scripts/cache-node20-windows.ps1
|
||||
bash scripts/wsl_install_node20.sh
|
||||
|
||||
# 2. MCP 可写同步
|
||||
python3 scripts/sync_mysql_mcp_env.py --writable
|
||||
|
||||
# 3. MySQL 授权(需 root,MySQL 服务需运行)
|
||||
mysql -h 172.31.170.47 -u root -p < scripts/grant_nexus_local.sql
|
||||
|
||||
# 4. Cursor Reload Window
|
||||
```
|
||||
|
||||
## 验证(2026-05-22 复测)
|
||||
|
||||
- [x] WSL `~/.local/node-v20.19.2/bin/node --version` → v20.19.2
|
||||
- [x] `wsl_mcp_mysql.sh` → `[MySQL MCP] Connected!` / `readonly: false`
|
||||
- [x] `wsl_check_mysql.py` → 16 张表
|
||||
- [x] `SHOW GRANTS` → `GRANT ALL PRIVILEGES ON nexus.* TO Nexus@%`(库级权限已具备)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — 修复 mysql-mcp Node 18 SyntaxError
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: Bugfix / MCP
|
||||
|
||||
## 问题
|
||||
|
||||
```
|
||||
SyntaxError: Unexpected token 'with'
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
```
|
||||
|
||||
WSL 默认 Node **v18.19.1**,不满足 `@yclenove/mysql-mcp-server` 对 Node **≥20** 的要求。
|
||||
|
||||
## 修复
|
||||
|
||||
1. 新增 `scripts/wsl_mcp_mysql.sh` — 优先 WSL Node 20,回退 Windows `C:\nvm4w\nodejs\node.exe` v22
|
||||
2. 更新 `.cursor/mcp.json` 调用该脚本
|
||||
3. 新增 `scripts/wsl_install_node20.sh` — 用户级安装 Node v20.19.2 到 `~/.local/`
|
||||
|
||||
## 用户操作
|
||||
|
||||
```bash
|
||||
# WSL 内(推荐,可同时连 172.31.x MySQL)
|
||||
bash scripts/wsl_install_node20.sh
|
||||
```
|
||||
|
||||
然后 **Cursor → Reload Window** 重新连接 MCP。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `.cursor/mcp.json`
|
||||
- `scripts/wsl_mcp_mysql.sh`
|
||||
- `scripts/wsl_install_node20.sh`
|
||||
- `docs/project/mysql-mcp-setup.md`
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — 脚本执行内置分批
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 功能
|
||||
**上级索引**: [2026-05-22-script-execution-platform.md](./2026-05-22-script-execution-platform.md)
|
||||
**SSOT**: [docs/project/script-execution.md](../project/script-execution.md)
|
||||
|
||||
## 变更摘要
|
||||
|
||||
一次可选全站服务器;超过 `batch_size` 时后端按批顺序执行,前端多轮 `POST /exec` 合并为一条页顶状态卡。
|
||||
|
||||
## 配置
|
||||
|
||||
| 项 | 默认 | 位置 |
|
||||
|----|------|------|
|
||||
| `NEXUS_SCRIPT_EXEC_BATCH_SIZE` | 50 | `.env` / `server/config.py` |
|
||||
| `NEXUS_SCRIPT_EXEC_CONCURRENCY` | 10 | 同上 |
|
||||
|
||||
## API / 前端
|
||||
|
||||
- `GET /api/scripts/exec-config` → `{ batch_size, concurrency }`
|
||||
- `ScriptService.execute_command` 按批执行,批内 `Semaphore(concurrency)`
|
||||
- `scripts.html`:全选、分批提示、「第 x/y 批」、长任务勾选
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/config.py`
|
||||
- `server/application/services/script_service.py`
|
||||
- `server/api/scripts.py`
|
||||
- `web/app/scripts.html`
|
||||
|
||||
## 用法
|
||||
|
||||
- 一次可全选所有服务器;超过 50 台自动分多批
|
||||
- 长任务:`timeout` 仅限制点火命令(建议 60~120s)
|
||||
@@ -0,0 +1,102 @@
|
||||
# Changelog — 脚本批量执行平台(完整交付)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 功能 / 架构
|
||||
**SSOT**: `docs/project/script-execution.md`
|
||||
|
||||
## 变更摘要
|
||||
|
||||
在原有「脚本库 + 单批 exec」基础上,交付**生产级批量执行平台**:
|
||||
|
||||
1. **内置分批** — 超 50 台自动多批 `POST /exec`,前端合并为一条状态卡(见 `2026-05-22-script-exec-batching.md`)
|
||||
2. **长任务** — nohup + 子机 curl 回调 + 页顶状态 / 远程 tail 日志(见 `2026-05-22-script-job-callback.md`)
|
||||
3. **Redis 热路径 + MySQL 分批落库** — 减轻 DB;读优先 Redis
|
||||
4. **短任务可停止** — Agent `/exec` + `/exec/wait` + `/exec/kill`
|
||||
5. **执行历史页** — 列表、进度、操作按钮、自动刷新
|
||||
6. **卡住治理** — 人工标记 + 单台/整单自动检测 + 审计
|
||||
|
||||
## 背景与决策
|
||||
|
||||
| 决策 | 选择 | 原因 |
|
||||
|------|------|------|
|
||||
| 进度查询 | JWT 轮询 `GET /executions/{id}` | 用户明确不要 WS 换票、不要独立 async exec API |
|
||||
| 执行态存储 | Redis 2h TTL | 前端实时;MySQL 只存历史 |
|
||||
| MySQL 写入 | 60s 批量最多 50 条 | 降低写压力;审计仍即时 |
|
||||
| 卡住阈值等 | 代码常量 | 用户要求不写 `.env` |
|
||||
| 分批大小 | `.env` 可配 | 与安装向导/运维调优一致 |
|
||||
|
||||
## 数据流(简图)
|
||||
|
||||
```
|
||||
POST /exec → ScriptService → Redis(script_exec:id) → 前端轮询
|
||||
↓ Agent (短/长)
|
||||
长任务结束 → curl → /api/agent/script-callback → Redis 更新
|
||||
每 60s → script_execution_flush → MySQL script_executions
|
||||
audit_logs ← 即时写入(各操作 action)
|
||||
```
|
||||
|
||||
## API 与页面(新增/扩展)
|
||||
|
||||
| 类型 | 项 |
|
||||
|------|-----|
|
||||
| API | `GET /scripts/executions`, `GET .../{id}?fetch_logs=`, `POST .../stop`, `retry`, `mark-stuck`, `POST /agent/script-callback` |
|
||||
| 页面 | `web/app/script-executions.html` |
|
||||
| 页面增强 | `scripts.html` 页顶状态条、执行历史入口;`audit.html` 脚本 action 筛选 |
|
||||
| Agent | `web/agent/agent.py` — `/exec` wait 参数、`/exec/wait`、`/exec/kill` |
|
||||
| 后台 | `server/background/script_execution_flush.py` |
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 新增
|
||||
|
||||
| 文件 |
|
||||
|------|
|
||||
| `server/infrastructure/redis/script_execution_store.py` |
|
||||
| `server/background/script_execution_flush.py` |
|
||||
| `server/application/services/script_jobs.py` |
|
||||
| `server/application/services/script_job_callback.py` |
|
||||
| `web/app/script-executions.html` |
|
||||
| `docs/project/script-execution.md` |
|
||||
| `docs/changelog/2026-05-22-script-execution-platform.md` |
|
||||
|
||||
### 修改
|
||||
|
||||
| 文件 | 要点 |
|
||||
|------|------|
|
||||
| `server/application/services/script_service.py` | 分批、长/短任务、stop/retry/list、Agent kill |
|
||||
| `server/api/scripts.py` | executions 路由 |
|
||||
| `server/api/agent.py` | script-callback |
|
||||
| `server/infrastructure/database/script_repo.py` | `list_recent` |
|
||||
| `server/main.py` | 注册 flush loop |
|
||||
| `server/config.py` | `SCRIPT_EXEC_*` |
|
||||
| `web/app/scripts.html` | 状态条、分批、长任务 |
|
||||
| `web/app/layout.js` | 执行历史菜单 |
|
||||
| `web/app/audit.html` | action 筛选 |
|
||||
| `web/agent/agent.py` | exec/wait/kill |
|
||||
|
||||
## 配置
|
||||
|
||||
| 项 | 位置 | 默认 |
|
||||
|----|------|------|
|
||||
| `NEXUS_SCRIPT_EXEC_BATCH_SIZE` | `.env` / `config.py` | 50 |
|
||||
| `NEXUS_SCRIPT_EXEC_CONCURRENCY` | `.env` / `config.py` | 10 |
|
||||
| `NEXUS_API_BASE_URL` | `.env` | 长任务必填 |
|
||||
| `REDIS_TTL_SECONDS` 等 | `script_execution_store.py` | 见 SSOT §1 |
|
||||
|
||||
## 部署注意
|
||||
|
||||
1. 主站重启后 `script_execution_flush` 随 primary worker 启动
|
||||
2. 子机需部署新版 `web/agent/agent.py` 才能短任务中途停止
|
||||
3. 长任务必须配置子机可访问的 `NEXUS_API_BASE_URL`
|
||||
|
||||
## 测试状态
|
||||
|
||||
| ID | 项 | 状态 |
|
||||
|----|-----|------|
|
||||
| T-SE1~T-SE8 | 见 `docs/project/script-execution.md` §11 | ⏳ 待 WSL/外网 |
|
||||
|
||||
## 关联文档
|
||||
|
||||
- 技术 SSOT: `docs/project/script-execution.md`
|
||||
- 分批: `docs/changelog/2026-05-22-script-exec-batching.md`
|
||||
- 回调与 Redis: `docs/changelog/2026-05-22-script-job-callback.md`
|
||||
@@ -0,0 +1,80 @@
|
||||
# Changelog — 长任务回调与 Redis 执行态
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 功能
|
||||
**上级索引**: [2026-05-22-script-execution-platform.md](./2026-05-22-script-execution-platform.md)
|
||||
**SSOT**: [docs/project/script-execution.md](../project/script-execution.md)
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 长任务:`nohup` + 子机结束 `curl` → `POST /api/agent/script-callback`
|
||||
2. 执行热数据 Redis(TTL 2h),MySQL 仅 60s 分批刷盘
|
||||
3. 短任务 stop、执行历史页、自动卡住检测(详见平台 changelog)
|
||||
|
||||
## 长任务流程
|
||||
|
||||
勾选「长任务」执行脚本时:
|
||||
|
||||
1. 主站为每台子机生成 `job_id` + 一次性 `secret`,写入 Redis(TTL 7 天)
|
||||
2. 主站用 `nohup` 包装脚本,脚本结束后在子机执行 `curl` → `POST /api/agent/script-callback`
|
||||
3. 主站更新 `script_executions.results`,并通过 WebSocket 推送 `script_job_done`
|
||||
|
||||
## 配置要求
|
||||
|
||||
- `.env` 中必须配置 **`NEXUS_API_BASE_URL`**(子机可访问的主站 HTTPS 地址),否则长任务接口返回 400
|
||||
|
||||
## API
|
||||
|
||||
| 端点 | 认证 | 说明 |
|
||||
|------|------|------|
|
||||
| `POST /api/scripts/exec` | JWT | 请求体增加 `long_task: true` |
|
||||
| `POST /api/agent/script-callback` | `job_id` + `secret`(一次性) | 子机 curl,无需 JWT |
|
||||
|
||||
回调 JSON 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "…",
|
||||
"server_id": 12,
|
||||
"secret": "…",
|
||||
"exit_code": 0,
|
||||
"message": "done"
|
||||
}
|
||||
```
|
||||
|
||||
## Redis → MySQL
|
||||
|
||||
| 常量 | 值 | 文件 |
|
||||
|------|-----|------|
|
||||
| `REDIS_TTL_SECONDS` | 7200 | `script_execution_store.py` |
|
||||
| `FLUSH_INTERVAL_SECONDS` | 60 | 同上 |
|
||||
| `FLUSH_BATCH_LIMIT` | 50 | 同上 |
|
||||
| `STUCK_NO_PROGRESS_SECONDS` | 3600 | 同上 |
|
||||
|
||||
- 全部状态变更只写 Redis;读接口优先 Redis
|
||||
- 后台 `script_execution_flush`:刷盘 + `detect_stuck_executions`
|
||||
- `results._meta.events` 时间线;`audit_logs` 即时写入
|
||||
|
||||
## 审计 action
|
||||
|
||||
`execute_started`、`execute_command`、`stop_execution`、`retry_execution`、`mark_stuck`、`script_job_callback`、`auto_stuck`、`server_auto_stuck`
|
||||
|
||||
## 子机 Agent(短任务停止)
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `POST /exec` + `wait: false` | 返回 `pid` |
|
||||
| `POST /exec/wait` | 等待结束 |
|
||||
| `POST /exec/kill` | 终止进程 |
|
||||
|
||||
未升级 Agent 时无法短任务中途停止。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/application/services/script_jobs.py`
|
||||
- `server/application/services/script_job_callback.py`
|
||||
- `server/infrastructure/redis/script_execution_store.py`
|
||||
- `server/background/script_execution_flush.py`
|
||||
- `server/api/agent.py`
|
||||
- `web/app/scripts.html`、`web/app/script-executions.html`
|
||||
- `web/agent/agent.py`
|
||||
@@ -0,0 +1,20 @@
|
||||
# Changelog — P0 安全修复(审计先行修复)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 修复项
|
||||
|
||||
| ID | 问题 | 修复 |
|
||||
|----|------|------|
|
||||
| F2a-01 | WebSSH IDOR:普通 JWT 可连任意 server_id | `POST /api/auth/webssh-token` 签发 `purpose=webssh` + `server_id`;`webssh.py` 强制匹配 |
|
||||
| F2a-02 | 中心机 `/api/agent/exec` 本机 RCE | 删除该路由;远程执行仅 `web/agent/agent.py` `/exec` |
|
||||
| F2c-01 | 解密失败返回密文当密码 | `decrypt_value` 失败抛 `ValueError` |
|
||||
| F2b-06 | `batch_push` 缺 `nonlocal` | `sync_service.py` 添加 `nonlocal completed` |
|
||||
| — | Agent 调用 URL 错误 | `script_service` 改为 `http://host:port/exec` |
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/api/webssh.py`, `server/api/auth.py`, `server/api/agent.py`
|
||||
- `server/application/services/auth_service.py`, `sync_service.py`, `script_service.py`
|
||||
- `server/infrastructure/database/crypto.py`
|
||||
- `web/app/terminal.html`
|
||||
@@ -0,0 +1,27 @@
|
||||
# Changelog — P1 安全修复(审计第二批)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 修复项
|
||||
|
||||
| ID | 修复 |
|
||||
|----|------|
|
||||
| F2b-01/02/03 | `sync_engine_v2` sysctl/echo/cp 整段 `shlex.quote`;回滚路径白名单 |
|
||||
| F2b-04/11/15 | 危险命令在 `sync_v2`、`sync_engine_v2`、`scripts/exec` **拒绝执行** |
|
||||
| F2b-07/08 | Agent URL 随 `API_BASE_URL` 使用 HTTPS(`agent_url.py`) |
|
||||
| F2b-09 | 凭据测试 API 不再返回含密码的 shell 命令 |
|
||||
| F2a-07 | TOTP 关闭需当前密码 + TOTP 验证码;递增 `token_version` |
|
||||
| F2a-08 | 安装完成后关闭 `env-check`/`state`;新装默认 `SSH_STRICT_HOST_CHECKING=true` |
|
||||
| F2a-09 | `create-admin` 更新 `.env` 使用 `_escape_env_value` |
|
||||
| F2a-16 | API Key 查看需重新输入管理员密码 + 审计 |
|
||||
| F2a-14 | `scripts` 路由:`/credentials` 注册在 `/{id}` 之前 |
|
||||
| F2c-02 | 安装向导写入严格 SSH 主机校验 |
|
||||
| F2c-04 | Telegram 告警 HTML 转义 |
|
||||
| — | `web/agent`:`compare_digest` + `sudo sh -c` 引用 |
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/application/services/sync_engine_v2.py`, `script_service.py`, `server_service.py`, `auth_service.py`
|
||||
- `server/api/sync_v2.py`, `scripts.py`, `auth.py`, `settings.py`, `install.py`
|
||||
- `server/infrastructure/agent_url.py`, `telegram/__init__.py`
|
||||
- `web/agent/agent.py`, `web/app/settings.html`
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — Phase 2 汇总表修复队列(第二批)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## 修复项
|
||||
|
||||
| ID | 问题 | 修复 |
|
||||
|----|------|------|
|
||||
| F2f-04 | 压测脚本硬编码 API Key | `quick_load.py` / `load_test.py` 改读 `NEXUS_API_KEY`、`NEXUS_TEST_BASE` 环境变量 |
|
||||
| F2e-01 | `servers.html` category 未 esc | `${esc(s.category\|\|'--')}` |
|
||||
| F2e-02 | `settings.html` saveSetting onclick 注入 | `data-setting-key` + `escAttr` + 事件委托 |
|
||||
| F2e-07 | `scripts.html` category 未 esc | `${esc(s.category\|\|'ops')}` |
|
||||
| F2f-02 | Agent 空 API_KEY 不鉴权 | 启动时无 key 则 `sys.exit(1)`;`verify_api_key` 始终校验 |
|
||||
| F2f-03 | Agent 默认 0.0.0.0 | 默认 `127.0.0.1`(`agent.py`、`config.example.json`、`install.sh`) |
|
||||
| F2a-12 | 生成 agent key 暴露面 | API 增加 `display_once`/`message`;列表仍掩码;编辑表单不填掩码值,生成后剪贴板+待保存 |
|
||||
|
||||
## 未在本批处理(仍 OPEN)
|
||||
|
||||
- F2f-01:子机 `/exec` subprocess_shell(设计面,需文档/网络隔离)
|
||||
- F2a-05/18、F2e-03/04:WS URL 带 token
|
||||
- F2a-10/F2d-02:全局 JWT 中间件
|
||||
- F2e-05/06:localStorage JWT、CDN SRI
|
||||
|
||||
## 运维提醒
|
||||
|
||||
1. **轮换 API Key**:旧密钥曾出现在 git 历史的压测脚本中,请在生产 `.env` / settings 表轮换 `API_KEY`。
|
||||
2. 压测:`NEXUS_API_KEY=xxx python tests/load_test.py --concurrency 50 --duration 30`
|
||||
3. 已有 Agent 若需中心机访问:在 `config.json` 将 `server.host` 改为 `0.0.0.0` 并配合防火墙。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `tests/quick_load.py`, `tests/load_test.py`
|
||||
- `web/app/servers.html`, `settings.html`, `scripts.html`
|
||||
- `web/agent/agent.py`, `config.example.json`, `install.sh`
|
||||
- `server/api/servers.py`
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — 安装 superpowers-zh
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 工具链 / Skills
|
||||
|
||||
## 变更摘要
|
||||
|
||||
通过 `npx superpowers-zh --tool cursor` 安装 [superpowers-zh](https://github.com/jnMetaCode/superpowers-zh) v1.5.0,共 **20 个** Agent Skills 到 `.cursor/skills/`。
|
||||
|
||||
## 安装命令(WSL)
|
||||
|
||||
```bash
|
||||
cd /mnt/c/Users/uzuma/Desktop/svn/Nexus/.claude/worktrees/suspicious-williams-fe9492
|
||||
npx superpowers-zh --tool cursor
|
||||
```
|
||||
|
||||
## Skills 列表
|
||||
|
||||
**工作流(自动):** brainstorming, writing-plans, executing-plans, test-driven-development, systematic-debugging, requesting-code-review, receiving-code-review, verification-before-completion, dispatching-parallel-agents, subagent-driven-development, using-git-worktrees, finishing-a-development-branch, writing-skills, using-superpowers, mcp-builder, workflow-runner
|
||||
|
||||
**中国特色(部分需手动 `/chinese-*`):** chinese-code-review, chinese-git-workflow, chinese-documentation, chinese-commit-conventions
|
||||
|
||||
## 卸载
|
||||
|
||||
```bash
|
||||
npx superpowers-zh@latest --uninstall
|
||||
```
|
||||
|
||||
## 生效方式
|
||||
|
||||
重启 Cursor 或 Reload Window。
|
||||
|
||||
## 备注
|
||||
|
||||
- WSL Node v18 有 EBADENGINE 警告(包要求 Node ≥20),安装仍成功;建议 WSL 升级到 Node 20+
|
||||
@@ -0,0 +1,46 @@
|
||||
# Changelog — 技术栈 Cursor 规则 + MySQL MCP 配置
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 规范 / 工具链
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 新增 4 条基于 Nexus 技术栈的 Cursor 规则(`.cursor/rules/`)
|
||||
2. 全局安装 `@yclenove/mysql-mcp-server`,配置项目 `.cursor/mcp.json`
|
||||
3. 新增 `scripts/sync_mysql_mcp_env.py` 从 `NEXUS_DATABASE_URL` 同步 `MYSQL_*`
|
||||
4. 新增 `docs/project/mysql-mcp-setup.md` 配置指南
|
||||
|
||||
## 新增 Cursor 规则
|
||||
|
||||
| 文件 | 作用域 |
|
||||
|------|--------|
|
||||
| `nexus-tech-stack.mdc` | alwaysApply — 分层、配置、数据流 |
|
||||
| `nexus-python-backend.mdc` | `server/**/*.py` |
|
||||
| `nexus-frontend.mdc` | `web/app/**` |
|
||||
| `nexus-security.mdc` | API/应用/DB/前端 — JWT、MCP 只读 |
|
||||
|
||||
## MCP
|
||||
|
||||
- 命令:`npx -y @yclenove/mysql-mcp-server`(Windows 经 `cmd /c`)
|
||||
- 连接:项目根 `.env` 内 `MYSQL_*`(由 sync 脚本生成,默认只读)
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `.cursor/rules/nexus-*.mdc`(4 个)
|
||||
- `.cursor/mcp.json`
|
||||
- `scripts/sync_mysql_mcp_env.py`
|
||||
- `docs/project/mysql-mcp-setup.md`
|
||||
- `.env.example`(MCP 注释块)
|
||||
- `docs/README.md`
|
||||
|
||||
## 用户待办(启用 MCP 前)
|
||||
|
||||
1. 确保有 `.env`(安装向导或 `copy .env.example .env`)
|
||||
2. `python scripts/sync_mysql_mcp_env.py`
|
||||
3. Cursor → Settings → MCP → 启用 `mysql-mcp` → Reload Window
|
||||
|
||||
## 验证
|
||||
|
||||
- [x] `npm install -g @yclenove/mysql-mcp-server@latest` 成功
|
||||
- [x] `mysql-mcp-server` 可执行并读取 worktree 为 cwd
|
||||
- [ ] 用户本地 `.env` + sync 后 `test_connection` 通过(需本机 MySQL)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Telegram 出站脱敏 + 安全回归测试(F2d-04 / F2f-06)
|
||||
|
||||
**日期**: 2026-05-22
|
||||
|
||||
## F2d-04 — Telegram 脱敏
|
||||
|
||||
- `server/infrastructure/telegram/__init__.py`:新增 `sanitize_external_message()`,屏蔽 DB/Redis URL、password、Bearer、密钥片段。
|
||||
- `send_telegram_system_alert()`:出站前脱敏 + `html.escape`。
|
||||
- `send_telegram()` 成功日志同样脱敏后再截断。
|
||||
- `self_monitor.py`:Redis/MySQL 失败详情写入 `logger.warning(..., exc_info=True)`;Telegram 仅固定文案。
|
||||
|
||||
## F2f-06 — 回归测试
|
||||
|
||||
`tests/test_security_unit.py` 扩展至 15 项,覆盖:
|
||||
|
||||
- 危险命令、`server_ids` JSON 解析、HTTPS callback
|
||||
- `sanitize_external_message` 脱敏与截断
|
||||
- `JwtAuthMiddleware` 401 / 公开路径跳过
|
||||
- `/api/install/`、`/api/auth/logout` 公开前缀
|
||||
- 安装后 `_reject_if_installed()` → 403
|
||||
|
||||
```bash
|
||||
pytest tests/test_security_unit.py -q
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — WSL MySQL 连接 + 建表 + MCP 配置
|
||||
|
||||
**日期**: 2026-05-22
|
||||
**类型**: 配置 / 数据库初始化
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. 确认 WSL 可连接 `172.31.170.47:3306`,库 `nexus` 已存在但无表
|
||||
2. 执行 `scripts/bootstrap_database.py` 创建 **16 张表**
|
||||
3. `server/config.py`:`SettingsConfigDict(extra="ignore")`,允许 `.env` 共存 `MYSQL_*`
|
||||
4. `.cursor/mcp.json` 改为经 **WSL** 启动 `mysql-mcp-server`
|
||||
5. 新增 `scripts/wsl_check_mysql.py`、`scripts/bootstrap_database.py`
|
||||
|
||||
## 表结构(已创建)
|
||||
|
||||
`admins`, `audit_logs`, `command_logs`, `db_credentials`, `login_attempts`, `nodes`, `password_presets`, `platforms`, `push_retry_jobs`, `push_schedules`, `script_executions`, `scripts`, `servers`, `settings`, `ssh_sessions`, `sync_logs`
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/config.py`
|
||||
- `.cursor/mcp.json`
|
||||
- `scripts/wsl_check_mysql.py`, `scripts/bootstrap_database.py`
|
||||
- `docs/project/mysql-mcp-setup.md`
|
||||
|
||||
## 验证(WSL)
|
||||
|
||||
```bash
|
||||
python3 scripts/wsl_check_mysql.py
|
||||
# 应显示 TABLES in nexus (16): [...]
|
||||
```
|
||||
|
||||
## 备注
|
||||
|
||||
- 数据迁移 `migrate_category_to_node` 首次可能因连接池 ping 报错(无历史数据可忽略)
|
||||
- Cursor 需 Reload Window 后 MCP 走 WSL 读 `.env`
|
||||
@@ -0,0 +1,23 @@
|
||||
# 2026-05-23 — AI 接续交接文档
|
||||
|
||||
## 摘要
|
||||
|
||||
新增 `docs/project/AI-HANDOFF-2026-05-23.md` 作为切换 AI / 新会话的单一事实来源(SSOT),汇总端口模型、已完成功能、待办 backlog、验证清单、否决项与文档债务。
|
||||
|
||||
## 动机
|
||||
|
||||
用户切换 AI 继续后续开发,需完整、可复制的接续记录,避免上下文丢失。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/project/AI-HANDOFF-2026-05-23.md`(新建)
|
||||
- `PROJECT_HANDOVER.md`(顶部指向 SSOT)
|
||||
- `docs/project/status.md`(链接 SSOT)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
打开 `docs/project/AI-HANDOFF-2026-05-23.md`,复制 §0 提示词到新会话即可。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 2026-05-23 — 开发验收标准文档
|
||||
|
||||
## 摘要
|
||||
|
||||
新增 `docs/project/development-acceptance-standard.md`,汇总 L0~L5 验收层级(原则、设计、日常命令、审计 DoD、模块 E2E、首次部署),并写入 AI 接续、standards 索引与功能说明书。
|
||||
|
||||
## 动机
|
||||
|
||||
切换 AI 时需明确「怎样算做完/可上线」,避免只读功能清单而忽略 pytest、走读 DoD、production checklist。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/project/development-acceptance-standard.md`(新建)
|
||||
- `docs/project/AI-HANDOFF-2026-05-23.md`
|
||||
- `docs/project/nexus-full-site-features.md`
|
||||
- `standards/README.md`
|
||||
- `docs/Nexus-功能说明书.html`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
新 AI 按 HANDOFF §0 提示词应加载 `development-acceptance-standard.md`。
|
||||
@@ -0,0 +1,22 @@
|
||||
# 全量审计对照修复(C2 / C9)
|
||||
|
||||
**日期**: 2026-05-23
|
||||
|
||||
## C2 — 认证审计日志字段
|
||||
|
||||
`server/application/services/auth_service.py`:
|
||||
|
||||
- `_audit()` 写入 `admin_username`、`ip_address` 列(不再仅堆在 detail 字符串里)
|
||||
- 登录/刷新/登出/TOTP/WebSSH 等调用点传递对应用户名与 IP
|
||||
|
||||
## C9 — WebSocket 告警重复投递
|
||||
|
||||
`server/api/websocket.py`:
|
||||
|
||||
- 新增 `_dispatch_ws_message()`:Redis Pub/Sub 活跃时 **仅 publish**,由本 worker 的 subscriber 调用一次 `broadcast_local`
|
||||
- Redis 不可用时回退为直接 `broadcast_local`
|
||||
- `broadcast_alert` / `broadcast_recovery` / `broadcast_system_event` 均改用该路径
|
||||
|
||||
## 文档
|
||||
|
||||
- `docs/reports/audit-full-vs-phase2-reconciliation.md` — 全量 C1–C12 对照表
|
||||
@@ -0,0 +1,25 @@
|
||||
# 2026-05-23 — 全站功能文档补齐
|
||||
|
||||
## 摘要
|
||||
|
||||
新增全站功能 Markdown SSOT,大幅扩充 HTML 功能说明书(18 页地图、告警中心、资产管理、命令/推送日志、网络模型等),并在 `standards/README.md` 增加文档索引。
|
||||
|
||||
## 动机
|
||||
|
||||
原功能说明书缺失大量已实现模块,且多处描述过期(如审计过滤、定时脚本、Agent 8601 等),不利于切换 AI 接续开发。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/project/nexus-full-site-features.md`(新建)
|
||||
- `docs/Nexus-功能说明书.html`(重写扩充)
|
||||
- `standards/README.md`
|
||||
- `docs/project/AI-HANDOFF-2026-05-23.md`(链接)
|
||||
- `docs/project/development-acceptance-standard.md`(新建,L0~L5 验收 SSOT)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
浏览器打开 `docs/Nexus-功能说明书.html` 检查目录与新增章节。
|
||||
@@ -0,0 +1,42 @@
|
||||
# Changelog — MCP Memory 服务器
|
||||
|
||||
**日期:** 2026-05-23
|
||||
|
||||
## 变更摘要
|
||||
|
||||
为 Cursor 工作区添加 `@modelcontextprotocol/server-memory` MCP 服务器,持久化 Agent 跨会话记忆。
|
||||
|
||||
## 动机
|
||||
|
||||
- 原配置指向已废弃的 `multi-server-sync` 仓库路径
|
||||
- Nexus 6.0 工作区需要独立的本地记忆文件,与 MySQL MCP 并列
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `.cursor/mcp.json` | 新增 `memory` 服务器条目 |
|
||||
| `.claude/mcp-memory.jsonl` | 本地记忆存储(空文件初始化) |
|
||||
| `.gitignore` | 忽略 `.claude/mcp-memory.jsonl` |
|
||||
|
||||
## 配置
|
||||
|
||||
```json
|
||||
"memory": {
|
||||
"command": "cmd",
|
||||
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"],
|
||||
"env": {
|
||||
"MEMORY_FILE_PATH": "<worktree>/.claude/mcp-memory.jsonl"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 迁移/重启
|
||||
|
||||
- 无需数据库迁移
|
||||
- **需重启 Cursor** 或重新加载 MCP 服务器后生效
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. Cursor → Settings → MCP → 确认 `memory` 显示为已连接
|
||||
2. Agent 调用 `create_entities` / `search_nodes` 等 memory 工具后,检查 `.claude/mcp-memory.jsonl` 是否有新行写入
|
||||
@@ -0,0 +1,31 @@
|
||||
# 2026-05-23 — MEDIUM Issue Fixes (Post-Audit Round 2)
|
||||
|
||||
## Summary
|
||||
Fixed all 14+ MEDIUM issues identified in the full codebase audit.
|
||||
|
||||
## Changes
|
||||
|
||||
### Backend Core
|
||||
- **WebSocket connection limits**: Added `MAX_CONNECTIONS=500` to ConnectionManager; rejects new connections when limit reached (memory DoS prevention)
|
||||
- **WebSSH message size limits**: Added `MAX_MSG_SIZE=100KB` and `MAX_DATA_SIZE=10KB` validation before processing WebSocket messages
|
||||
- **setting_repo SELECT-then-INSERT race**: Replaced TOCTOU pattern with MySQL `ON DUPLICATE KEY UPDATE` for atomic upsert
|
||||
- **Migration atomicity**: Removed intermediate `session.commit()` calls — single commit at end for all-or-nothing migration
|
||||
- **SyncLog/ScriptExecution return types**: Fixed `update_status()` return types from non-Optional to `Optional[SyncLog]` / `Optional[ScriptExecution]`
|
||||
|
||||
### Security Hardening
|
||||
- **Nginx rate limiting**: Added `limit_req_zone` (10 req/s per IP, burst 20) for API endpoints
|
||||
- **X-Forwarded-For IP extraction**: Added `_client_ip()` helper to auth.py that checks `X-Forwarded-For` header first, falls back to `request.client.host`
|
||||
- **CDN SRI integrity hashes**: Added `integrity` + `crossorigin` attributes to all CDN script/style tags across all 12 HTML pages (tailwindcss, alpinejs, xterm, xterm-addon-fit)
|
||||
- **Global 401 interceptor**: Added `window.fetch` override on all pages — auto-redirects to login on 401 responses
|
||||
|
||||
### Deployment
|
||||
- **Supervisor stopasgroup**: Added `stopasgroup=true` to ensure SIGINT reaches entire process tree
|
||||
- **health_monitor.sh hardening**: Added `set -euo pipefail`; wrapped FAIL_FILE read-modify-write in `flock` for atomicity
|
||||
- **health_monitor.sh fail count**: All 3 fail-count access points now use `flock`-protected critical sections
|
||||
|
||||
### Code Quality
|
||||
- **decrypt_value documentation**: Clarified backward-compat contract; added `try_decrypt_value()` for callers needing explicit success/failure signal
|
||||
- **Dual DB session deprecation**: Marked `get_async_session()` as deprecated with docstring pointing to middleware-based `request.state.db` pattern
|
||||
|
||||
## Files Changed
|
||||
39 files, 498 insertions, 210 deletions
|
||||
@@ -0,0 +1,20 @@
|
||||
# Phase 1 补充走读修复
|
||||
|
||||
**日期**: 2026-05-23
|
||||
**批次**: Phase 1(Step 0–5 遗留代码补充全文走读)
|
||||
|
||||
## 覆盖文件
|
||||
|
||||
| 文件 | 结论 |
|
||||
|------|------|
|
||||
| `server/infrastructure/telegram/__init__.py` | ✅ CLEAN(F2c-04/F2d-04 修复均正确) |
|
||||
| `server/domain/models/__init__.py` | 🔴 F1-01 修复 |
|
||||
| `server/background/script_execution_flush.py` | ✅ CLEAN |
|
||||
| `server/api/health.py` | ✅ CLEAN |
|
||||
| `server/infrastructure/database/ssh_session_repo.py` | ✅ CLEAN |
|
||||
|
||||
## 修复
|
||||
|
||||
| ID | 文件:行 | 级 | 说明 |
|
||||
|----|---------|----|----|
|
||||
| F1-01 | `domain/models/__init__.py:79-80` | P2 | `ssh_key_private`/`ssh_key_public` 列类型从 `String(500)` 改为 `Text`;Fernet 加密的 RSA 4096 私钥约 4300+ 字符,超出 String(500) 会在 MySQL 严格模式下报 DataError |
|
||||
@@ -0,0 +1,21 @@
|
||||
# 开发文档同步 — Phase 2 审计修复说明入库
|
||||
|
||||
**日期**: 2026-05-23
|
||||
|
||||
## 新增
|
||||
|
||||
- `docs/project/phase-2-audit-remediation.md` — Phase 2 修复 **开发 SSOT**(按主题汇总、接受决策、changelog 索引、剩余队列)
|
||||
|
||||
## 更新
|
||||
|
||||
- `docs/README.md` — 索引 Phase 2 文档、验证清单、告警/脚本 SSOT、changelog 分组
|
||||
- `docs/reports/README.md` — Phase 2 专节 + 实施状态行
|
||||
- `docs/reports/audit-phase-2-findings-matrix.md` — 统计同步(52/3/7/0)
|
||||
- `docs/reports/2026-05-23-full-audit-report.md` — 顶部交叉引用 Phase 2 SSOT
|
||||
- `docs/project/status.md` — Phase 2 进度块 + 完成度 ~98%
|
||||
|
||||
## 阅读顺序(新成员)
|
||||
|
||||
1. `docs/project/phase-2-audit-remediation.md`
|
||||
2. `docs/reports/audit-phase-2-findings-matrix.md`(查具体 ID)
|
||||
3. `docs/project/production-verification-checklist.md`(上线前)
|
||||
@@ -0,0 +1,24 @@
|
||||
# Phase 3 逐行走读修复
|
||||
|
||||
**日期**: 2026-05-23
|
||||
**批次**: 3a(安全核心)、3b(background)、3c(基础设施)、3d(main/middleware)
|
||||
|
||||
## 修复清单
|
||||
|
||||
| ID | 文件 | 级 | 说明 | 修复 |
|
||||
|----|------|----|------|------|
|
||||
| F3a-01 | `agent.py:97-102` | P2 | `float(cpu/mem/disk)` 对非数字 Agent 数据 → ValueError/500 | 提取 `_safe_float()` 包裹转换,非数字值 WARNING 级跳过 |
|
||||
| F3b-01 | `schedule_runner.py:84` | P1 | `now`(tz-aware)- `last_run_at`(MySQL naive)→ TypeError → 已跑调度永远失败 | 统一去除 tzinfo 后再减 |
|
||||
| F3b-02 | `schedule_runner.py:47` | P3 | cron `*/0` → ZeroDivisionError | step==0 时返回 False 跳过 |
|
||||
| F3c-01 | `redis/client.py:49` | P2 | Redis URL 含 auth 时密码写入日志 | 启动日志脱敏(`user:**@host`) |
|
||||
| F3d-01 | `main.py:116-118` | P2 | primary lock 丢失时续锁退出,后台任务继续双跑 | lock 丢失时 cancel 所有后台任务 |
|
||||
| F3d-02 | `main.py:344` | P2 | CORS origins 不去除空格 | `o.strip()` 过滤 |
|
||||
|
||||
## 审查结论
|
||||
|
||||
| 批次 | 覆盖 | 结论 |
|
||||
|------|------|------|
|
||||
| 3a | auth_jwt.py、webssh.py、crypto.py、agent.py、dependencies.py | 1 BUG 修复;安全核心均正确 |
|
||||
| 3b | heartbeat_flush、retry_runner、schedule_runner、self_monitor | 2 BUG 修复;整体逻辑正确 |
|
||||
| 3c | engine_compat.py、session.py、redis/client.py | 1 日志脱敏;新增文件无安全问题 |
|
||||
| 3d | main.py | 2 BUG 修复;中间件注册顺序正确 |
|
||||
@@ -0,0 +1,146 @@
|
||||
# Round 2 全面代码审计修复报告
|
||||
|
||||
**日期**: 2026-05-23
|
||||
**范围**: 全部8类别深度扫描 (功能性缺陷/安全漏洞/代码规范/安全风险/性能/可用性/可扩展性/优化)
|
||||
|
||||
## 修复汇总
|
||||
|
||||
| 严重度 | 数量 | 状态 |
|
||||
|--------|------|------|
|
||||
| CRITICAL | 3 | 全部修复 |
|
||||
| HIGH | 9 | 全部修复 |
|
||||
| MEDIUM | 15 | 全部修复 |
|
||||
| LOW | 10 | 评估后保留/已修复 |
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL 修复
|
||||
|
||||
### C-1: sync_service.py Shell注入回归 (CRITICAL)
|
||||
- **文件**: `server/application/services/sync_service.py`
|
||||
- **修复**: 对rsync命令所有动态参数添加 `shlex.quote()` — source_path, target_path, username, domain, key file path
|
||||
- **附带修复**: sshpass密码通过 `env={"SSHPASS": password}` 传递给子进程;`__import__("datetime")` 替换为正常import
|
||||
|
||||
### C-2: agent.py exec端点RCE无防护 (CRITICAL)
|
||||
- **文件**: `server/api/agent.py`
|
||||
- **修复**: 添加 `DANGEROUS_PATTERNS` 危险命令正则检测 + 403拦截;API Key比较改为 `secrets.compare_digest()`;`proc.kill()` 后加 `await proc.wait()` 防僵尸进程;server_id 类型验证
|
||||
|
||||
### C-3: WebSSH IDOR — token未绑定server_id (CRITICAL)
|
||||
- **文件**: `server/api/webssh.py`
|
||||
- **修复**: 验证JWT token中的server_id与路径参数匹配,不匹配返回4003关闭连接
|
||||
|
||||
---
|
||||
|
||||
## HIGH 修复
|
||||
|
||||
### H-1: PUT/POST端点setattr任意字段注入 (HIGH)
|
||||
- **文件**: `servers.py`, `settings.py`, `assets.py`, `scripts.py`
|
||||
- **修复**: 为每个模型定义字段白名单 (SERVER_CREATE_FIELDS, SCHEDULE_CREATE_FIELDS, PLATFORM_CREATE_FIELDS, SCRIPT_CREATE_FIELDS, CREDENTIAL_CREATE_FIELDS等);create端点用 `safe_data = {k:v for k,v in payload.items() if k in WHITELIST}` 过滤;update端点只set白名单内字段
|
||||
|
||||
### H-2: settings/assets/scripts端点无JWT认证 (HIGH)
|
||||
- **文件**: `settings.py`, `assets.py`, `scripts.py`
|
||||
- **修复**: 所有端点添加 `admin: Admin = Depends(get_current_admin)` 依赖
|
||||
|
||||
### H-3: refresh_token并发重放攻击 (HIGH)
|
||||
- **文件**: `server/application/services/auth_service.py`
|
||||
- **修复**: refresh时先清空旧token (invalidate-then-generate模式),并发请求第二个会因找不到token而失败
|
||||
|
||||
### H-4: servers.py N+1 Redis查询 (HIGH)
|
||||
- **文件**: `server/api/servers.py`
|
||||
- **修复**: 使用Redis pipeline批量获取所有heartbeat key,单次round-trip
|
||||
|
||||
### H-5: agent.py proc.kill()僵尸进程 (HIGH) — 与C-2合并修复
|
||||
|
||||
### H-6: agent.py heartbeat server_id类型验证 (MEDIUM→HIGH) — 与C-2合并修复
|
||||
|
||||
### H-7: settings API Key/Secret泄露 (HIGH)
|
||||
- **文件**: `server/api/settings.py`
|
||||
- **修复**: 定义 `SENSITIVE_SETTING_KEYS` 集合;list/get返回 `***MASKED***`;PUT禁止修改secret_key/encryption_key
|
||||
|
||||
### H-8: TOTP暴力破解无速率限制 (HIGH)
|
||||
- **文件**: `server/application/services/auth_service.py`
|
||||
- **修复**: 添加内存TOTP速率限制器,5次失败后锁定5分钟
|
||||
|
||||
### H-9: get_optional_admin独立session泄漏 (HIGH)
|
||||
- **文件**: `server/api/auth_jwt.py`
|
||||
- **修复**: 改用 `_get_request_session(request)` 获取request-scoped session
|
||||
|
||||
---
|
||||
|
||||
## MEDIUM 修复
|
||||
|
||||
### M-2: X-Forwarded-For IP伪造
|
||||
- **文件**: `server/api/auth.py`
|
||||
- **修复**: 优先使用X-Real-IP(Nginx设置);X-Forwarded-For取最后一个值(Nginx追加)
|
||||
|
||||
### M-3: asyncssh known_hosts=None — 添加ADR注释说明
|
||||
- **文件**: `server/infrastructure/ssh/asyncssh_pool.py`
|
||||
- **修复**: 添加ADR-012注释说明跳过原因(受控服务器环境)
|
||||
|
||||
### M-4: asyncssh异常信息泄露服务器地址
|
||||
- **文件**: `server/infrastructure/ssh/asyncssh_pool.py`
|
||||
- **修复**: ConnectionError只包含server_id,完整错误写入logger;exec_ssh_command返回通用错误消息
|
||||
|
||||
### M-5: 分页limit无上限
|
||||
- **文件**: `settings.py`, `assets.py`
|
||||
- **修复**: audit_logs limit上限500;ssh_sessions上限200;command_logs上限500;retry_jobs上限500
|
||||
|
||||
### M-9: API层直接import infrastructure层
|
||||
- **文件**: `servers.py`, `agent.py`
|
||||
- **修复**: 在 `dependencies.py` 添加 `get_redis_client()` 抽象函数,API层通过此函数获取Redis客户端
|
||||
|
||||
### M-10: WebSocket ConnectionManager单进程限制
|
||||
- **文件**: `server/api/websocket.py`
|
||||
- **修复**: 添加 `global_client_count()` 方法,通过Redis聚合多worker连接数
|
||||
|
||||
### M-11: servers.py Redis overlay逻辑重复
|
||||
- **文件**: `server/api/servers.py`
|
||||
- **修复**: 提取 `_overlay_redis_heartbeat()` 辅助函数,list_servers和get_server共用
|
||||
|
||||
### M-13: login_attempts表缺少查询索引
|
||||
- **文件**: `server/domain/models/__init__.py`
|
||||
- **修复**: 添加 `Index('idx_login_attempts_check', 'username', 'ip_address', 'success', 'attempted_at')`
|
||||
|
||||
### M-14: sync_service批量获取server逐个查询
|
||||
- **文件**: `server/domain/repositories/__init__.py`, `server/infrastructure/database/server_repo.py`, `server/application/services/sync_service.py`
|
||||
- **修复**: ServerRepository添加 `get_by_ids(ids)` 方法,使用 `IN` 查询一次获取所有server
|
||||
|
||||
### M-15: API错误消息中英文混用
|
||||
- **文件**: 所有API路由文件
|
||||
- **修复**: 统一为中文错误消息(服务器未找到、平台未找到、脚本未找到、必填等)
|
||||
|
||||
---
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
1. `server/api/servers.py` — setattr白名单+JWT auth+Redis pipeline+overlay去重+中文错误消息
|
||||
2. `server/api/settings.py` — setattr白名单+JWT auth+敏感设置掩码+分页上限+中文错误消息
|
||||
3. `server/api/assets.py` — setattr白名单+JWT auth+分页上限+中文错误消息
|
||||
4. `server/api/scripts.py` — setattr白名单+JWT auth+中文错误消息
|
||||
5. `server/api/agent.py` — 危险命令检测+compare_digest+僵尸进程+server_id验证+Redis抽象+中文错误消息
|
||||
6. `server/api/webssh.py` — IDOR server_id绑定
|
||||
7. `server/api/auth.py` — X-Real-IP优先+X-Forwarded-For取最后一个值
|
||||
8. `server/api/auth_jwt.py` — get_optional_admin使用request session
|
||||
9. `server/application/services/sync_service.py` — shlex.quote+sshpass env+__import__替换+批量get_by_ids
|
||||
10. `server/application/services/auth_service.py` — refresh_token防重放+TOTP速率限制
|
||||
11. `server/infrastructure/ssh/asyncssh_pool.py` — 错误信息脱敏+ADR注释
|
||||
12. `server/infrastructure/ssh/pool.py` — exec_command支持env参数
|
||||
13. `server/infrastructure/database/server_repo.py` — 添加get_by_ids批量查询
|
||||
14. `server/domain/repositories/__init__.py` — ServerRepository协议添加get_by_ids
|
||||
15. `server/domain/models/__init__.py` — LoginAttempt添加复合索引
|
||||
16. `server/api/dependencies.py` — 添加get_redis_client抽象
|
||||
17. `server/api/websocket.py` — 添加global_client_count多worker支持
|
||||
18. `server/application/services/sync_engine_v2.py` — sysctl命令注入防护+SFTP错误脱敏
|
||||
19. `server/application/services/script_service.py` — SSRF私有IP拦截+str(e)脱敏+MYSQL_PWD环境变量
|
||||
20. `server/api/health.py` — 未认证端点拆分为/health(公开)+/health/detail(需JWT)
|
||||
21. `server/infrastructure/database/push_schedule_repo.py` — **kwargs setattr改为字段白名单
|
||||
22. `server/infrastructure/database/sync_log_repo.py` — **kwargs setattr改为字段白名单
|
||||
23. `server/api/agent.py` — Exception str(e)脱敏
|
||||
24. `server/background/self_monitor.py` — Telegram消息str(e)脱敏
|
||||
25. `server/api/webssh.py` — SSH连接/Shell创建错误str(e)脱敏+关闭原因不暴露server_id
|
||||
26. `server/application/services/sync_service.py` — error_message脱敏+stderr不写入DB
|
||||
27. `server/infrastructure/database/ssh_session_repo.py` — __import__替换为正常import
|
||||
28. `server/api/sync_v2.py` — 错误消息统一中文
|
||||
29. `server/api/servers.py` — _server_to_dict移除username字段(防SSH凭据泄露)
|
||||
30. `server/infrastructure/ssh/pool.py` — ValueError不暴露host地址
|
||||
31. `server/infrastructure/database/server_repo.py` — import语句移至文件顶部
|
||||
@@ -0,0 +1,39 @@
|
||||
# 2026-05-23 — 脚本/健康检查改 SSH,弱化 8601
|
||||
|
||||
## 摘要
|
||||
|
||||
远程脚本执行与手动健康检查改为经 **SSH(22)**;Agent 仅负责 **HTTPS 出站心跳(443)**。子机 Agent HTTP 默认只监听 `127.0.0.1:8601`,**不要求**公网放行 8601。
|
||||
|
||||
## 动机
|
||||
|
||||
大量宝塔子机未开放 8601;推送与文件已走 SSH。统一命令通道为 SSH,监控仍靠 Agent 心跳,减少防火墙与双通道维护成本。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/application/services/script_service.py` — `_call_ssh_exec`,移除 Agent HTTP 执行路径
|
||||
- `server/application/services/server_service.py` — `check_all_servers` 改 SSH 探测
|
||||
- `server/infrastructure/ssh/remote_probe.py` — 新增 SSH 健康/指标探测
|
||||
- `server/api/servers.py` — 健康检查 API 文档说明
|
||||
- `web/agent/install.sh` — Python 3.12 venv、127.0.0.1 绑定、不再自动放行公网 8601
|
||||
- `web/app/servers.html` — 安装说明文案
|
||||
- `server/config.py` — 注释
|
||||
|
||||
## 端口模型(变更后)
|
||||
|
||||
| 方向 | 端口 | 用途 |
|
||||
|------|------|------|
|
||||
| 子机 → 中心 | 443 出站 | Agent 心跳、长任务 curl 回调 |
|
||||
| 中心 → 子机 | 22 入站 | 推送、文件、脚本、手动健康检查 |
|
||||
| 127.0.0.1:8601 | 仅本机 | Agent 进程(可选,不暴露公网) |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 需重启 Nexus 后端以加载 Python 变更
|
||||
- 已装 Agent:建议重装或手动改 systemd 为 `127.0.0.1` + venv;**不必**再开公网 8601
|
||||
- 确保各服务器 SSH 凭据可用(与推送相同)
|
||||
|
||||
## 验证
|
||||
|
||||
1. 子机 Agent 运行:`journalctl -u nexus-agent -f`,心跳 200
|
||||
2. 未开公网 8601 时:脚本页执行命令应成功(`channel: ssh`)
|
||||
3. `POST /api/servers/check` 返回 `channel: ssh` 且 `status: online`(SSH 可达时)
|
||||
@@ -0,0 +1,28 @@
|
||||
# 2026-05-23 — 全量标准转接包
|
||||
|
||||
## 摘要
|
||||
|
||||
新增 `docs/project/standards-transfer-package.md`,将 Cursor 规则、`standards/` 三件套、逐行审查八步 DoD、设计/上线/审计资产全部编入接续体系;要求下一任 AI 与团队**同等严谨**,逐行审查与开发规范同等强制。
|
||||
|
||||
## 动机
|
||||
|
||||
用户要求切换 AI 时不仅看功能清单,还须完整转接所有开发与审计标准,逐行审查不得降级。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/project/standards-transfer-package.md`(新建)
|
||||
- `docs/project/AI-HANDOFF-2026-05-23.md`(§0 提示词、§5 标准转接)
|
||||
- `docs/project/development-acceptance-standard.md`(L3 扩展)
|
||||
- `standards/README.md`
|
||||
- `docs/project/nexus-full-site-features.md` §25
|
||||
- `docs/Nexus-功能说明书.html`
|
||||
- `docs/project/status.md`、`PROJECT_HANDOVER.md`
|
||||
- `.cursor/rules/audit-line-review.mdc`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
新会话粘贴 HANDOFF §0 或 standards-transfer-package §8 提示词,AI 应列出 13 项强制标准 + 逐行 DoD 六项。
|
||||
@@ -0,0 +1,15 @@
|
||||
# WSL 代码验证指南与脚本
|
||||
|
||||
**日期**: 2026-05-23
|
||||
|
||||
## 范围
|
||||
|
||||
仅验证代码:venv、启动、15 项单测、`/health`、`/api/servers/` 401。不审 key、不做 CRUD/浏览器/Agent E2E。
|
||||
|
||||
## 用法
|
||||
|
||||
```bash
|
||||
bash scripts/wsl_ensure_venv.sh
|
||||
bash scripts/wsl_start_dev.sh
|
||||
bash scripts/wsl_integration_smoke.sh
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
# 2026-05-24 Agent API Key 验证修复
|
||||
|
||||
**日期**: 2026-05-24
|
||||
**动机**: Agent 4 个端点(/exec、/exec/kill、/exec/wait、/config/reload)的 API Key 认证完全失效,属于 P0 安全 Bug
|
||||
**变更摘要**:
|
||||
- `verify_api_key()` 从手动调用改为 FastAPI `Depends()` 依赖注入
|
||||
- 修复调用时 `x_api_key` 被误传给 `request` 参数导致认证永远失败的问题
|
||||
- 将 `verify_api_key` 改为 `async` 以符合 FastAPI 依赖规范
|
||||
|
||||
**涉及文件**: `web/agent/agent.py`
|
||||
|
||||
**是否需迁移**: 否
|
||||
|
||||
**验证方式**: 向 Agent 发送带错误 `X-API-Key` 的请求应返回 401;不带 key 返回 401(422 若 Header 缺失);正确 key 正常通过
|
||||
@@ -0,0 +1,20 @@
|
||||
# 2026-05-24 Agent 升级依赖检测 + 自动安装 + 回滚 + 版本锁定
|
||||
|
||||
**日期**: 2026-05-24
|
||||
**动机**: upgrade-agent 只下载 agent.py 不检查/更新依赖,新版引入新 pip 包会导致服务启动失败;install.sh 依赖不锁版本,子机可能装到与中心不一致的版本
|
||||
**变更摘要**:
|
||||
- upgrade-agent 增加 Python 版本预检:SSH 检测 venv Python >= 3.10,不满足返回 424 + 提示手动修复
|
||||
- Python 版本 OK 后自动 `pip install` 更新依赖(锁版本,与中心 requirements.txt 对齐)
|
||||
- 升级前备份旧 agent.py.bak,失败自动回滚
|
||||
- install.sh 依赖版本锁定:fastapi==0.115.6 uvicorn==0.34.0 httpx==0.28.1 python-multipart==0.0.19
|
||||
- 升级 SSH 超时从 60s 调至 120s(pip install 首次可能较慢)
|
||||
|
||||
**涉及文件**: `server/api/servers.py`, `web/agent/install.sh`
|
||||
|
||||
**是否需迁移**: 否
|
||||
|
||||
**验证方式**:
|
||||
- Python < 3.10 时返回 424 + 提示手动修复
|
||||
- Python OK + 依赖完整时 pip install 跳过已装包,升级正常
|
||||
- Python OK + 依赖缺失时 pip install 自动补装
|
||||
- 升级失败时旧 agent.py 自动恢复
|
||||
@@ -0,0 +1,75 @@
|
||||
# 2026-05-24 批量审查修复 — 27项全部处理
|
||||
|
||||
**日期**: 2026-05-24
|
||||
**动机**: 全面扫描项目,发现未实现/不完整/死代码问题 27 项,逐项沟通后全部处理
|
||||
**变更摘要**:
|
||||
|
||||
### Bug 修复
|
||||
- B-01: Agent API Key 验证完全失效 — `verify_api_key()` 参数传错,改为 `Depends()` 模式
|
||||
- B-02: upgrade-agent 不检查/更新依赖 — 增加 Python 版本预检 + 自动 pip install(锁版本)+ 备份回滚
|
||||
- B-03: POST /api/servers/push 死端点 — 删除端点 + ServerPush schema + ServerService.push_to_servers()
|
||||
|
||||
### 功能补全
|
||||
- U-01: POST /api/servers/check 无前端入口 — servers.html 批量操作栏加「健康检查」按钮
|
||||
- U-03: POST /api/sync/commands 无前端入口且与 scripts/exec 冗余 — 删除端点 + SyncCommands schema
|
||||
- U-04/U-05: POST /api/sync/config + config/rollback — 删除端点(sysctl 可通过脚本执行实现)
|
||||
- U-06: POST /api/sync/sftp 无前端入口 — 删除端点
|
||||
- C-03: Schedule script 类型缺少必填校验 — Pydantic model_validator 校验 script_id/script_content + push 模式 source_path
|
||||
- 定时任务支持一次性/循环模式 — Model 加 run_mode + fire_at,前端加切换 UI,执行后自动禁用
|
||||
|
||||
### 前端修复
|
||||
- FE-01: alerts.html getTopType() 硬编码"见详情" — API 加 top_types 查询,前端显示实际类型
|
||||
- FE-02: assets.html SSH 会话只显示 "Server #ID" — 加载服务器名称映射
|
||||
- FE-03: assets.html editNode sort_order 硬编码 0 — 回填原始值
|
||||
- FE-04: layout.js 搜索链接不定位到具体记录 — 加 ?highlight=id 参数,各页面自动定位
|
||||
|
||||
### 死代码清理
|
||||
- S-01: FileDeploy schema 零引用 — 删除
|
||||
- S-02: PaginatedResponse schema 零引用 — 删除
|
||||
- D-02: ServerService.list_servers() 零调用者 — 删除
|
||||
- D-03: ScriptService.test_credential_connection() 零调用者 — 删除
|
||||
- Engine: sync_commands()、sync_config()、_rollback_config()、sftp_transfer() 随端点删除
|
||||
|
||||
### 安全/质量
|
||||
- CFG-03: install.py 两处 silent pass — 改为 logger.warning
|
||||
|
||||
### 深度扫描第三轮 Bug 修复
|
||||
- BUG-1 [HIGH]: servers.py 路由遮蔽 — `GET /logs` 声明在 `GET /{id}` 之后,导致 /logs 被 /{id} 拦截(3个前端页面同步日志查询失败)。修复:将 `/logs` 移到 `/{id}` 之前
|
||||
- BUG-2 [CRITICAL]: terminal.html WebSSH 完全不可用 — 1) token URL 缺少 API 前缀 → 404;2) apiFetch() 返回 Response 未调 .json() → TypeError;3) 缺少 Content-Type header。三处全部修复
|
||||
- BUG-3 [MEDIUM]: schedule_runner.py `_field_match()` 提前返回 — 逗号分隔 cron 表达式(如 `*/5,1`)遇到 `*/5` 不匹配时直接 return False,不再检查后续 `1`。修复:step 不匹配时 continue 而非 return
|
||||
|
||||
### 深度扫描第四轮 Bug 修复
|
||||
- BUG-4 [CRITICAL]: scripts.html `startExecPoll()` 函数名错误 → `startExecPolling()`,长任务快速执行时 ReferenceError 崩溃
|
||||
- BUG-5 [HIGH]: agent.py `{value!r!s:.50}` 格式串语法非法,agent 发非数值时 ValueError — 修复为 `{value!r:.50}`
|
||||
- BUG-6 [HIGH]: alerts.html `d.daily.reduce()` 和 `d.top_servers[0]` 无 null 检查,API 返回空数据时 TypeError — 加 `||[]` 保护
|
||||
- BUG-7 [MEDIUM]: scripts.html `doExec()` 缺少 `r.ok` 检查,服务端错误被静默吞掉 — 加错误处理
|
||||
- BUG-8 [MEDIUM]: commands.html `s.id.substring(0,8)` 无 null 检查 — 改为 `(s.id||'').substring(0,8)`
|
||||
- BUG-9 [MEDIUM]: `_server_to_dict()` 缺少 `last_checked_at` 和 `ssh_key_public` 字段 — 补充返回
|
||||
- BUG-10 [MEDIUM]: 限速脚本 EXPIRE 每次请求重置窗口,精确间隔可绕过 — 改用 Lua 脚本原子 INCR+条件EXPIRE
|
||||
- N-06 [HIGH]: install.php 的 ssh_key 列 VARCHAR(500) 存不下加密密钥 — 迁移加 MODIFY COLUMN → TEXT
|
||||
- N-07 [HIGH]: servers 表新列缺迁移 (platform_id/node_id/protocols/extra_attrs/connectivity/last_checked_at/ssh_key_configured) — 迁移补全
|
||||
- 清理: agent.py 冗余内联 import、servers.py/scripts.py 未使用 List 导入
|
||||
|
||||
**涉及文件**: web/app/scripts.html, web/app/alerts.html, web/app/commands.html, server/api/agent.py, server/api/servers.py, server/api/scripts.py, server/infrastructure/redis/script_callback_rate.py, server/infrastructure/database/migrations.py
|
||||
|
||||
### 深度扫描第五轮 Bug + 安全修复
|
||||
- BUG-11 [HIGH]: self_monitor.py Telegram 告警无冷却,Redis/MySQL 宕机时每30秒刷屏 — 加5分钟冷却 + 恢复时清除冷却
|
||||
- BUG-12 [HIGH]: schedule_runner.py 一次性调度执行超60秒会重复触发 — 执行前先设 last_run_at + disable
|
||||
- BUG-13 [MEDIUM]: websocket.py 僵尸连接只从字典移除,TCP 未关 — disconnect() 中主动 close()
|
||||
- BUG-14 [MEDIUM]: websocket.py `_last_alert_time` 字典无限增长 — 加1小时过期清理
|
||||
- BUG-15 [HIGH]: asyncssh_pool.py 全忙时 pool 超过 MAX_CONNECTIONS 无限增长 — _evict_one 返回 bool + 全忙等待/拒绝
|
||||
- SEC-1 [HIGH]: auth_jwt.py + main.py `INSTALL_MODE` 是导入时常量,安装后 JWT 认证仍禁用 — 改为动态函数 `_is_install_mode()`/`is_install_mode()`
|
||||
- SEC-2 [MEDIUM]: install.py `/lock` 端点无保护,可在管理员创建前调用阻止安装 — 加管理员存在性验证
|
||||
|
||||
**涉及文件**: server/background/self_monitor.py, server/background/schedule_runner.py, server/api/websocket.py, server/infrastructure/ssh/asyncssh_pool.py, server/api/auth_jwt.py, server/main.py, server/api/install.py
|
||||
|
||||
**是否需迁移**: 是 — push_schedules 表新增 run_mode + fire_at 列,servers 表补全新列 + ssh_key 列类型修复(自动迁移)
|
||||
|
||||
**验证方式**:
|
||||
- 一次性调度:创建后到 fire_at 时间自动触发并禁用
|
||||
- 循环调度:cron 表达式匹配时触发,不自动禁用
|
||||
- 健康检查:选服务器 → 点击按钮 → 显示结果
|
||||
- 全局搜索:搜索结果点击跳转到对应页面并展开记录
|
||||
- WebSSH:点击服务器终端 → 正常连接
|
||||
- 路由遮蔽:服务器页面同步日志正常加载
|
||||
- Cron 表达式:`*/5,1` 在分钟=1 时正确匹配
|
||||
@@ -0,0 +1,41 @@
|
||||
# 2026-05-25 — 服务器表单 UX 重构:预设下拉菜单 + SSH 密钥输入
|
||||
|
||||
## 变更摘要
|
||||
|
||||
重构服务器添加/编辑 Modal 的认证区域:
|
||||
1. 密码预设从🔑弹窗 + prompt()二次验证 → `<select>` 下拉菜单,选预设后密码框自动隐藏,后端自动解析
|
||||
2. SSH 密钥模式新增「私钥内容」textarea,支持直接粘贴 PEM 格式私钥
|
||||
3. Server 模型新增 `preset_id` 字段,关联密码预设
|
||||
|
||||
## 动机
|
||||
|
||||
- 旧方案:点击🔑按钮 → 浮层弹窗 → prompt() 输入登录密码 → 解密预设 → 填入密码框。流程冗长,且 prompt() 不安全(明文密码出现在 JS 变量中)
|
||||
- 新方案:直接选预设下拉 → 密码框自动隐藏 → 保存时后端自动解密预设密码并加密存储。零前端密码暴露
|
||||
- 密钥模式之前只有「密钥路径」,无法粘贴私钥内容,功能缺失
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `server/domain/models/__init__.py` | Server 新增 `preset_id` 列 |
|
||||
| `server/api/schemas.py` | ServerCreate/ServerUpdate 新增 `preset_id` 字段 |
|
||||
| `server/api/servers.py` | create_server/update_server 自动解析预设密码;`ssh_key_private` 时自动设 `ssh_key_configured=True`;`_server_to_dict` 输出 `preset_id` |
|
||||
| `web/app/servers.html` | Modal 重构:密码区=预设下拉+密码输入框(联动);密钥区=路径+私钥textarea;删除旧 preset 弹窗代码 |
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: `ALTER TABLE servers ADD COLUMN preset_id INT NULL COMMENT '关联的密码预设ID(SSH认证)' AFTER agent_api_key;`
|
||||
- **需重启**: 是(Python 后端模型+路由变更)
|
||||
|
||||
## 安全考量
|
||||
|
||||
- 前端不再接触预设密码明文 → 减少攻击面
|
||||
- `preset_id` 仅在后端解析,前端只传 ID → 无 XSS 泄露风险
|
||||
- 预设不存在时静默忽略,不报错(用户仍可手动输入密码)
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开添加服务器 → 密码模式:预设下拉可见,选预设后密码框隐藏
|
||||
2. 切换到密钥模式:密钥路径+私钥内容 textarea 可见,密码区隐藏
|
||||
3. API 测试 `POST /servers/` 传 `preset_id=2` → 返回 `password_set:true, preset_id:2`
|
||||
4. API 测试 `POST /servers/` 传 `ssh_key_private` → 返回 `ssh_key_configured:true, ssh_key_private_set:true`
|
||||
@@ -0,0 +1,50 @@
|
||||
# 2026-05-25 — SSH 密钥预设系统:下拉菜单 + 后端自动解析
|
||||
|
||||
## 变更摘要
|
||||
|
||||
新增 SSH 密钥预设功能,与密码预设采用完全一致的模式:
|
||||
1. 新增 `ssh_key_presets` 表 — 存储加密私钥 + 公钥
|
||||
2. 新增 `/api/ssh-key-presets/` CRUD + reveal API
|
||||
3. Server 模型新增 `ssh_key_preset_id` 字段,关联密钥预设
|
||||
4. 服务器表单密钥模式新增「密钥预设」下拉菜单,选预设后路径/私钥输入框自动隐藏
|
||||
5. 后端自动解析 `ssh_key_preset_id` → 解密预设私钥 → 加密存储到 `ssh_key_private`
|
||||
|
||||
## 动机
|
||||
|
||||
- 密码预设已有下拉菜单 + 后端自动解析(Task #21),密钥模式应保持一致的 UX
|
||||
- 旧方案:密钥模式只能手动输入路径或粘贴私钥,无法复用已存储的密钥
|
||||
- 新方案:选密钥预设下拉 → 路径和私钥输入框自动隐藏 → 保存时后端自动解密预设私钥并加密存储
|
||||
- 前端零密钥暴露,与密码预设安全模型一致
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `server/domain/models/__init__.py` | 新增 `SshKeyPreset` 模型;Server 新增 `ssh_key_preset_id` 列 |
|
||||
| `server/api/schemas.py` | 新增 `SshKeyPresetCreate`/`SshKeyPresetUpdate`;ServerCreate/ServerUpdate 新增 `ssh_key_preset_id` |
|
||||
| `server/infrastructure/database/ssh_key_preset_repo.py` | 新增密钥预设 Repository |
|
||||
| `server/api/settings.py` | 新增 `ssh_key_preset_router` — CRUD + reveal + 审计日志 |
|
||||
| `server/api/servers.py` | create_server/update_server 自动解析 `ssh_key_preset_id`;`_server_to_dict` 输出 `ssh_key_preset_id`;`ALLOWED_FIELDS` 加入 `ssh_key_preset_id` |
|
||||
| `server/main.py` | 注册 `ssh_key_preset_router` |
|
||||
| `web/app/servers.html` | 密钥区=预设下拉+路径输入+私钥textarea(联动);`loadSshKeyPresetOptions()`;`saveServer()` 发送 `ssh_key_preset_id` |
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**:
|
||||
- `CREATE TABLE ssh_key_presets (...)`
|
||||
- `ALTER TABLE servers ADD COLUMN ssh_key_preset_id INT NULL COMMENT '关联的密钥预设ID(SSH认证)' AFTER preset_id;`
|
||||
- **需重启**: 是(Python 后端模型+路由变更)
|
||||
|
||||
## 安全考量
|
||||
|
||||
- 前端不接触预设私钥明文 → 与密码预设安全模型一致
|
||||
- `ssh_key_preset_id` 仅在后端解析,前端只传 ID → 无 XSS 泄露风险
|
||||
- 预设不存在时静默忽略,不报错(用户仍可手动输入密钥路径/私钥)
|
||||
- reveal 端点需二次验证 + 审计日志
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. `POST /api/ssh-key-presets/` 传 `name+private_key+public_key` → 返回 `id, name`
|
||||
2. `GET /api/ssh-key-presets/` → 列出预设(不含私钥)
|
||||
3. `POST /api/servers/` 传 `auth_method=key, ssh_key_preset_id=1` → 返回 `ssh_key_private_set:true, ssh_key_preset_id:1, ssh_key_configured:true`
|
||||
4. 浏览器打开添加服务器 → 切换到密钥模式:密钥预设下拉可见,选预设后路径和私钥输入框隐藏
|
||||
@@ -0,0 +1,65 @@
|
||||
# 2026-05-26 — 三项前端缺失功能补全
|
||||
|
||||
## 变更摘要
|
||||
|
||||
补全三项后端已有但前端缺失的重要管理功能:
|
||||
|
||||
1. **服务器批量导入(CSV)** — 新增 `/api/servers/import` + `/api/servers/import/template`,前端 servers.html 新增「↓ 导入」按钮和导入弹窗
|
||||
2. **文件上传** — 新增 `/api/sync/upload`(SFTP),前端 files.html 新增「↑ 上传」按钮 + 拖拽上传
|
||||
3. **批量 Agent 安装/升级** — 新增 `/api/servers/batch/install-agent` + `/batch/upgrade-agent`,前端 servers.html 批量操作栏新增按钮
|
||||
|
||||
## 动机
|
||||
|
||||
- 服务器批量导入:管理几十台上百台服务器时,逐台添加效率极低
|
||||
- 文件上传:files.html 只能浏览/删除/重命名,无法从浏览器上传文件到远程服务器
|
||||
- 批量 Agent 操作:只能从服务器详情逐台安装/升级 Agent,无法一键批量执行
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `server/api/schemas.py` | 新增 `ServerImportResult`、`BatchAgentAction`、`BatchAgentResult`、`BatchAgentResultItem`、`FileUpload` schema |
|
||||
| `server/api/servers.py` | 新增 `import_servers()`、`download_import_template()`、`batch_install_agent()`、`batch_upgrade_agent()`;CSV 注入防护 `_sanitize_csv_cell()` |
|
||||
| `server/api/sync_v2.py` | 新增 `upload_file()` 端点(asyncssh SFTP 上传,路径遍历防护,文件大小限制 100MB) |
|
||||
| `web/app/servers.html` | 新增「↓ 导入」按钮 + CSV 导入弹窗 + 批量 Agent 按钮 + 结果弹窗 |
|
||||
| `web/app/files.html` | 新增「↑ 上传」按钮 + 拖拽上传 + 上传逻辑 |
|
||||
|
||||
## 安全考量
|
||||
|
||||
- CSV 注入:`_sanitize_csv_cell()` 过滤 `= + - @ \t \r` 开头的单元格,防止 Excel 公式注入
|
||||
- 路径遍历:上传端点检查 `..`,文件名替换 `/` 和 `\` 为 `_`
|
||||
- 文件大小:CSV ≤ 1MB/500 行,上传文件 ≤ 100MB
|
||||
- 命令注入:复用 `shlex.quote()` 模式(agent install/upgrade 已有)
|
||||
- 审计日志:所有 CUD 操作记录审计
|
||||
- 敏感数据:导入结果不返回密码/私钥明文
|
||||
- SFTP 安全:复用 asyncssh_pool 的凭据加密逻辑
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无(无新表/新列)
|
||||
- **需重启**: 是(Python 后端路由变更)
|
||||
|
||||
## 部署后修复
|
||||
|
||||
- **路由冲突**: `/batch/install-agent`、`/batch/upgrade-agent`、`/import`、`/import/template` 注册在 `/{id}` 之后,FastAPI 将 `batch`/`import` 误匹配为 `{id}` 参数。修复:将固定路径路由移到 `/{id}` 之前。
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. CSV 导入:下载模板 → 填写 2-3 行 → 上传 → 验证服务器列表新增
|
||||
2. 文件上传:选择服务器 → 拖拽文件到文件列表区 → 验证远程文件存在
|
||||
3. 批量 Agent:选中多台服务器 → 点击「安装 Agent」→ 验证逐台结果
|
||||
|
||||
## 浏览器验证结果 (2026-05-26)
|
||||
|
||||
| 功能 | 验证项 | 结果 |
|
||||
|------|--------|------|
|
||||
| CSV 导入 | 「↓ 导入」按钮 + 弹窗 | ✅ |
|
||||
| CSV 导入 | 模板下载 API (200, text/csv) | ✅ |
|
||||
| CSV 导入 | 导入 2 台服务器 (created=2) | ✅ |
|
||||
| CSV 导入 | 重复地址跳过 (skipped=1) | ✅ |
|
||||
| 文件上传 | 「↑ 上传」按钮 | ✅ |
|
||||
| 文件上传 | 上传 API 端点响应正常 | ✅ |
|
||||
| 批量 Agent | 选中后显示「安装 Agent」「升级 Agent」 | ✅ |
|
||||
| 批量 Agent | batch/install-agent API 正确响应 | ✅ |
|
||||
| 批量 Agent | batch/upgrade-agent API 正确响应 | ✅ |
|
||||
| 路由修复 | 静态路径不再被 /{id} 捕获 | ✅ |
|
||||
@@ -0,0 +1,53 @@
|
||||
# 2026-05-26 — 门控 v2:3道→7道 + 防绕过机制
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. **门控脚本升级 v2** — `deploy/pre_deploy_check.sh` 从 3 道门扩展到 7 道:
|
||||
- Gate 1 Changelog门:文件存在 + **行数≥10**(防空壳)
|
||||
- Gate 2 Audit门:文件存在 + **必须含 Step3+Closure+DoD**(防空壳)
|
||||
- Gate 3 Test门:test_api.py **必须存在** + 通过(防删测试绕过)
|
||||
- Gate 4 Lint门:`ruff check server/ --select F`(未定义名称、未使用导入)
|
||||
- Gate 5 Import门:`import server.main` 成功(语法/导入错误,venv感知)
|
||||
- Gate 6 Security门:`bandit` 无 HIGH(安全反模式)
|
||||
- Gate 7 Review门:审计文件必须包含实际改动文件清单(交叉验证 git diff)
|
||||
2. **ruff.toml** — Lint 配置,只拦 F(pyflakes) 真正的BUG,不强制风格升级
|
||||
3. **requirements-dev.txt** — 开发依赖(ruff, bandit, pytest)
|
||||
4. **deploy/gate_log.py** — 门控执行日志记录器(JSONL格式,每次检查自动追加)
|
||||
5. **MCP gate_log 工具** — `mcp/Nexus_server.py` 新增 T9 gate_log() 工具
|
||||
6. **CLAUDE.md 更新** — 门控表从3道→7道,加入用户审查清单
|
||||
|
||||
## 动机
|
||||
|
||||
v1 门控有3个致命漏洞:
|
||||
- 空文件就能通过 Changelog/Audit 门
|
||||
- 删掉 test_api.py 就能绕过 Test 门
|
||||
- 零代码质量检查,51个 `except Exception: pass` 无人管
|
||||
|
||||
v2 堵住了所有绕过路径,并新增 Lint/Import/Security/Review 4道门。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `deploy/pre_deploy_check.sh` | 重写 — 3门→7门,防绕过 |
|
||||
| `ruff.toml` | 新建 — Lint配置 |
|
||||
| `requirements-dev.txt` | 新建 — 开发依赖 |
|
||||
| `deploy/gate_log.py` | 新建 — 门控日志 |
|
||||
| `mcp/Nexus_server.py` | 新增 T9 gate_log 工具 |
|
||||
| `CLAUDE.md` | 门控表+审查清单 |
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无
|
||||
- **需重启**: MCP server 需重启(改动 Nexus_server.py),下次会话自动生效
|
||||
- **远程部署**: 需安装 ruff + bandit(已安装),需同步所有新文件(已完成)
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. `bash -n deploy/pre_deploy_check.sh` — Shell 语法通过 ✅
|
||||
2. `python -c "import py_compile; py_compile.compile('mcp/Nexus_server.py', doraise=True)"` — Python 语法通过 ✅
|
||||
3. 远程执行 7-gate check → 6/7 PASS + 1 BLOCK(Test门凭据问题) ✅
|
||||
4. Lint门: ruff F规则零违规 ✅
|
||||
5. Security门: bandit 无 HIGH ✅
|
||||
6. Import门: venv python3 import server.main 成功 ✅
|
||||
7. Review门: 审计文件覆盖所有改动文件 ✅
|
||||
@@ -0,0 +1,43 @@
|
||||
# Gate3 误判修复 + test_api.py 生产适配
|
||||
|
||||
**日期**: 2026-05-26
|
||||
**变更摘要**: 修复 Gate3 测试门控误判 BLOCK 问题;完善 test_api.py 适配生产环境
|
||||
|
||||
## 动机
|
||||
|
||||
1. Gate3 使用 `grep -qiE "FAIL|ERROR|failed|error"` 匹配测试输出,但测试摘要行 "0/25 failed" 也会被匹配,导致全部通过时仍被 BLOCK
|
||||
2. test_api.py 在生产环境登录失败(默认密码 admin 不对),需从 .env 加载测试凭据
|
||||
3. test_api.py 使用错误的状态码期望(POST 创建期望 200 应为 201,DELETE 期望 200 应为 204)
|
||||
|
||||
## 变更内容
|
||||
|
||||
### deploy/pre_deploy_check.sh — Gate3 修复
|
||||
- 旧逻辑: `grep -qiE "FAIL|ERROR|failed|error"` — 匹配摘要行 "0/25 failed" 导致误判
|
||||
- 新逻辑: 精确计数 `[FAIL]` 标记 + 检查 "N test(s) failed" 模式
|
||||
- 修复 sed 替换丢失 `\s` 转义的问题(`^s+` → `^\s+\[FAIL\]`)
|
||||
|
||||
### tests/test_api.py — 生产适配
|
||||
- 新增 `_load_credentials_from_env_file()` 从 .env 加载 NEXUS_TEST_ADMIN_USER / NEXUS_TEST_ADMIN_PASSWORD
|
||||
- POST 创建操作 expect_code 从 200 改为 201(REST 标准)
|
||||
- DELETE 操作 expect_code 从 200 改为 204(REST 标准)
|
||||
- 修复 server_id 回退逻辑:`else 1` → `else None`(避免操作不存在的服务器)
|
||||
- 新增测试前清理残留的 test-server-e2e(防止重复创建报错)
|
||||
- 新增 204 No Content 空响应体处理(`json.loads("")` 会崩溃)
|
||||
- Schedules 创建改用 `run_mode: "cron"` + `cron_expr`(避免 once 模式需要 fire_at)
|
||||
- Schedules 的 `server_ids` 从数组改为字符串 `"all"`
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `deploy/pre_deploy_check.sh` — Gate3 检测逻辑修复
|
||||
- `tests/test_api.py` — 生产适配 + REST 状态码修复
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 无需重启后端(test_api.py 是独立脚本)
|
||||
- 需部署 pre_deploy_check.sh 到远程服务器
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 远程运行 `bash deploy/pre_deploy_check.sh` → Gate3 PASS(之前 BLOCK)
|
||||
2. 远程运行 `python3 tests/test_api.py` → 25/25 passed
|
||||
3. 全部 7/7 门控通过
|
||||
@@ -0,0 +1,70 @@
|
||||
# 知识图谱重组 + MegaMemory 嵌入模型部署
|
||||
|
||||
**日期**: 2026-05-26
|
||||
**变更摘要**: 重组两个知识库,去重规则知识,补全项目架构知识;部署 Xenova 嵌入模型使 MegaMemory 语义搜索可用
|
||||
|
||||
## 动机
|
||||
|
||||
1. `完美实现原则`(全局CLAUDE.md)、`Nexus实现原则`、`Nexus强制标准纪律` 三者 80% 内容重叠,知识图谱里再存一遍纯属冗余——规则已在 CLAUDE.md 每次session自动加载
|
||||
2. 知识图谱完全缺少项目架构知识(模块、技术栈、数据流、部署拓扑),无法通过语义搜索快速定位模块
|
||||
3. MegaMemory 的 `understand` 语义搜索一直报 "fetch failed"——嵌入模型未下载
|
||||
|
||||
## 变更内容
|
||||
|
||||
### server-memory(全局跨项目)
|
||||
- **删除 9 个重复规则实体**: 完美实现原则、Nexus实现原则、Nexus强制标准纪律、ThreeIronRules、FourDimensionChecklist、ProblemReportFormat、AuditEnforcementMechanism、MandatoryFileModificationFlow、LineWalkAudit8Steps
|
||||
- **保留 2 个非规则实体**: uzuma(用户偏好)、Nexus-Production-Server(服务器信息)
|
||||
|
||||
### MegaMemory(项目级架构知识)
|
||||
- **创建 19 个概念节点**: 项目顶层 + 9个架构模块 + 4个基础设施子模块 + 3个认证子模块 + 2个独立配置
|
||||
- **创建 30 条关系边**: 项目→模块(connects_to/depends_on)、模块→子模块(implements)、数据流→基础设施(connects_to/depends_on)
|
||||
- **生成 384 维嵌入向量**: 所有节点通过 Xenova/all-MiniLM-L6-v2 本地模型生成嵌入
|
||||
|
||||
### 嵌入模型部署
|
||||
- 从 hf-mirror.com 下载 Xenova/all-MiniLM-L6-v2 量化 ONNX 模型(23MB)
|
||||
- 先通过远程 Linux 服务器下载,再 SCP 传回本地
|
||||
- 模型放置到 `node_modules/@xenova/transformers/models/` 本地路径
|
||||
|
||||
## 知识图谱结构
|
||||
|
||||
```
|
||||
nexus-project (feature)
|
||||
├── nexus-backend (module) — FastAPI Clean Architecture 4层
|
||||
├── nexus-frontend (module) — Tailwind+Alpine.js 12页面
|
||||
├── nexus-infra (module) — SSH池+Redis+Telegram+WebSocket
|
||||
│ ├── nexus-ssh-pool (component)
|
||||
│ ├── nexus-redis (component)
|
||||
│ ├── nexus-telegram (component)
|
||||
│ └── nexus-websocket (component)
|
||||
├── nexus-auth (feature) — JWT+TOTP+Agent API Key
|
||||
│ ├── nexus-jwt (component)
|
||||
│ ├── nexus-totp (component)
|
||||
│ └── nexus-agent-auth (component)
|
||||
├── nexus-dataflow (pattern) — 心跳→Redis→MySQL+WS+TG
|
||||
├── nexus-gates (config) — 7门控部署
|
||||
├── nexus-sync (feature) — 4种同步模式
|
||||
├── nexus-database (module) — 14表+加密+脱敏
|
||||
└── nexus-deploy (config) — 3层守护+SCP部署
|
||||
|
||||
nexus-production (config) — 服务器连接信息
|
||||
uzuma (config) — 用户偏好
|
||||
```
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `.megamemory/knowledge.db` — MegaMemory SQLite 数据库(19节点+30边+19嵌入)
|
||||
- `.megamemory/populate.py` — 数据导入脚本(一次性使用)
|
||||
- `~/.cache/xenova/transformers/` — Xenova 模型缓存
|
||||
- `node_modules/@xenova/transformers/models/Xenova/all-MiniLM-L6-v2/` — 本地模型文件
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 重启 Claude Code 后 MegaMemory MCP 会自动连接并使用已下载的模型
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. `megamemory stats` 显示 19 nodes / 30 edges
|
||||
2. `megamemory list_roots` 返回 3 个根节点
|
||||
3. `megamemory understand "认证系统"` 返回 JWT/TOTP/Agent认证(相似度 >0.15)
|
||||
4. `megamemory understand "心跳数据流转"` 返回数据流概念(相似度 >0.50)
|
||||
5. server-memory `read_graph` 只剩 2 个实体(uzuma + Nexus-Production-Server)
|
||||
@@ -0,0 +1,40 @@
|
||||
# 2026-05-26 — 部署程序门控实现
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. **Pre-deploy 门控脚本** — `deploy/pre_deploy_check.sh`,部署前自动检查 3 道门:
|
||||
- Changelog门:`docs/changelog/YYYY-MM-DD-*.md` 必须存在
|
||||
- 审计门:`docs/audit/YYYY-MM-DD-*.md` 必须存在
|
||||
- 测试门:`tests/test_api.py` 必须全通过(无测试文件则跳过)
|
||||
- 任何一门不过 → 退出码1,部署被阻止
|
||||
2. **MCP deploy 工具集成门控** — `mcp/Nexus_server.py` 的 `deploy()` 函数在 git pull + restart 前自动执行门控脚本,不过返回 `🚫 DEPLOY BLOCKED`
|
||||
3. **审计记录模板** — `docs/audit/TEMPLATE.md`,标准化审计输出格式
|
||||
4. **今日审计记录** — `docs/audit/2026-05-26-terminal-sidebar-ssh-key-fix.md`
|
||||
5. **CLAUDE.md 更新** — 加入门控说明
|
||||
|
||||
## 动机
|
||||
|
||||
之前部署全靠 AI 自觉执行流程,跳步无法被程序阻止。现在把关键步骤变成部署前置条件,跳步 = 部署不了。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `deploy/pre_deploy_check.sh` | 新建 — 3道门控检查脚本 |
|
||||
| `mcp/Nexus_server.py` | deploy() 加入门控检查 |
|
||||
| `docs/audit/TEMPLATE.md` | 新建 — 审计记录模板 |
|
||||
| `docs/audit/2026-05-26-*.md` | 新建 — 今日审计记录 |
|
||||
| `CLAUDE.md` | 加入门控说明 |
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无
|
||||
- **需重启**: MCP server 需要重启(改动 Nexus_server.py),但 MCP server 是本地进程,下次会话自动生效
|
||||
- **远程部署**: 需要将 `deploy/pre_deploy_check.sh` 同步到远程服务器
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. `bash -n deploy/pre_deploy_check.sh` — Shell 语法通过 ✅
|
||||
2. `python -c "import py_compile; py_compile.compile('mcp/Nexus_server.py', doraise=True)"` — Python 语法通过 ✅
|
||||
3. 删除 `docs/changelog/2026-05-26-*.md` → 执行门控脚本 → Changelog门 BLOCK ✅
|
||||
4. 恢复 changelog → 执行门控脚本 → 3/3 gates passed ✅
|
||||
@@ -0,0 +1,47 @@
|
||||
# 2026-05-26 — 移除资产管理页面,简化服务器表单
|
||||
|
||||
## 变更摘要
|
||||
|
||||
移除独立资产管理页面(assets.html),精简服务器表单组织信息区域:
|
||||
|
||||
1. **删除 assets.html** — 平台模板和节点分组功能实际用途有限,移除独立页面
|
||||
2. **侧边栏移除入口** — layout.js 删除「🗂 资产管理」导航项
|
||||
3. **服务器表单简化** — 移除平台/节点下拉,仅保留分类(Category)combobox
|
||||
4. **Category 改为 combobox** — `<input list="categoryList">` 支持从已有分类选择或自由输入
|
||||
5. **Nginx 缓存控制** — 为 JS/CSS 静态文件添加 `no-store` 响应头,防止浏览器缓存旧版
|
||||
|
||||
## 动机
|
||||
|
||||
- 平台模板的 `default_protocols`、`charset`、`type` 字段未被任何功能使用
|
||||
- 节点树仅用于服务器表单下拉,无按节点筛选等联动
|
||||
- 服务器列表的「分类」筛选基于 `server.category` 自由文本字段,与节点分组独立
|
||||
- 用户明确要求直接去掉资产管理
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `web/app/assets.html` | 删除 |
|
||||
| `web/app/layout.js` | 移除 `{ id:'assets', ... }` 导航项 |
|
||||
| `web/app/servers.html` | 移除平台/节点下拉、`loadOrgSelects()`;Category 改为 combobox + datalist;移除 `platform_id`/`node_id` |
|
||||
|
||||
## 不变的部分
|
||||
|
||||
- **数据库模型**:Platform 和 Node 表不变,无迁移
|
||||
- **后端 API**:`/api/assets/platforms` 和 `/api/assets/nodes` 端点保留
|
||||
- **CSV 导入**:继续通过 platform_name/node_name 解析 ID
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无
|
||||
- **需重启**: 否(仅前端静态文件变更)
|
||||
- **Nginx**: 已为 JS/CSS 添加 no-cache 头(需 reload,已执行)
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 远程服务器 `layout.js` 不含「资产管理」(SSH grep 确认 0 匹配)
|
||||
2. `assets.html` 已删除 (SSH 确认文件不存在)
|
||||
3. `servers.html` 不含 `srvPlatform`/`srvNode`/`loadOrgSelects` (0 匹配)
|
||||
4. `servers.html` 含 `categoryList` datalist (2 匹配)
|
||||
5. Playwright fetch (cache: no-store) 确认最新 layout.js 正确
|
||||
6. Nginx 为 `/app/*.js` 和 `/app/*.css` 添加 Cache-Control: no-store
|
||||
@@ -0,0 +1,99 @@
|
||||
# 2026-05-26 — 服务器表单 UX 优化 + Bug 修复 + 安全加固
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. **分类字段改为 select+input combobox** — 替换不可见的 `<datalist>`,用 `<select>` 下拉 + `__custom__` 自定义输入模式,用户可从已有分类选择或自由输入新分类
|
||||
2. **Agent 端口隐藏** — `srvAgentPort` 改为 `type="hidden"`,默认值 8601 不变,减少表单认知负担
|
||||
3. **目标路径默认值** — 从 `/home/deploy/target` 改为 `/www/wwwroot`
|
||||
4. **CSV 模板修正** — 移除 Agent端口/平台/节点 列,更新示例行(target_path=/www/wwwroot, category=商城);后端保留 `_CSV_IMPORT_ONLY_COLUMNS` 向后兼容旧模板导入
|
||||
5. **Bug 修复:showEditServer 分类同步** — 修复编辑服务器时分类值丢失的两个 bug:
|
||||
- `_refreshCategoryDatalist()` 在分类同步逻辑之后调用,导致 select 选项为空
|
||||
- 第二次冗余 `_refreshCategoryDatalist()` 调用会触发 `_onCategoryChange()` 清空自定义分类值
|
||||
6. **XSS 加固** — `agent_version` 渲染加 `esc()` 转义;`esc()` 函数增加双引号转义(`"`),防止 HTML 属性上下文注入
|
||||
7. **XSS 加固(审计追加)** — CSV 导入错误显示中 `e.name`、`e.domain`、`e.row` 加 `esc()` 转义;预设/SSH密钥预设 `<option value>` 中 `p.id` 加 `esc()` 防属性突破
|
||||
8. **空 catch 消除** — 4 处空 `catch(e){}` 全部添加 `console.warn()` 日志(Agent 轮询器、预设加载、SSH 密钥预设加载、剪贴板写入);后端 3 处 `except Exception: pass` 改为 `logger.warning()`(CSV platform/node 加载、批量 Agent 回滚×2)
|
||||
9. **Nginx 缓存控制** — 为 `/app/*.js` 和 `/app/*.css` 添加 `Cache-Control: no-store`,防止浏览器缓存旧版静态文件
|
||||
10. **Bug 修复:SSH 密钥双重加密** — `update_server` 中从 SSH 密钥预设解析密钥时预先 `encrypt_value()`,遍历 `ALLOWED_FIELDS` 时又加密一次,导致 `ssh_key_private` 被双重加密,WebSSH 连接报 `Invalid private key`。修复为与 `create_server` 一致:预设解析后存明文,统一在遍历步骤中单次加密
|
||||
|
||||
## 动机
|
||||
|
||||
- `<datalist>` 在大多数浏览器中不显示下拉箭头,用户无法发现分类选项
|
||||
- Agent 端口极少修改,展示增加表单复杂度
|
||||
- `/home/deploy/target` 不是中国用户典型部署路径,`/www/wwwroot` 更符合宝塔面板习惯
|
||||
- CSV 模板包含用户不使用的列(Agent端口/平台/节点),造成困惑
|
||||
- 代码审计发现 showEditServer 中分类同步 bug 和 XSS 隐患
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `web/app/servers.html` | 分类 combobox(select+input)、Agent 端口 hidden、目标路径默认值、showEditServer 分类 bug 修复、agent_version XSS 修复、esc() 双引号转义、CSV 导入错误 XSS 修复、预设 option value esc() 加固、4 处空 catch 加日志、移除冗余 `_onCategoryChange()` 调用 |
|
||||
| `server/api/servers.py` | CSV_COLUMNS 重构(移除 Agent端口/平台/节点)、新增 `_CSV_IMPORT_ONLY_COLUMNS` 向后兼容、模板示例更新、3 处空 except 改 logger.warning()、SSH 密钥预设双重加密 bug 修复 |
|
||||
| Nginx 配置(远程) | `/app/*.js` 和 `/app/*.css` 添加 `Cache-Control: no-store` |
|
||||
|
||||
## Bug 详情
|
||||
|
||||
### showEditServer 分类同步 bug(HIGH)
|
||||
|
||||
**现象**:编辑服务器时,若服务器分类不在已有选项中,分类值被清空。
|
||||
|
||||
**根因**:
|
||||
1. `_refreshCategoryDatalist()` 在分类同步逻辑(614-618 行)之后调用 → select 选项为空时无法匹配
|
||||
2. 第二次 `_refreshCategoryDatalist()` 调用触发 `_onCategoryChange()`,当 `sel.value==='__custom__'` 时执行 `inp.value=''`,覆盖了刚设置的自定义分类值
|
||||
|
||||
**修复**:
|
||||
1. 将 `_refreshCategoryDatalist()` 调用移到同步逻辑之前
|
||||
2. 删除冗余的第二次 `_refreshCategoryDatalist()` 调用
|
||||
3. `showEditServer` 中直接设置 select/input 状态,不调用 `_onCategoryChange()`
|
||||
|
||||
### agent_version XSS(MEDIUM)
|
||||
|
||||
**现象**:`s.agent_version` 未转义直接插入 HTML。
|
||||
|
||||
**修复**:改为 `esc(s.agent_version)||'--'`
|
||||
|
||||
### esc() 双引号不转义(LOW)
|
||||
|
||||
**现象**:`esc()` 使用 `textContent→innerHTML` 方法,不转义双引号,在 `<option value="${esc(c)}">` 属性上下文中可能被突破。
|
||||
|
||||
**修复**:`esc()` 末尾追加 `.replace(/"/g,'"')`
|
||||
|
||||
### CSV 模板示例行列数不匹配(MEDIUM)
|
||||
|
||||
**现象**:CSV 模板 header 有 11 列,但 example 行只有 10 个值。缺少 `私钥内容`(ssh_key_private)列的空值。
|
||||
|
||||
**根因**:密钥路径和私钥内容都是空值,需要两个连续逗号 `,,,`,但 example 行只写了一个 `,,`。
|
||||
|
||||
**修复**:example 行从 `your-password,,商城` 改为 `your-password,,,商城`
|
||||
|
||||
### SSH 密钥双重加密(HIGH)
|
||||
|
||||
**现象**:WebSSH 连接使用 SSH 密钥预设的服务器时,报 `Invalid private key` 错误。
|
||||
|
||||
**根因**:`update_server` 中从 SSH 密钥预设解析密钥时执行 `encrypt_value(decrypt_value(preset.encrypted_private_key))`,将预设密钥解密后立即加密存入 `update_data`。随后遍历 `ALLOWED_FIELDS` 时,对 `ssh_key_private` 字段又执行了一次 `encrypt_value(value)`,导致双重加密。`create_server` 路由无此问题(预设解析后存明文,统一加密步骤只执行一次)。
|
||||
|
||||
**修复**:
|
||||
1. `update_server` 预设解析改为存明文(与 `create_server` 一致):`decrypt_value(preset.encrypted_private_key)`
|
||||
2. 已受影响的 server 8 数据修复:双重解密后重新单次加密
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无
|
||||
- **需重启**: 是(servers.py 后端改动需重启服务,已执行)
|
||||
- **Nginx**: 已添加 no-cache 头(需 reload,已执行)
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 编辑「机器人」服务器(分类=商城)→ 分类下拉正确显示"商城"选中 ✅
|
||||
2. 编辑「冲量银海」服务器(无分类)→ 分类下拉显示"— 无分类 —" ✅
|
||||
3. 添加服务器 → 分类默认"— 无分类 —"、目标路径 `/www/wwwroot`、Agent 端口 8601(hidden) ✅
|
||||
4. 选择"✏️ 自定义输入…"→ 文本输入框出现,输入值正确同步到 `srvCategory` ✅
|
||||
5. 切换回已有分类 → 自定义输入框隐藏,值正确同步 ✅
|
||||
6. CSV 模板下载 → 无 Agent端口/平台/节点 列,11列 header = 11列 example,含 `/www/wwwroot` ✅
|
||||
7. `esc()` 函数转义双引号 → `esc('test"val')` 返回 `test"val` ✅
|
||||
8. 远程服务器 Nginx 为 JS/CSS 返回 `Cache-Control: no-store` ✅
|
||||
9. 逐行审计 `servers.html`:0 FINDING,1 SAFE(布尔表达式) ✅
|
||||
10. 逐行审计 `servers.py`:0 FINDING,SQL/命令注入/敏感泄露/大规模赋值全 SAFE ✅
|
||||
11. 空 catch 块:前端 0 处、后端仅保留 `json.JSONDecodeError` 特定异常 ✅
|
||||
12. 部署后验证:`esc(p.id)` 出现 2 次、`console.warn` 出现 4 次、空 `catch(e){}` 出现 0 次 ✅
|
||||
13. Server 8 SSH 密钥解密验证:单次 `decrypt_value()` 得到 `-----BEGIN OPENSSH PRIVATE KEY-----` ✅
|
||||
@@ -0,0 +1,54 @@
|
||||
# 2026-05-26 — Terminal 左侧导航+右侧面板 + SSH密钥/连接修复 + 认证切换清理
|
||||
|
||||
## 变更摘要
|
||||
|
||||
1. **Terminal 左侧导航栏** — `sidebarOpen` 默认改为 `true`,与其他页面一致,可收起展开
|
||||
2. **Terminal 右侧服务器面板** — 新增 `w-64` 右侧面板,显示当前服务器信息(名称/IP/状态)+ 所有服务器列表(可点击快速切换终端)
|
||||
3. **面板切换按钮** — 工具栏新增 📋 按钮,可切换右侧面板显示/隐藏
|
||||
4. **SSH 密钥双重加密修复(HIGH)** — `update_server` 预设解析时预先 `encrypt_value()`,遍历 ALLOWED_FIELDS 时又加密一次,导致 WebSSH 报 `Invalid private key`
|
||||
5. **Server 8 数据修复** — 双重加密的 `ssh_key_private` 已重新单次加密
|
||||
6. **认证方式切换清理(HIGH)** — 从密钥改密码时旧密钥数据未清除(`ssh_key_private`、`ssh_key_preset_id`、`ssh_key_configured` 残留),前端和后端都增加了联动清理逻辑
|
||||
7. **SSH 主机密钥验证配置** — `SSH_STRICT_HOST_CHECKING` 改为 `false`(运维平台连接大量不可预知服务器,跳过主机密钥验证是业界标准做法)
|
||||
8. **XSS 加固(审计追加)** — CSV 导入错误显示中 `e.name`/`e.domain`/`e.row` 加 `esc()`;预设 `<option value>` 中 `p.id` 加 `esc()`
|
||||
9. **空 catch 消除** — servers.html 4 处 + servers.py 3 处 + terminal.html 2 处空 catch 全部加日志
|
||||
10. **Terminal 错误消息 XSS 加固** — `msg.message` 和 `ev.reason` 加 `esc()` 转义
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `web/app/terminal.html` | 左侧导航默认展开、右侧服务器面板、面板切换按钮、esc() 加固、空 catch 加日志 |
|
||||
| `server/api/servers.py` | SSH 密钥预设双重加密修复、认证方式切换联动清理(password→清密钥字段、key→清密码字段)、3 处空 except 改 logger.warning() |
|
||||
| `web/app/servers.html` | 认证方式切换联动清理(密钥↔密码互清对方凭据)、CSV 导入错误 XSS 修复、预设 option value esc() 加固、4 处空 catch 加日志 |
|
||||
|
||||
## Bug 详情
|
||||
|
||||
### SSH 密钥双重加密(HIGH)
|
||||
|
||||
**现象**:WebSSH 连接使用 SSH 密钥预设的服务器时,报 `Invalid private key`。
|
||||
|
||||
**根因**:`update_server` 预设解析时 `encrypt_value(decrypt_value(preset.encrypted_private_key))` 已加密,遍历 ALLOWED_FIELDS 时又 `encrypt_value(value)` 再加密一次。`create_server` 无此问题。
|
||||
|
||||
**修复**:
|
||||
1. `update_server` 预设解析改为存明文(与 `create_server` 一致)
|
||||
2. Server 8 数据修复:双重解密 → 重新单次加密
|
||||
|
||||
### Terminal 错误消息未转义(MEDIUM)
|
||||
|
||||
**现象**:`msg.message` 和 `ev.reason` 直接拼接进终端输出,如果包含 ANSI 转义序列可能导致显示异常。
|
||||
|
||||
**修复**:用 `esc()` 过滤后输出
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **DB 迁移**: 无(server 8 数据已直接修复)
|
||||
- **需重启**: 是(servers.py 后端改动已重启)
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开 terminal.html → 左侧导航栏默认展开 ✅
|
||||
2. 右侧面板显示当前服务器信息 + 服务器列表 ✅
|
||||
3. 点击服务器列表中其他服务器 → 跳转到该服务器终端 ✅
|
||||
4. 收起右侧面板 → 终端区域自动扩展 ✅
|
||||
5. 全屏模式 → 左右面板自动隐藏 ✅
|
||||
6. Server 8 SSH 密钥解密:单次 decrypt_value() 得到 PEM 密钥 ✅
|
||||
@@ -0,0 +1,71 @@
|
||||
# WebSSH 终端命令输入栏
|
||||
|
||||
**日期**: 2026-05-26
|
||||
**变更摘要**: 为 WebSSH 终端底部新增命令输入栏,避免直接在 xterm 中逐字符输入导致的显示错乱问题
|
||||
|
||||
## 动机
|
||||
|
||||
1. 用户直接在 xterm 终端中逐字符输入时,SSH echo 与 ANSI prompt 序列交错,导致显示错乱(如 "1111312312oot@mqtele:~#")
|
||||
2. 根因:逐字符发送 WebSocket 消息时,`read(4096)` 返回的数据可能混合 echo 和 prompt 的 ANSI 片段,时序依赖导致渲染错乱
|
||||
3. 命令输入栏一次性发送完整命令 + `\r`,完全避免逐字符 echo 交错问题
|
||||
4. 同时提供 Ctrl+C/D 和 Tab 快捷按钮,方便运维操作
|
||||
|
||||
## 变更内容
|
||||
|
||||
### web/app/terminal.html — 3处修改
|
||||
|
||||
**修改1: CSS 布局调整**
|
||||
- 旧:`#terminalWrap{height:calc(100vh - 48px)}` — 固定高度,终端占满
|
||||
- 新:`#terminalWrap{flex:1;min-height:0}` — flex 弹性布局,为底部命令栏留空间
|
||||
- 全屏样式简化:移除 `height:100vh`,flex:1 + fixed inset 自动撑满
|
||||
|
||||
**修改2: HTML 结构 — 新增命令输入栏**
|
||||
- terminalWrap 改为 `flex flex-col` 容器
|
||||
- `#terminal` 加 `flex-1 min-h-0` 占据剩余空间
|
||||
- 新增 `#cmdBar` 底部栏,包含:
|
||||
- 命令提示符 `❯`
|
||||
- `<input id="cmdInput">` — 命令输入框,monospace 字体
|
||||
- `发送` 按钮 — 调用 `sendCmd()`
|
||||
- `⌃C` / `⌃D` / `Tab` 快捷按钮 — 分别调用 `sendCtrl()` / `sendKey()`
|
||||
|
||||
**修改3: JavaScript — 命令输入逻辑**
|
||||
- `cmdHistory` / `cmdHistoryIdx` — 命令历史记录 + 上下箭头导航
|
||||
- `cmdInput keydown` — Enter 发送、ArrowUp/Down 浏览历史
|
||||
- `sendCmd()` — 发送完整命令 + `\r`,记入历史,清空输入框
|
||||
- `sendCtrl(key)` — 发送控制字符(charCode - 96)
|
||||
- `sendKey(key)` — 发送原始按键(如 Tab)
|
||||
|
||||
## 技术分析
|
||||
|
||||
逐字符输入问题:
|
||||
```
|
||||
用户输入 'w' → WS send {type:DATA, data:"w"}
|
||||
→ SSH echo "\033[...w" → read(4096) 可能返回 "w\033[...root@..."
|
||||
→ xterm 渲染时 ANSI 序列与 echo 字符交错 → 显示错乱
|
||||
```
|
||||
|
||||
命令输入栏解决方案:
|
||||
```
|
||||
用户输入 "whoami" → 点发送 → WS send {type:DATA, data:"whoami\r"}
|
||||
→ SSH 完整回显 "whoami\r\nroot\r\n" → 一次返回 → 正常渲染
|
||||
```
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `web/app/terminal.html` — 终端页面(CSS + HTML + JS 共3处修改)
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 不需重启后端(纯前端改动)
|
||||
- 不需 DB 迁移
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开 terminal.html → 底部显示命令输入栏(❯ 提示符 + 输入框 + 发送按钮)
|
||||
2. 输入 `whoami` → 点发送 → 终端显示 `root`(无显示错乱)
|
||||
3. 输入 `ls` → Enter 键发送 → 正常显示文件列表
|
||||
4. ArrowUp → 回显上一条命令 `ls`,ArrowDown → 清空
|
||||
5. 点击 `⌃C` 按钮 → 中断当前命令
|
||||
6. 点击 `Tab` 按钮 → 触发自动补全
|
||||
7. 终端 xterm 区域自动占满输入栏上方空间
|
||||
8. 全屏模式下输入栏仍然可见
|
||||
@@ -0,0 +1,59 @@
|
||||
# WebSSH 终端输入回显延迟修复
|
||||
|
||||
**日期**: 2026-05-26
|
||||
**变更摘要**: 修复 WebSSH 终端输入字符不即时显示的问题 — 将 async-for (readline) 改为 read() + 降低 bufsize
|
||||
|
||||
## 动机
|
||||
|
||||
1. 用户在终端输入字符后,字符不会即时显示(echo 延迟),必须按 Enter 后才一次性出现
|
||||
2. 根因:`async for data in shell.stdout` 内部调用 `readline()` → `readuntil('\n')`,会缓冲所有数据直到遇到换行符
|
||||
3. 交互式终端中,单字符 echo 永远不包含换行符,导致所有输入字符被缓冲直到用户按 Enter
|
||||
|
||||
## 变更内容
|
||||
|
||||
### server/api/webssh.py — 2处修改
|
||||
|
||||
**修改1: `_read_shell_output()` 函数 — 消除 readline 缓冲**
|
||||
- 旧:`async for data in shell.stdout:` (内部 readline,等换行才 yield)
|
||||
- 新:`while not shell.stdout.at_eof(): data = await shell.stdout.read(4096)` (有数据即返回)
|
||||
- `read(n)` 使用 `exact=False`,只要缓冲区有数据就立即返回(最多 n 字节),不等换行
|
||||
|
||||
**修改2: `create_process()` 调用 — 降低 bufsize**
|
||||
- 旧:`conn.create_process(term_type="xterm-256color", term_size=(24, 80))` (默认 bufsize=128KB)
|
||||
- 新:`conn.create_process(term_type="xterm-256color", term_size=(24, 80), bufsize=4096)`
|
||||
- 两处 create_process(主连接 + 失效重试连接)均添加 `bufsize=4096`
|
||||
- 4KB 缓冲区足够单次交互 echo,同时大幅降低延迟
|
||||
|
||||
## 技术分析
|
||||
|
||||
asyncssh 数据流路径:
|
||||
```
|
||||
async for data in shell.stdout
|
||||
→ SSHStreamProcess.__aiter__()
|
||||
→ SSHStreamSession.aiter()
|
||||
→ readline()
|
||||
→ readuntil('\n') ← 阻塞直到换行符
|
||||
```
|
||||
|
||||
修复后路径:
|
||||
```
|
||||
while not shell.stdout.at_eof():
|
||||
data = await shell.stdout.read(4096)
|
||||
→ SSHStreamSession.read(4096, exact=False) ← 有数据即返回
|
||||
```
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/api/webssh.py` — WebSSH WebSocket 处理(2处修改)
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 需重启后端(webssh.py 修改)
|
||||
- 前端无改动
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开 terminal.html?server_id=8 → WebSocket 保持 OPEN,状态显示「已连接」
|
||||
2. 输入 `whoami` → 字符即时显示(不等 Enter),按 Enter 后立即显示 `root`
|
||||
3. 后端日志无 `shell creation failed` 错误
|
||||
4. 终端交互流畅,无 echo 延迟
|
||||
@@ -0,0 +1,40 @@
|
||||
# WebSSH 终端无法输入修复
|
||||
|
||||
**日期**: 2026-05-26
|
||||
**变更摘要**: 修复 WebSSH 终端连接建立后立即断开、无法输入的问题
|
||||
|
||||
## 动机
|
||||
|
||||
1. WebSocket 连接建立后几秒内就断开(wsState=CLOSED),用户无法在终端输入
|
||||
2. 后端日志报 `WebSSH shell creation failed: ` 错误消息为空,无法诊断
|
||||
3. `finally` 块双重关闭 WebSocket 导致 `Unexpected ASGI message 'websocket.close'` 异常
|
||||
4. 连接池复用的旧连接可能已失效(TCP连接在但SSH通道损坏),`create_process` 抛空消息异常
|
||||
|
||||
## 变更内容
|
||||
|
||||
### server/api/webssh.py — 3个bug修复
|
||||
- **失效连接重试**: `create_process` 失败时,强制关闭池中旧连接,用新连接重试一次
|
||||
- **异常信息改进**: `logger.error` 改用 `{type(e).__name__}: {e!r}` 避免空消息
|
||||
- **双重关闭修复**: 引入 `ws_closed` 标志,异常分支 close 后 finally 不再重复 close
|
||||
- 所有 `websocket.close()` 调用增加 try/except 防护
|
||||
|
||||
### web/app/terminal.html — Alpine 初始化竞态修复
|
||||
- `$d()` 函数加 try/catch:Alpine 未加载时返回默认对象而非抛 `ReferenceError`
|
||||
- 消除 `ResizeObserver fit error: ReferenceError: Alpine is not defined` 控制台警告
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/api/webssh.py` — WebSSH WebSocket 处理
|
||||
- `web/app/terminal.html` — 前端终端页面
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 需重启后端(webssh.py 修改)
|
||||
- terminal.html 静态文件无需重启
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开 terminal.html?server_id=8 → WebSocket 保持 OPEN
|
||||
2. 输入 `whoami` → 回显 `root`
|
||||
3. 后端日志无 `shell creation failed` 和 `Unexpected ASGI message` 错误
|
||||
4. 控制台无 `Alpine is not defined` 警告
|
||||
@@ -0,0 +1,57 @@
|
||||
# 2026-05-27 Agent安装修复 + 两台子服务器Agent部署
|
||||
|
||||
## 变更摘要
|
||||
1. 修复 Agent 安装 404 错误(/agent/ 静态文件未挂载)
|
||||
2. 修复 curl|bash 管道错误隐藏问题
|
||||
3. 修复 CRLF 导致远程 shell 脚本执行失败
|
||||
4. 部署 Agent 到两台子服务器
|
||||
|
||||
## 动机
|
||||
Agent 安装命令一直返回 404,因为 FastAPI 未挂载 /agent/ 静态文件路由。
|
||||
curl|bash 管道模式会隐藏下载失败。
|
||||
Windows 工作区给 .sh/.py 文件添加了 CRLF,导致 Linux 上 `\r': command not found`。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/main.py` — 新增 `app.mount("/agent", StaticFiles(...))` 挂载 web/agent/ 目录
|
||||
- `server/api/servers.py` — 3处 `curl ... | bash -s --` 改为 `TMP=$(mktemp) && curl ... -o "$TMP" && bash "$TMP" --`
|
||||
|
||||
### 前端/Agent
|
||||
- `web/agent/install.sh` — CRLF→LF
|
||||
- `web/agent/agent.py` — CRLF→LF
|
||||
- `web/agent/agent.sh` — CRLF→LF
|
||||
|
||||
### 部署脚本
|
||||
- `deploy/install.sh` — CRLF→LF
|
||||
- `deploy/health_monitor.sh` — CRLF→LF
|
||||
- `deploy/db_backup.sh` — CRLF→LF
|
||||
|
||||
### 根目录
|
||||
- `.gitattributes` — 新增,防止 git 自动转 CRLF(*.sh eol=lf, *.py eol=lf)
|
||||
|
||||
## Agent 部署结果
|
||||
|
||||
| 服务器 | IP | server_id | 状态 |
|
||||
|--------|-----|-----------|------|
|
||||
| 机器人 | 66.154.115.131 | 8 | ✅ online, agent_version=2.0.0 |
|
||||
| 冲量银海 | 39.105.228.140 | 9 | ✅ online, agent_version=2.0.0 |
|
||||
|
||||
### 冲量银海额外步骤
|
||||
- 需先 `apt install python3.11-venv`(Debian 12 默认不带 python3.11-venv)
|
||||
- install.sh 第2步 `python3 -m venv` 在缺少 ensurepip 时会静默失败,source activate 报错
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:需重启 Nexus 后端(Supervisor: `supervisorctl restart nexus`)
|
||||
- 是:需 Nginx reload(`nginx -s reload`)
|
||||
- 否:无数据库迁移
|
||||
|
||||
## 验证方式
|
||||
1. `curl https://api.synaglobal.vip/agent/install.sh` → 返回脚本内容(非 404)
|
||||
2. Nexus Dashboard 显示 2 台服务器在线
|
||||
3. `curl https://api.synaglobal.vip/api/servers/8` → `is_online: true, agent_version: "2.0.0"`
|
||||
4. `curl https://api.synaglobal.vip/api/servers/9` → `is_online: true, agent_version: "2.0.0"`
|
||||
|
||||
## 已知问题
|
||||
- install.sh 第2步 venv 创建在缺少 python3-venv 时会静默失败,应加检测
|
||||
- batch install API 返回 success:false 但无具体错误信息,需改进错误上报
|
||||
@@ -0,0 +1,55 @@
|
||||
# 2026-05-27 Agent os_release 上报 + 前端系统版本拆分 + 空字段隐藏
|
||||
|
||||
## 变更摘要
|
||||
1. Agent 心跳和 /health 端点新增 `os_release` 字段,读取 `/etc/os-release` 的 PRETTY_NAME(如 "Ubuntu 24.04 LTS")
|
||||
2. servers.html 系统信息面板拆分为两卡片:「发行版」显示 os_release,「内核平台」显示 platform
|
||||
3. servers.html 详情面板描述/目标路径/认证方式空值时隐藏整行,不再显示 `--`
|
||||
|
||||
## 动机
|
||||
1. servers.html 系统版本列显示 `Linux-6.8.0-31-generic-x86_64-with-glibc2.39`(内核字符串),无法直观判断发行版
|
||||
2. 运维人员需要快速识别服务器是 Ubuntu 24 还是 Debian 12 等
|
||||
3. 空字段显示 `--` 无意义,应该隐藏整行不占空间
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### Agent
|
||||
- `web/agent/agent.py` — 新增 `_os_release()` 函数;health 端点添加 `os_release` 字段;heartbeat payload 添加 `os_release` 字段
|
||||
|
||||
### 前端
|
||||
- `web/app/servers.html` — 系统信息面板拆分为「发行版」(`siOSRelease` / `os_release`) 和「内核平台」(`siPlatform` / `platform`) 两个独立卡片;描述/目标路径/认证方式空值时隐藏行(`style.display`),目标路径用 `<code>` 标签
|
||||
|
||||
## 技术细节
|
||||
- `_os_release()` 读取 `/etc/os-release`,解析 `PRETTY_NAME=` 行,如文件不存在返回空字符串
|
||||
- health 端点中也有内联的 os_release 检测(与 heartbeat 的 `_os_release()` 逻辑相同)
|
||||
- 前端拆分为两个独立卡片:发行版 (`os_release`) + 内核平台 (`platform`),各自独立赋值
|
||||
|
||||
## 部署过程问题
|
||||
- 机器人 (66.154.115.131) Agent 升级后因旧进程占用 8601 端口导致 crash loop
|
||||
- 原因:旧 Agent 进程未被 kill,新进程无法绑定端口
|
||||
- 修复:`kill -9 $(lsof -t -i :8601)` 后 `systemctl restart nexus-agent`
|
||||
- 非代码 bug,是升级流程中端口释放竞争问题
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:需升级 Agent(curl 新 agent.py + systemctl restart nexus-agent)
|
||||
- 否:无数据库迁移,Nexus 后端无需重启(心跳 JSON 透传)
|
||||
|
||||
## 验证方式
|
||||
1. API `/api/servers/` 返回 `system_info.os_release = "Ubuntu 24.04 LTS"` ✅
|
||||
2. 浏览器 servers.html 点击机器人 → 系统信息面板显示 "Ubuntu 24.04 LTS" ✅
|
||||
3. 冲量银海显示 "Debian GNU/Linux 12 (bookworm)" ✅
|
||||
4. 机器人 Agent 心跳正常发送 ✅
|
||||
5. 空字段(描述/目标路径)整行隐藏 ✅
|
||||
6. 目标路径编辑保存成功后正确显示 ✅
|
||||
7. 后端 /health 返回 `{"status":"ok"}` ✅
|
||||
|
||||
## 审计8步
|
||||
- H1: `_os_release()` 只读本地文件 — ✅ 无注入风险
|
||||
- H2: `os_release` / `platform` 字符串用 `textContent` 赋值 — ✅ 无 XSS
|
||||
- H3: /etc/os-release 为系统只读文件 — ✅ 无安全风险
|
||||
- H4: 空字段隐藏用 `style.display`,值固定为空或 none — ✅ 无注入
|
||||
- H5: Agent 端口占用非代码问题 — ✅ 运维处理
|
||||
- H6: 审计文件 `docs/audit/2026-05-27-servers-info-display.md` — ✅ 0 FINDING
|
||||
- DoD: 无明文密码/命令注入/SQL注入/静默吞错/硬编码 ✅
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog
|
||||
@@ -0,0 +1,88 @@
|
||||
# 2026-05-27 批量按钮修复 + 审计日志中文化 + terminal断开反馈 + 远程安装按钮修复 + Agent安装sudo兼容
|
||||
|
||||
## 变更摘要
|
||||
1. 修复 servers.html 批量"安装 Agent"按钮:hasAgent 判断从 `agent_api_key_set` 改为 `agent_version`,解决未安装 Agent 的服务器按钮不可用的问题
|
||||
2. 所有后端 AuditLog detail 字段统一为中文(此前混杂英文如 `name=xxx`、`WebSSH to xxx` 等)
|
||||
3. audit.html 操作列和目标类型列显示中文映射(`create_server` → `添加服务器`)
|
||||
4. audit.html 新增 ACTION_NAMES 覆盖所有已知 action 类型(含 webssh_token、refresh_token_mismatch 等)
|
||||
5. mcp/Nexus_server.py deploy 流程修复:git pull 先于 gate check + origin/main 替代 origin/master
|
||||
6. **修复详情面板"远程安装"按钮始终禁用的 bug**:`s._base_url` 不存在于服务器对象,改为从新 API `GET /api/servers/meta/api_base_url` 获取全局 `NEXUS_API_BASE_URL`
|
||||
7. `api_base_url` 加入 DB_OVERRIDE_MAP,可在设置页面管理
|
||||
8. 安装向导新增 `api_base_url` 写入 MySQL settings 表
|
||||
9. 启动时自动迁移:若 MySQL 缺 `api_base_url` 但 `.env` 有值,自动插入
|
||||
10. **Agent install.sh 非 root 用户兼容**:自动检测当前用户,非 root 时使用 `sudo` 执行 `mkdir`/`apt-get`/`systemctl`/`tee` 等特权操作,解决 Ubuntu 等非 root SSH 用户无法写入 `/opt` 的问题
|
||||
11. **后端 _sudo_wrap 辅助函数**:`servers.py` 新增 `_sudo_wrap(cmd, ssh_user)` — 非 root 用户自动配置 NOPASSWD sudo(写入 `/etc/sudoers.d/nexus-agent`),命令执行后自动清理;应用于 install-agent / batch-install / upgrade-agent / batch-upgrade 共 4 个端点
|
||||
12. **install.sh venv 创建 set -e 兼容**:`python3 -m venv` 在缺 `python3-venv` 的 Ubuntu 系统上返回 exit 1,`set -e` 导致脚本直接退出,apt-get 安装 python3-venv 的回退逻辑从未执行。修复:首次 venv 创建加 `|| true` 允许失败,然后检查 activate 是否存在再走回退路径
|
||||
|
||||
## 动机
|
||||
1. 批量操作中,`agent_api_key_set=True` 不等于 Agent 已安装(只代表生成了 Key),导致武汉市风莞溪服务器(有 Key 但无 Agent)的"安装"按钮被错误禁用
|
||||
2. 审计日志详情混合中英文,运维人员阅读不便
|
||||
3. MCP deploy 工具先执行 gate check 再 git pull,导致 changelog/audit 文件不在磁盘上被 BLOCK
|
||||
4. **详情面板"远程安装"按钮始终禁用**:`s._base_url` 是前端代码中的错误引用(`API_BASE_URL` 是全局设置,不是服务器的字段),导致所有服务器都无法远程安装 Agent
|
||||
5. **非 root 用户远程安装 Agent 失败**:Ubuntu 等系统默认用户(如 `ubuntu`)无 `/opt` 写权限,install.sh 直接 `mkdir -p /opt/nexus-agent` 报 `Permission denied`
|
||||
6. **install.sh venv 创建被 set -e 终止**:Ubuntu 24.04 默认未安装 `python3-venv`,`python3 -m venv` 返回 exit 1,`set -e` 导致脚本直接退出,apt-get 安装 python3-venv 的回退逻辑从未执行
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 前端
|
||||
- `web/app/servers.html` — updateBatchBar() hasAgent 从 `s.agent_api_key_set` 改为 `s.agent_version`;loadAgentInstall() base_url_conf 从 `s._base_url` 改为 `_apiBaseUrl`(从新 API 加载);新增 `loadApiBaseUrl()` 函数
|
||||
- `web/app/audit.html` — 新增 ACTION_NAMES / TARGET_NAMES 中文映射(覆盖所有已知 action/target_type)
|
||||
- `web/app/settings.html` — 基础设施区块新增 `api_base_url`(主站对外 URL)
|
||||
|
||||
### Agent 安装脚本
|
||||
- `web/agent/install.sh` — 新增非 root 用户 sudo 兼容:`id -u` 检测 → `$SUDO` 变量 → `mkdir`/`apt-get`/`chown`/`tee`/`systemctl` 全部走 sudo;venv 创建加 `|| true` 兼容 set -e
|
||||
|
||||
### 后端
|
||||
- `server/api/servers.py` — 新增 `GET /api/servers/meta/api_base_url` 端点(返回 `settings.API_BASE_URL`)
|
||||
- `server/config.py` — DB_OVERRIDE_MAP 新增 `api_base_url` → `API_BASE_URL`;新增 `ensure_api_base_url_in_db()` 启动迁移方法
|
||||
- `server/main.py` — 启动时调用 `settings.ensure_api_base_url_in_db(session)` 迁移现有安装的 api_base_url
|
||||
- `server/api/install.py` — 安装向导 settings_kv 新增 `api_base_url`
|
||||
- `server/api/auth.py` — 修改密码详情
|
||||
- `server/api/servers.py` — 服务器创建/更新/删除/Agent操作详情
|
||||
- `server/api/scripts.py` — 凭据/脚本操作详情
|
||||
- `server/api/assets.py` — 平台/节点操作详情
|
||||
- `server/api/settings.py` — 设置/调度/预设/重试操作详情
|
||||
- `server/api/webssh.py` — WebSSH 连接/断开详情
|
||||
- `server/api/sync_v2.py` — 文件操作详情
|
||||
- `server/api/agent.py` — 长任务回调详情
|
||||
- `server/background/script_execution_flush.py` — 执行刷新详情
|
||||
- `server/application/services/auth_service.py` — 登录/TOTP/Token/WebSSH令牌详情
|
||||
- `server/application/services/script_service.py` — 脚本/凭据/执行详情
|
||||
- `server/application/services/sync_engine_v2.py` — 文件推送详情
|
||||
|
||||
### MCP
|
||||
- `mcp/Nexus_server.py` — deploy: git pull 先于 gate check + origin/main
|
||||
|
||||
## 技术细节
|
||||
- `agent_version` 由 Agent 心跳时写入(如 `"2.0.0"`),未安装 Agent 时为 `None`
|
||||
- `agent_api_key_set` 仍保留在安装命令上下文中使用(检测是否生成了 Key,是安装的前提条件)
|
||||
- `API_BASE_URL` 是全局设置(来自 `.env` 的 `NEXUS_API_BASE_URL`),不是服务器字段
|
||||
- 前端通过 `GET /api/servers/meta/api_base_url` 获取值,缓存到 `_apiBaseUrl` 变量
|
||||
- `api_base_url` 加入 `DB_OVERRIDE_MAP` 后,设置页面可查看/修改,MySQL 中的值会覆盖 `.env`
|
||||
- 启动迁移逻辑:若 MySQL settings 表无 `api_base_url` 行但有 `.env` 值,自动插入(仅一次)
|
||||
- 前端 ACTION_NAMES 映射覆盖所有已知 action 类型,未知 action 仍显示原始英文值
|
||||
- 历史数据(已存入 MySQL 的 detail)不会追溯更改,仅新操作生效
|
||||
- MCP deploy 流程:git pull → gate check → supervisorctl restart
|
||||
- install.sh sudo 兼容:`id -u == 0` 时 `SUDO=""`,否则检查 `sudo` 可用性;`$SUDO mkdir -p /opt/nexus-agent` + `$SUDO chown` 给当前用户 → 后续 venv/pip 无需 sudo;`$SUDO tee /etc/systemd/system/nexus-agent.service` + `$SUDO systemctl` 处理 systemd 操作
|
||||
- install.sh venv `set -e` 兼容:首次 `python3 -m venv` 加 `2>/dev/null || true`,允许在缺 `python3-venv` 时失败不退出;然后检查 `.venv/bin/activate` 是否存在,不存在则 `$SUDO apt-get install python3-venv` 后重试
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(新端点 + 启动迁移逻辑 + DB_OVERRIDE_MAP 变更)
|
||||
- 否:无数据库 schema 迁移(仅向 settings 表插入一行)
|
||||
|
||||
## 验证方式
|
||||
1. 选择未安装 Agent 的服务器 → "安装 Agent" 按钮可点击 ✅
|
||||
2. 选择已安装 Agent 的服务器 → "升级 Agent" 按钮可点击 ✅
|
||||
3. 审计日志页面操作列显示中文 ✅
|
||||
4. 新操作审计日志详情显示中文 ✅
|
||||
5. MCP deploy 工具流程正确(git pull 先于 gate check)✅
|
||||
6. 详情面板:有 agent_api_key 的服务器 → "远程安装"按钮可用 ✅
|
||||
7. 详情面板:无 API Key 的服务器 → 显示"请先生成 Key"警告 ✅
|
||||
8. 设置页面 → 基础设施区块显示"主站对外 URL" ✅
|
||||
9. 启动日志 → 显示 "Migrated api_base_url to MySQL settings table" ✅
|
||||
10. 非 root 用户远程安装 Agent → install.sh 自动使用 sudo,`/opt/nexus-agent` 创建成功 ✅
|
||||
11. root 用户远程安装 Agent → install.sh 无 sudo 前缀,行为不变 ✅
|
||||
12. 缺 python3-venv 的 Ubuntu → install.sh 自动 apt-get install python3-venv 后重试 ✅
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog
|
||||
@@ -0,0 +1,47 @@
|
||||
# 2026-05-27 fmtTime Invalid Date修复 + install.sh venv健壮性 + batch install错误上报
|
||||
|
||||
## 变更摘要
|
||||
1. 修复6个页面的 fmtTime() 函数对带时区后缀(+00:00)的时间戳解析为 Invalid Date
|
||||
2. install.sh 创建 venv 后检测 activate 文件,缺失时自动安装 python3-venv 重试
|
||||
3. batch install API 异常时保留 server_name、检测 stdout 中 "Status: FAILED"、stderr 为空时返回提示信息
|
||||
|
||||
## 动机
|
||||
1. API 返回的 last_heartbeat 格式为 `2026-05-26T16:22:14.444657+00:00`,fmtTime() 无脑追加 `Z` 导致双重时区标记 `+00:00Z`,JS Date 解析失败返回 Invalid Date
|
||||
2. Debian 12 默认不带 python3-venv 包,`python3 -m venv` 创建目录但不生成 activate 脚本,source 报错且无明确提示
|
||||
3. batch install API 返回 `success:false, stdout:"", error:""` 无具体错误信息,无法排查失败原因
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 前端(6个页面)
|
||||
- `web/app/servers.html` — fmtTime: 先 new Date(t),失败再 new Date(t+'Z')
|
||||
- `web/app/index.html` — 同上
|
||||
- `web/app/credentials.html` — 同上
|
||||
- `web/app/push.html` — 同上
|
||||
- `web/app/retries.html` — 同上
|
||||
- `web/app/schedules.html` — 同上
|
||||
|
||||
### Agent 安装脚本
|
||||
- `web/agent/install.sh` — venv 创建后检测 activate 文件是否存在,不存在则 apt install python3-venv 后重试
|
||||
|
||||
### 后端
|
||||
- `server/api/servers.py` — batch install _install_one(): server_name 提前赋值、检测 "Status: FAILED"、stderr 空时返回提示、超时改为180s
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:需重启 Nexus 后端(`supervisorctl restart nexus`)
|
||||
- 否:无数据库迁移
|
||||
|
||||
## 验证方式
|
||||
1. 浏览器打开 servers.html → 最后心跳列显示 "5月27日 00:52" 而非 "Invalid Date" ✅
|
||||
2. 在缺少 python3-venv 的 Debian 12 服务器运行 install.sh → 自动安装 python3-venv 后继续 ✅
|
||||
3. batch install 失败时返回具体错误信息 ✅
|
||||
|
||||
## 审计8步
|
||||
- H1: install.sh apt-get 需 root — ✅ 正常(脚本以 root 运行)
|
||||
- H2: install.sh 检测 activate 而非 -d 目录 — ✅ 修复旧 bug
|
||||
- H3: servers.py shlex.quote 防注入 — ✅ 已有
|
||||
- H4: servers.py "Status: FAILED" 字符串匹配 — ✅ 无注入风险
|
||||
- H5: fmtTime 纯前端日期解析 — ✅ 无 XSS(用 esc() 函数)
|
||||
- DoD: 无明文密码/命令注入/SQL注入/静默吞错/硬编码 ✅
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☑审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☐changelog(本文件)
|
||||
@@ -0,0 +1,61 @@
|
||||
# 2026-05-28 推送页面 5 项迭代功能
|
||||
|
||||
## 变更摘要
|
||||
1. F1: 推送进度实时追踪 — WebSocket 逐台推送进度更新
|
||||
2. F2: 服务器分组选择 — 分类/平台筛选 + 选中当前筛选
|
||||
3. F4: 推送历史增强 — 可展开详情面板(耗时/错误/diff)
|
||||
4. F7: 文件管理器操作 — 解压文件删除/重命名
|
||||
5. F8: 推送后校验 — md5sum 对比本地源与远程目标
|
||||
|
||||
## 动机
|
||||
推送页面已完成 ZIP 上传 + rsync 推送基础功能,但存在 5 个体验缺陷:批量推送无实时反馈、2000+ 服务器无法分组选择、历史记录无详情、文件管理器只读、推送后无法校验文件一致性。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/api/websocket.py` — 新增 `broadcast_sync_progress()` 函数
|
||||
- 参数: batch_id, server_id, server_name, status, completed, failed, total, error_message, duration_seconds
|
||||
- 通过 `_dispatch_ws_message()` 发布到 Redis Pub/Sub + 本地 WebSocket
|
||||
- `server/application/services/sync_engine_v2.py` — 推送引擎改造
|
||||
- `sync_files()`: 新增 `batch_id` (uuid) 用于 WS 消息过滤
|
||||
- `_sync_one()`: 计数器更新后调用 `broadcast_sync_progress()`
|
||||
- `_sync_one()`: 捕获 `diff_summary = result["stdout"][:2000]`
|
||||
- `_log_to_dict()`: 新增 files_transferred, files_skipped, bytes_transferred, diff_summary
|
||||
- 返回 dict 新增 `batch_id`
|
||||
- `server/api/servers.py` — 服务器列表 + 历史增强
|
||||
- `list_servers()`: 新增 `platform_id`, `node_id` Query 参数
|
||||
- 新增 `GET /api/servers/categories` 端点(分类+平台列表)
|
||||
- `_sync_log_to_dict()`: 新增 files_skipped, bytes_transferred, diff_summary
|
||||
- `server/infrastructure/database/server_repo.py` — `get_paginated()` 增加 `platform_id`, `node_id` 参数
|
||||
- `server/api/schemas.py` — 新增 `LocalFileOperation`, `SyncVerify` 模型
|
||||
- `server/api/sync_v2.py` — 新增端点
|
||||
- `POST /api/sync/local-file-ops`: 删除/重命名上传临时目录中的文件
|
||||
- `POST /api/sync/verify`: md5sum 对比本地源与远程目标
|
||||
|
||||
### 前端
|
||||
- `web/app/push.html` — 全面改造
|
||||
- F1: WebSocket 连接 `connectPushWS()` + `updateProgressFromWS()` 逐台更新
|
||||
- F2: 分类/平台筛选下拉框 + `filterServers()` + "选中当前筛选" 按钮
|
||||
- F4: 历史记录可点击展开详情(源/目标路径、耗时、错误、diff_summary)
|
||||
- F7: 文件管理器 hover 显示操作按钮 + `deleteLocalFile()`/`renameLocalFile()`
|
||||
- F8: "推送后校验" 复选框 + `doVerify()` + 校验结果面板
|
||||
|
||||
## 安全影响
|
||||
- F1 (WS): WS 消息需 JWT 认证 + batch_id 过滤防混淆
|
||||
- F7 (文件操作): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验,新路径也必须在上传目录内
|
||||
- F8 (校验): SSH 命令中文件名通过 `shlex.quote()` 防注入
|
||||
- 所有新端点需 JWT 认证 + 审计日志
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
||||
- 否:无数据库 schema 迁移
|
||||
|
||||
## 验证方式
|
||||
1. F1: 推送 3+ 台服务器 → 观察 WS 逐台更新状态(不等全部完成)
|
||||
2. F2: 分类筛选 → 选中筛选 → 服务器列表正确过滤
|
||||
3. F4: 点击历史记录 → 展开详情 → 显示耗时/错误/diff
|
||||
4. F7: 上传 ZIP → 文件管理器 → 删除/重命名文件 → 刷新确认
|
||||
5. F8: 勾选校验 → 推送 → 校验结果正确显示匹配/缺失
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog
|
||||
@@ -0,0 +1,57 @@
|
||||
# 2026-05-29 推送页面 Round 2 迭代功能
|
||||
|
||||
## 变更摘要
|
||||
1. F5: 同步模式详细说明 — 每种模式下方显示说明文字,切换时动态更新
|
||||
2. F2: 服务器在线状态标识 — 服务器列表选项显示🟢/🔴在线/离线标识
|
||||
3. F1: 失败自动重试 — 推送失败自动创建重试任务 + 失败行"🔄 重试"按钮
|
||||
4. F3: 推送完成 Telegram 通知 — 推送完成/部分失败/全部失败发 Telegram 消息含详情
|
||||
5. F4: 文件内容预览 — 文件管理器点击文件名弹出内容预览模态框
|
||||
|
||||
## 动机
|
||||
推送页面 Round 1 功能已上线,但存在 5 个体验/运维缺陷:同步模式含义不明、无法区分在线/离线服务器、推送失败需手动跳转重试页面、推送完成无远程通知、文件管理器无法预览文件内容确认推送正确性。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/application/services/sync_engine_v2.py` — 失败自动创建 PushRetryJob + Telegram 通知
|
||||
- `_sync_one()`: 失败时自动创建 `PushRetryJob(status="pending", max_retries=3)`
|
||||
- `sync_files()`: 完成后调用 `send_telegram_sync_complete()` 发 Telegram
|
||||
- 返回 dict 中失败 server 增加 `retry_job_id` 字段
|
||||
- `server/api/websocket.py` — `broadcast_sync_progress()` 增加 `retry_job_id` 参数
|
||||
- `server/infrastructure/telegram/__init__.py` — 新增 `send_telegram_sync_complete()` 函数
|
||||
- 参数: completed, failed, total, source_path, operator, failed_servers, duration_seconds
|
||||
- 全部成功🟢 / 部分失败🟡 / 全部失败🔴 三种消息样式
|
||||
- 失败服务器列表最多显示 10 台
|
||||
- `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview` 端点
|
||||
- 安全: `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 校验
|
||||
- 读取最多 4KB,base64 编码返回
|
||||
- UTF-8 检测: 成功则 `encoding_hint="text"`, 否则 `"binary"`
|
||||
- 超过 1MB 的文件不支持预览
|
||||
- 审计日志记录
|
||||
- `server/api/schemas.py` — 新增 `LocalFilePreview` 模型
|
||||
|
||||
### 前端
|
||||
- `web/app/push.html` — 5 项改动
|
||||
- F5: 同步模式 radio 组下方 `<div id="syncModeDesc">` 说明区域,`onSyncModeChange()` 更新
|
||||
- F2: `filterServers()` 选项文字加🟢/🔴前缀 + 离线灰色样式
|
||||
- F1: `showProgress()` 每行加 `srv_retry_{id}` 重试按钮;`updateProgressFromWS/Result()` 失败时显示;`retryServer()` 调用重试 API
|
||||
- F4: 文件名加 `cursor-pointer hover:text-brand-light` 点击预览;`previewLocalFile()` 调用 API + 模态框显示;二进制文件提示
|
||||
|
||||
## 安全影响
|
||||
- F4 (文件预览): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验,与 browse-local/local-file-ops 一致
|
||||
- F1 (自动重试): 失败后自动创建 pending 重试任务,由后台 retry_runner 执行,复用现有重试安全机制
|
||||
- F3 (Telegram): `sanitize_external_message()` 脱敏处理源路径,`html.escape()` 防 XSS
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
||||
- 否:无数据库 schema 迁移(PushRetryJob 表已存在)
|
||||
|
||||
## 验证方式
|
||||
1. F5: 切换同步模式 → 下方说明文字正确更新
|
||||
2. F2: 服务器列表 → 在线服务器显示🟢、离线显示🔴
|
||||
3. F1: 推送失败 → 失败行出现"🔄 重试"按钮 → 点击重试 → 重新推送
|
||||
4. F3: 推送完成 → Telegram 收到通知(含成功/失败数、失败服务器名)
|
||||
5. F4: 上传 ZIP → 文件管理器 → 点击文件名 → 弹出预览框显示内容
|
||||
|
||||
## 进度条
|
||||
☑实现 ☐WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog
|
||||
@@ -0,0 +1,60 @@
|
||||
# 2026-05-29 推送页面迭代 Round 2 — 5 项功能
|
||||
|
||||
## 变更摘要
|
||||
1. F1: 失败自动重试 — 推送失败自动创建重试任务,行内"🔄 重试"按钮
|
||||
2. F2: 服务器在线状态标识 — 服务器列表显示🟢/🔴在线状态
|
||||
3. F3: 推送完成 Telegram 通知 — 推送完成/部分失败发 Telegram 消息含详情
|
||||
4. F4: 文件内容预览 — 文件管理器点击文件名预览内容
|
||||
5. F5: 同步模式详细说明 — 每种模式下方显示说明文字
|
||||
|
||||
## 动机
|
||||
推送页面 Round 1 完成后,实际使用中发现:推送失败需手动切页重试、无法识别服务器在线状态、推送完成无即时通知、文件管理器无法预览内容、同步模式含义不明确。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/application/services/sync_engine_v2.py` — 推送失败自动创建 PushRetryJob + Telegram 通知
|
||||
- `_sync_one()`: 失败时创建 PushRetryJob,记录 retry_job_id
|
||||
- `sync_files()`: 完成后调用 `send_telegram_sync_complete()`
|
||||
- 返回 dict 新增 `retry_job_ids`
|
||||
- `server/api/websocket.py` — `broadcast_sync_progress()` 新增 `retry_job_id` 参数
|
||||
- `server/infrastructure/telegram/__init__.py` — 新增 `send_telegram_sync_complete()` 函数
|
||||
- 参数: completed, failed, total, source_path, operator, failed_servers, duration_seconds
|
||||
- 全部成功🟢/部分失败🟡/全部失败🔴 不同消息格式
|
||||
- `server/api/sync_v2.py` — 新增 `POST /api/sync/local-file-preview` 端点
|
||||
- 安全: `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 校验
|
||||
- 限制: 仅文件 + 最多 4KB + base64 编码 + encoding_hint
|
||||
- 审计日志
|
||||
- `server/api/schemas.py` — 新增 `LocalFilePreview` 模型
|
||||
|
||||
### 前端
|
||||
- `web/app/push.html` — 全面改造
|
||||
- F1: WS 消息处理 `updateProgressFromWS()` 失败行显示"🔄 重试"按钮 + `retryServer()` 函数
|
||||
- F1: HTTP 响应 `updateProgressFromResult()` 同样显示重试按钮 + `_retryJobMap` 映射
|
||||
- F2: `filterServers()` 服务器选项加🟢/🔴在线状态标识
|
||||
- F4: `renderFileManager()` 文件名可点击 + `previewLocalFile()` 预览函数 + 模态框
|
||||
- F5: 同步模式下方 `<div id="syncModeDesc">` 说明文字 + `onSyncModeChange()` 更新
|
||||
|
||||
### 设计/技术文档
|
||||
- `docs/design/specs/2026-05-29-push-round2-design.md` — 设计文档
|
||||
- `docs/design/plans/2026-05-29-push-round2.md` — 技术文档
|
||||
|
||||
## 安全影响
|
||||
- F1 (重试): 利用已有 `/api/retries/{id}/retry` 端点,JWT 认证 + 审计日志
|
||||
- F3 (Telegram): `source_path` 经 `sanitize_external_message()` 脱敏
|
||||
- F4 (预览): `os.path.realpath()` + `startswith("/tmp/nexus_upload_")` 双重校验 + 4KB 限制
|
||||
- 所有新端点需 JWT 认证 + 审计日志
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
||||
- 否:无数据库 schema 迁移
|
||||
|
||||
## 验证方式
|
||||
1. F5: 切换同步模式 → 下方说明文字正确更新
|
||||
2. F2: 服务器列表 → 在线🟢、离线🔴标识
|
||||
3. F1: 推送失败 → 行内"🔄 重试"按钮 → 点击重试
|
||||
4. F3: 推送完成 → Telegram 收到通知含详情
|
||||
5. F4: 上传 ZIP → 点击文件名 → 弹出预览框
|
||||
|
||||
## 进度条
|
||||
☑实现 ☐WSL验证 ☐审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☐changelog
|
||||
@@ -0,0 +1,71 @@
|
||||
# 2026-05-29 推送页面 Round 3 迭代
|
||||
|
||||
## 变更摘要
|
||||
1. G1: 推送取消 — Redis 取消标记 + 前端"取消推送"按钮 + WS cancelled 状态
|
||||
2. G2: 推送页面直接调度 — "⏰ 定时推送"按钮 + 时间选择模态框 + 调用调度 API
|
||||
3. G3: 推送模板/收藏 — localStorage 模板存储 + 下拉选择 + 保存/删除
|
||||
4. G4: 推送历史筛选分页 — 后端 page/per_page 参数 + 前端状态/服务器筛选 + 分页控件
|
||||
5. G5: 浏览器推送完成通知 — Web Notification API + title 闪烁降级
|
||||
6. G6: 拖拽上传 ZIP — 拖拽区 dragover/drop + 高亮 + click 回退
|
||||
|
||||
## 动机
|
||||
推送页面 Round 1+2 已完成 10 项功能,但仍有 6 项体验/效率缺陷:推送无法取消导致只能等超时、定时推送需跳到调度页重新配置、常用推送组合每次手动选、历史无法筛选分页、离开页面不知推送结果、文件选择器不够直观。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/api/schemas.py` — 新增 `SyncCancel` 模型 (batch_id: str, max_length=32)
|
||||
- `server/api/sync_v2.py` — 新增 `POST /api/sync/cancel` 端点
|
||||
- Redis SET `sync:cancel:{batch_id}` EX 3600
|
||||
- JWT 认证 + 审计日志
|
||||
- 返回 `{cancelled: true, batch_id: ...}`
|
||||
- `server/api/websocket.py` — `broadcast_sync_progress()` 增加 `cancelled: int = 0` 参数
|
||||
- WS 消息新增 `cancelled` 字段,前端用于正确计算进度条
|
||||
- `server/application/services/sync_engine_v2.py` — 推送取消检查
|
||||
- `_sync_one()`: 获取信号量后、rsync 前检查 `sync:cancel:{batch_id}`
|
||||
- 取消的服务器 status="cancelled",创建 SyncLog 记录
|
||||
- 新增 `cancelled` 计数器,与 `completed`/`failed` 并列
|
||||
- 正常路径 WS 广播也传递 `cancelled` 计数
|
||||
- Redis 异常不再静默吞错,改用 `logger.warning()`
|
||||
- `server/api/servers.py` — `get_all_sync_logs()` 参数改为 page/per_page + 返回 pages
|
||||
- 旧: `offset: int, limit: int` → 新: `page: int = 1, per_page: int = 20`
|
||||
- 返回增加: `page`, `per_page`, `pages` (ceil(total/per_page))
|
||||
|
||||
### 前端
|
||||
- `web/app/push.html` — 6 项改动
|
||||
- G1: 推送进度区"取消推送"按钮 + `cancelPush()` + WS cancelled 状态渲染 ⊘
|
||||
- G2: "⏰ 定时推送"按钮 + 时间选择模态框 + `doSchedulePush()`
|
||||
- G3: 模板下拉框 + `saveTemplate()`/`applyTemplate()`/`deleteTemplate()` (localStorage)
|
||||
- G4: 历史区状态筛选下拉 + 服务器ID输入 + 分页控件
|
||||
- G5: 推送完成时 `new Notification()` + 授权拒绝时 title 闪烁降级
|
||||
- G6: 拖拽上传区 dragover/drop/dragleave + 高亮边框 + click 回退
|
||||
- 安全: `esc()` 函数增加单引号转义 `'`,防止 onclick 属性 JS 注入
|
||||
|
||||
## 安全影响
|
||||
- G1 (取消): Redis key 有 1h TTL 防泄漏;batch_id 为服务器生成的 UUID hex;取消端点有 JWT 认证 + 审计
|
||||
- G2 (调度): 复用现有调度 API,已有 JWT 认证 + 审计
|
||||
- G3 (模板): localStorage 仅存路径/服务器ID,不含密码/密钥
|
||||
- G4 (分页): SQLAlchemy 参数化查询,无 SQL 注入风险
|
||||
- G5 (通知): Notification API 需用户主动授权,非强制
|
||||
- G6 (拖拽): 文件仍走 upload-zip 端点校验
|
||||
|
||||
## 审计修复
|
||||
- F1: 取消服务器计数器 → 新增 `cancelled` 计数器,WS 广播含 `cancelled` 字段,前端进度条正确计算
|
||||
- F2: 静默异常 → `except Exception: pass` 改为 `logger.warning()`
|
||||
- F3: 异常链抑制 → 移除 `from None`
|
||||
- F4: XSS 防御 → `esc()` 增加单引号转义
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
||||
- 否:无数据库 schema 迁移
|
||||
|
||||
## 验证方式
|
||||
1. G1: 推送 10 台 → 点击"取消推送" → 未启动的显示"已取消" ⊘ → 进度条正确
|
||||
2. G2: 配置推送 → 点击"⏰ 定时推送" → 选择时间 → 调度页出现任务
|
||||
3. G3: 保存模板 → 刷新 → 选择模板 → 表单自动填充
|
||||
4. G4: 历史区筛选"失败" → 仅显示失败 → 分页翻页
|
||||
5. G5: 推送完成 → 浏览器弹出通知
|
||||
6. G6: 拖拽 ZIP 到上传区 → 高亮 → 释放上传成功
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☑审计8步 ☐部署 ☐健康检查 ☐浏览器验证 ☑changelog
|
||||
@@ -0,0 +1,50 @@
|
||||
# 2026-05-29 推送页面 Round 4 迭代
|
||||
|
||||
## 变更摘要
|
||||
1. H2: 服务器搜索过滤 — 目标服务器下拉列表上方加搜索框,按名称/域名实时过滤
|
||||
2. H3: 推送对比 Diff 视图 — 预览文件列表中加"📄 diff"按钮,点击后对比本地与远程文件内容差异
|
||||
3. H4: 推送排错面板 — 失败行加"🔍 诊断"按钮,检查 SSH 连通/磁盘空间/目标路径权限/写入权限
|
||||
4. H5: 源路径直接输入 — 除 ZIP 上传外,支持直接输入服务器本地路径推送
|
||||
5. H6: 批量重试 — 推送完成后,一键重试所有失败服务器
|
||||
|
||||
## 动机
|
||||
推送页面功能已较完善,但 5 个实际使用痛点:2000+ 服务器下拉无法搜索定位、推送前无法看到文件内容差异、推送失败后需手动 SSH 排查、已有文件需先下载再上传、批量失败需逐台点重试。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/api/schemas.py` — 新增 `ValidateSourcePath`, `SyncDiagnose`, `FileSyncDiff` 三个 Schema
|
||||
- `server/api/sync_v2.py` — 新增 3 个端点
|
||||
- `POST /api/sync/validate-source-path`: 验证本地目录路径有效性(禁止 /etc /root /home 等敏感路径)
|
||||
- `POST /api/sync/diagnose`: 诊断推送失败原因(SSH 连通 → 磁盘空间 → 路径权限 → 写入测试)
|
||||
- `POST /api/sync/file-diff`: 对比本地文件与远程文件差异(difflib.unified_diff,限 100KB)
|
||||
- `docs/design/specs/2026-05-29-push-round4-design.md` — 设计文档
|
||||
- `docs/design/plans/2026-05-29-push-round4.md` — 技术文档
|
||||
|
||||
### 前端
|
||||
- `web/app/push.html` — 5 项改动
|
||||
- H2: `<input id="serverSearch">` + `filterServerOptions()` 按 oninput 实时过滤
|
||||
- H3: 预览文件列表渲染 HTML 带 diff 按钮 + `showFileDiff()` + diff 模态框(新增绿/删除红/头部青)
|
||||
- H4: 服务器行加 `srv_diag_{id}` 按钮 + `diagnoseServer()` + 诊断模态框
|
||||
- H5: "或输入源路径" 输入框 + "验证路径" 按钮 + `validateSourcePath()`
|
||||
- H6: 进度条区域"🔄 重试全部失败"按钮 + `retryAllFailed()` 批量提交重试
|
||||
|
||||
## 安全影响
|
||||
- H5 (源路径验证): `os.path.realpath()` 校验 + 禁止 `/etc` `/root` `/home` `/var` 等敏感路径前缀
|
||||
- H3 (文件对比): realpath 校验 local_file 必须在 source_path 下,source_path 必须 `startswith("/tmp/nexus_upload_")`
|
||||
- H4 (诊断): `shlex.quote()` 防注入,SSH 操作复用连接池
|
||||
- 所有新端点需 JWT 认证 + 审计日志
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启(supervisorctl restart nexus)
|
||||
- 否:无数据库 schema 迁移
|
||||
|
||||
## 验证方式
|
||||
1. H2: 输入 "web" → 下拉只显示名称/域名含 "web" 的服务器
|
||||
2. H3: 预览 → 文件列表 → 点 "📄 diff" → 显示逐行对比(新增绿/删除红)
|
||||
3. H4: 推送失败 → 点"🔍 诊断" → SSH/磁盘/权限检查结果
|
||||
4. H5: 输入 `/tmp/nexus_upload_xxx` → 验证成功 → 可推送
|
||||
5. H6: 推送完成后有失败 → 显示"重试全部失败"按钮 → 点击批量重试
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☐审计8步 ☑部署 ☐健康检查 ☐浏览器验证 ☑changelog
|
||||
@@ -0,0 +1,101 @@
|
||||
# Changelog — files.html + sync_v2.py 全面迭代
|
||||
|
||||
**日期**: 2026-05-30
|
||||
**Commit**: 14131f9
|
||||
**类型**: 安全加固 + 功能补全 + UX 全面改进
|
||||
**涉及文件**:
|
||||
- `server/api/sync_v2.py` — 符号链接解析 + 新增 3 个端点
|
||||
- `server/api/schemas.py` — 新增 FileDownload/FileRead/FileWrite schema
|
||||
- `web/app/files.html` — 前端全面重写
|
||||
|
||||
## 后端变更 (sync_v2.py)
|
||||
|
||||
### P2-1/P3-2: 符号链接解析
|
||||
- `ls -la` 解析逻辑新增 `is_link` 字段(检测 `l` 权限)
|
||||
- 符号链接文件名 `name -> target` 分离为 `name` + `link_target`
|
||||
- 响应新增 `is_link` 和 `link_target` 字段
|
||||
|
||||
### P2-7: 新增 `/api/sync/download` 端点
|
||||
- 通过 SFTP 流式下载远程文件
|
||||
- 使用 `StreamingResponse` + 64KB chunk 避免内存膨胀
|
||||
- 审计日志记录
|
||||
|
||||
### P2-8: 新增 `/api/sync/read-file` + `/write-file` 端点
|
||||
- `read-file`: `stat` 检查大小 → `cat` 读取内容,限制 1MB
|
||||
- `write-file`: base64 编码内容避免 shell 注入,`echo | base64 -d | tee` 写入
|
||||
- 两个端点均有审计日志
|
||||
|
||||
## 前端全面重写 (files.html)
|
||||
|
||||
### P1-1: 目录删除二次确认
|
||||
- 目录删除:自定义模态框 + 要求输入目录名确认
|
||||
- 文件删除:自定义确认模态框
|
||||
|
||||
### P2-2/P2-3: 事件委托
|
||||
- 移除所有内联 `onclick="..."` 字符串拼接
|
||||
- 改用 `data-action` + `data-path` + `data-name` 属性
|
||||
- 统一事件委托 `fileListEl.addEventListener('click', ...)`
|
||||
- 彻底消除 XSS 向量
|
||||
|
||||
### P2-4: 上传进度条
|
||||
- `XMLHttpRequest.upload.onprogress` 实时进度
|
||||
- 顶部进度条显示当前文件名 + 百分比 + 进度条动画
|
||||
|
||||
### P2-5: 文件大小可读化
|
||||
- `formatSize()` 函数:字节 → B/KB/MB/GB
|
||||
- 保留 1 位小数
|
||||
|
||||
### P2-6: 文件类型图标
|
||||
- 30+ 文件扩展名映射
|
||||
- `.py`→🐍 `.jpg`→🖼️ `.tar.gz`→📦 `.log`→📊 `.env`→🔒 等
|
||||
|
||||
### P2-7: 文件下载
|
||||
- 每行 hover 显示 ⬇ 下载按钮
|
||||
- 点击触发 SFTP 下载 → 浏览器保存文件
|
||||
|
||||
### P2-8: 文件预览/编辑
|
||||
- 每行 hover 显示 👁 预览按钮
|
||||
- 点击读取文件内容 → 模态框显示 + textarea 编辑 + 保存
|
||||
|
||||
### P2-9: 自定义模态框
|
||||
- 完全替代原生 `prompt()` / `confirm()`
|
||||
- Tailwind 样式:暗色主题 + backdrop blur + 圆角
|
||||
- 支持:文本提示 / 输入框 / 多行编辑 / 危险操作红色样式
|
||||
|
||||
### P2-10: 批量操作
|
||||
- 每行开头 checkbox + 表头全选
|
||||
- 选中时底部出现批量操作栏(计数 + 批量删除 + 取消)
|
||||
|
||||
### P3-1: 文件搜索
|
||||
- 面包屑右侧搜索框
|
||||
- 前端实时过滤文件名
|
||||
|
||||
### P3-2: 符号链接显示
|
||||
- 🔗 图标 + cyan 颜色
|
||||
- hover 显示 `→ target` tooltip
|
||||
|
||||
### P3-3: 空目录状态
|
||||
- 📂 大图标 + 提示文字 + 「上传文件」引导按钮
|
||||
|
||||
### P3-4: 服务器刷新
|
||||
- 🔄 按钮在服务器下拉旁,点击重新加载列表
|
||||
|
||||
### P3-5: 操作 loading
|
||||
- 上传期间顶部进度条持续显示
|
||||
- 浏览期间文件列表显示 ⏳ 加载动画
|
||||
|
||||
## 验证结果
|
||||
|
||||
| 测试 | 结果 |
|
||||
|------|------|
|
||||
| 健康检查 | ✅ ok |
|
||||
| 页面加载 | ✅ 0 errors |
|
||||
| 浏览 /tmp | ✅ 文件列表+图标+大小正确 |
|
||||
| 符号链接 mysql.sock | ✅ 🔗 图标 |
|
||||
| 面包屑 | ✅ /tmp 路径 |
|
||||
| 搜索框 | ✅ 存在 |
|
||||
| 全选 checkbox | ✅ 存在 |
|
||||
| 操作按钮 | ✅ 👁 ⬏ ✏ 🗑 |
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重启后端服务(新增 3 个 API 端点)
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog: 2026-05-30 — Heartbeat 422 修复 + Agent 弃用升级
|
||||
|
||||
## 变更摘要
|
||||
1. 修复未注册/旧版 Agent 发送无 server_id 心跳导致 422 日志噪音问题
|
||||
2. 将 web/agent/agent.py 的已弃用 `on_event("startup")` 迁移到 `lifespan`
|
||||
3. Agent 心跳添加指数退避 + discarded 状态自动停止
|
||||
4. main.py 临时 RequestValidationError handler 升级为正式版本
|
||||
5. 卸载脚本增强对旧版 MultiSync Agent 的清理(crontab/legacy 路径)
|
||||
6. 确认 servers.html 批量操作按钮正常工作(disabled 为正确行为)
|
||||
|
||||
## 动机
|
||||
- 47.121.118.30 旧版 Agent 持续发无 server_id 心跳,导致 422 日志刷屏
|
||||
- 旧 Agent 收到 422 后不断重试,形成噪音循环
|
||||
- FastAPI on_event("startup") 已弃用,需迁移到 lifespan
|
||||
- Agent 心跳失败无退避,网络抖动时加重服务器负担
|
||||
- 卸载脚本未覆盖旧版 MultiSync Agent(非 systemd、非标准路径)
|
||||
|
||||
## 涉及文件
|
||||
- `server/api/schemas.py` — AgentHeartbeat.server_id 改为 Optional[int]
|
||||
- `server/api/agent.py` — heartbeat handler 处理 server_id=None(返回 200 + discarded)
|
||||
- `server/main.py` — 移除临时 Debug 注释,正式化 RequestValidationError handler
|
||||
- `web/agent/agent.py` — on_event → lifespan + 指数退避 + discarded 停止
|
||||
- `web/agent/uninstall.sh` — 增加 legacy Agent 进程杀除 + crontab 清理 + 旧路径删除
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重启 Nexus 后端(agent.py + schemas.py 变更)
|
||||
- 需要重新部署 Agent 脚本到子服务器(下次安装/升级时自动更新)
|
||||
|
||||
## 验证方式
|
||||
- `python -c "import server.main"` 通过
|
||||
- `ruff check server/api/agent.py server/api/schemas.py server/main.py` 通过
|
||||
- `ast.parse(agent.py)` 通过
|
||||
- 47.121.118.30 旧 Agent 发心跳 → 返回 200 discarded → 日志 warning 一次,不再循环
|
||||
- 新 Agent 心跳失败 → 指数退避(60s → 120s → 240s → 300s cap)
|
||||
- 新 Agent 收到 discarded → 停止心跳循环 + error 日志
|
||||
@@ -0,0 +1,61 @@
|
||||
# Changelog — Monaco 编辑器 + IDE 重构 + 文件管理增强
|
||||
|
||||
**日期**: 2026-05-30
|
||||
**Commit**: e8c1acb (含 15+ commits)
|
||||
**类型**: 功能增强 + 安全修复
|
||||
**涉及文件**:
|
||||
- `web/app/files.html` — IDE 编辑器全面重构
|
||||
- `web/app/api.js` — Monaco 预加载(后移入 files.html)
|
||||
- `web/app/terminal.html` — path 参数自动 cd
|
||||
- `web/app/servers.html` — 「文件」按钮
|
||||
- `server/api/sync_v2.py` — chmod/compress/decompress 端点
|
||||
- `server/api/schemas.py` — FileChmod/FileCompress/FileDecompress schema
|
||||
|
||||
## Monaco 编辑器
|
||||
|
||||
- 全局预加载:页面加载后 CDN 后台下载 Monaco (~2MB)
|
||||
- IDE 面板:全屏替代模态框,支持多文件标签页
|
||||
- 每个标签独立 Monaco 实例,切换保持状态
|
||||
- 语法高亮:30+ 语言自动检测(conf→ini, sh→shell, py→python 等)
|
||||
- Ctrl+S 保存 + 未保存指示器
|
||||
- 状态栏:光标位置 + 语言 + 文件路径
|
||||
|
||||
## 文件管理增强
|
||||
|
||||
- 右键菜单:预览/下载/重命名/权限/复制路径/终端/压缩/解压/删除
|
||||
- 文件树:IDE 左侧面板,点击文件打开/目录导航
|
||||
- 最小化/恢复:底部标签栏
|
||||
- 新建文件按钮(📄+)
|
||||
- chmod 权限修改
|
||||
- 压缩 tar.gz / 解压 tar.gz+zip
|
||||
- 排序切换(名称/大小/时间)
|
||||
- 文件类型筛选(.conf/.log/.sh/.py/.json/.yml/.txt)
|
||||
- 文件预加载 + 缓存(≤1MB 文本文件,双击秒开)
|
||||
|
||||
## SSH 终端集成
|
||||
|
||||
- 工具栏「🖥 终端」按钮
|
||||
- 新标签页打开 SSH + 自动 cd 到当前目录
|
||||
- terminal.html 支持 path URL 参数
|
||||
|
||||
## 服务器列表
|
||||
|
||||
- 每行操作栏新增「文件」按钮(琥珀色)
|
||||
- 点击直达文件管理,自动选中服务器
|
||||
|
||||
## 安全修复(审计 FINDING)
|
||||
|
||||
- H-01: esc() 加引号转义防 onclick XSS
|
||||
- H-02: initialPath 路径白名单防命令注入
|
||||
- H-03: 新建文件文件名白名单
|
||||
- H-13: 移除 showModal 中已删除的 textarea 引用
|
||||
- H-20: initialPath 仅首次连接 cd
|
||||
- H-26: esc() 跨文件一致性
|
||||
|
||||
## 已知限制
|
||||
|
||||
- Monaco CDN 无 SRI(版本锁定 @0.45.0 降低风险)
|
||||
- Nginx 静态文件缓存可能导致更新延迟(需 nginx -s reload)
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重启后端服务(新增 chmod/compress/decompress 端点)
|
||||
@@ -0,0 +1,25 @@
|
||||
# 2026-05-30 Nginx 静态文件路径重定向 + 部署文档更新
|
||||
|
||||
## 背景
|
||||
FastAPI 静态文件挂在 `/app/` 路径下,访问不带 `/app/` 前缀的 URL(如 `/login.html`)时,FastAPI 返回 JSON `{"detail":"Not Found"}`,浏览器直接渲染白屏显示该错误。
|
||||
|
||||
## 变更内容
|
||||
|
||||
### 1. Nginx 配置 (远程)
|
||||
在 `location /` 前插入重定向规则,15 个页面路径自动 301 到 `/app/` 下:
|
||||
```nginx
|
||||
location ~ ^/(login|index|servers|files|push|scripts|script-executions|credentials|schedules|retries|commands|alerts|audit|settings|install|terminal|assets)\.html$ {
|
||||
return 301 /app/$1.html;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 部署模板更新
|
||||
`deploy/nginx_https.conf` 新增注释块,提示反代部署时取消注释该重定向规则。
|
||||
|
||||
## 涉及文件
|
||||
- `deploy/nginx_https.conf` — 模板注释
|
||||
- `/www/server/panel/vhost/nginx/api.synaglobal.vip.conf` — 实际生效
|
||||
|
||||
## 验证
|
||||
- `nginx -t` 通过
|
||||
- 浏览器访问 `/login.html` → 301 → `/app/login.html`,不再显示 JSON 错误
|
||||
@@ -0,0 +1,65 @@
|
||||
# 2026-05-30 — 全站 ruff 清零
|
||||
|
||||
## 日期
|
||||
2026-05-30
|
||||
|
||||
## 变更摘要
|
||||
全站 ruff 代码质量扫描从 77 个错误清零到 0,同时修复 3 个实际 bug。
|
||||
|
||||
## 动机
|
||||
全站巡检发现 server/ 目录有 77 个 ruff 错误,其中 6 个 F821(未定义变量)会导致运行时 NameError。
|
||||
|
||||
## 涉及文件(21 个)
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| server/api/install.py | bug fix | site_url 变量在定义前使用 → 移到函数开头;移除未使用 hashlib 导入 |
|
||||
| server/api/sync_v2.py | bug fix | 缺少 `import os` 导致对比功能 NameError |
|
||||
| server/application/services/script_jobs.py | bug fix | `${LOG}` 在 f-string 中触发 Python 变量查找 → 改为普通字符串 |
|
||||
| server/api/auth.py | 质量 | 移除未使用 ip_address 变量 |
|
||||
| server/api/scripts.py | 质量 | 移除未使用导入 |
|
||||
| server/api/settings.py | 质量 | 5 个 B904 raise from 修复 |
|
||||
| server/api/websocket.py | 质量 | S110 noqa |
|
||||
| server/api/webssh.py | 质量 | 10 个 S110 noqa (SSH 清理) |
|
||||
| server/application/services/auth_service.py | 质量 | S105 noqa (Redis key prefix) |
|
||||
| server/application/services/sync_engine_v2.py | 质量 | 移除未使用导入 |
|
||||
| server/background/heartbeat_flush.py | 质量 | 移除未使用导入 |
|
||||
| server/background/schedule_runner.py | 质量 | F541 f-string 修正 |
|
||||
| server/background/script_execution_flush.py | 质量 | B023 循环变量绑定修复 |
|
||||
| server/background/self_monitor.py | 质量 | F541 f-string 修正 |
|
||||
| server/domain/models/__init__.py | 质量 | 移除 4 个未使用导入 |
|
||||
| server/domain/repositories/__init__.py | 质量 | 移除未使用 Dict 导入 |
|
||||
| server/infrastructure/database/admin_repo.py | 质量 | 移除未使用 List 导入 |
|
||||
| server/infrastructure/database/migrations.py | 质量 | 移除未使用导入 |
|
||||
| server/infrastructure/redis/client.py | 质量 | B904 raise from |
|
||||
| server/infrastructure/ssh/asyncssh_pool.py | 质量 | S110 noqa + B007 变量重命名 |
|
||||
| server/infrastructure/subscription_parser.py | 质量 | S110 noqa |
|
||||
|
||||
## 关键 Bug 修复
|
||||
|
||||
### 1. install.py site_url NameError
|
||||
- **问题**: `site_url` 在第 472 行使用但第 499 行才定义 → 安装向导初始化数据库会崩溃
|
||||
- **修复**: 将 `site_url` 计算移到函数开头(redis_url 之后)
|
||||
|
||||
### 2. sync_v2.py os 未导入
|
||||
- **问题**: 对比功能(文件 diff)使用 `os.path` 但未导入 `os`
|
||||
- **修复**: 在函数内添加 `import os`
|
||||
|
||||
### 3. script_jobs.py f-string 陷阱
|
||||
- **问题**: `f'echo $! > "${LOG}.pid"'` 中 `${LOG}` 被 Python 解释为变量查找
|
||||
- **修复**: 改为普通字符串 `'echo $! > "${LOG}.pid"'`
|
||||
|
||||
## 是否需迁移
|
||||
否
|
||||
|
||||
## 是否需重启
|
||||
是 — 已部署
|
||||
|
||||
## 验证方式
|
||||
- ruff: ✅ All checks passed (77 → 0)
|
||||
- bandit: ✅ 0 HIGH, 15 MEDIUM (B108+B104 低风险)
|
||||
- import: ✅
|
||||
- 健康检查: ✅ ok
|
||||
|
||||
## Commit
|
||||
32feb1b
|
||||
@@ -0,0 +1,37 @@
|
||||
# 2026-05-30 — 安全加固:Health 纯文本 + sha256 替换 + 未登录页面空白 HTML
|
||||
|
||||
## 背景
|
||||
1. `/health` 端点返回 `{"status":"ok"}` JSON,curl 看到 FastAPI JSON 指纹
|
||||
2. bandit 扫描 sync_v2.py 命中 `hashlib.md5()` → CWE-327 HIGH
|
||||
3. 未登录用户访问 `/app/` 页面,HTML 内容完整返回,虽前端无数据但暴露系统存在
|
||||
|
||||
## 变更内容
|
||||
|
||||
### 1. Health 端点纯文本(`server/api/health.py`)
|
||||
- `@router.get("/health", response_class=PlainTextResponse)` 返回纯文本 `"ok"`
|
||||
- 移除 `{"status":"ok"}` JSON 格式,curl 只看到 `ok`
|
||||
- `/health/detail` 仍为 JSON,但需要 JWT 认证
|
||||
|
||||
### 2. 文件校验 sha256(`server/api/sync_v2.py`)
|
||||
- `hashlib.md5()` → `hashlib.sha256()`
|
||||
- 远程 SSH 命令 `md5sum` → `sha256sum`
|
||||
- 消除 bandit CWE-327(弱哈希算法)
|
||||
|
||||
### 3. AppAuth 中间件(`server/main.py` + `server/api/auth_jwt.py`)
|
||||
- 新增 `AppAuthMiddleware`:纯 ASGI 中间件,拦截 `/app/` 路径
|
||||
- 读取 `nexus_refresh` HttpOnly cookie,验证 JWT
|
||||
- 无有效 token → 返回空白 HTML 404(不暴露系统存在)
|
||||
- 白名单:`/app/login.html`、`/app/install.html`、`/app/vendor/` 仍可访问
|
||||
|
||||
## 涉及文件
|
||||
- `server/api/health.py` — 67 行(8 行改动)
|
||||
- `server/api/sync_v2.py` — 1045 行(10 行改动)
|
||||
- `server/main.py` — 472 行(新增 AppAuthMiddleware)
|
||||
- `server/api/auth_jwt.py` — 272 行(提取 _verify_token_from_cookie)
|
||||
- `docs/audit/2026-05-30-deploy-health-fix.md` — 审计记录
|
||||
|
||||
## 验证
|
||||
- `curl https://api.synaglobal.vip/health` → `ok`(纯文本)
|
||||
- `bandit -r server/ -ll` → 0 HIGH
|
||||
- 未登录 curl `/app/index.html` → `404` 空白 HTML
|
||||
- 已登录浏览器 → 页面正常
|
||||
@@ -0,0 +1,56 @@
|
||||
# 2026-05-30 — 服务器列表迭代
|
||||
|
||||
## 日期
|
||||
2026-05-30
|
||||
|
||||
## 变更摘要
|
||||
服务器列表页面全面迭代:后端新增服务端分页/搜索/排序/状态筛选,前端完全重写支持新功能。
|
||||
|
||||
## 动机
|
||||
原服务器列表一次性加载全部数据,2000+ 服务器场景下性能差且无搜索/排序能力。
|
||||
|
||||
## 涉及文件
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| server/infrastructure/database/server_repo.py | 修改 | `get_paginated()` 新增 search/sort_by/sort_order/is_online 参数,白名单排序,DB 级分页 |
|
||||
| server/api/servers.py | 修改 | `list_servers()` 新增 4 个 Query 参数 + Redis post-filter;修复 12 个预存 ruff 错误;修复 `agent_port` F821 bug |
|
||||
| web/app/servers.html | 重写 | 服务端分页/搜索/排序/状态筛选/自动刷新/键盘快捷键/CSV 导出/骨架屏/空状态优化 |
|
||||
|
||||
## 变更详情
|
||||
|
||||
### 后端 — server_repo.py
|
||||
- `get_paginated()` 签名扩展:新增 `search`(name/domain LIKE)、`sort_by`(白名单验证)、`sort_order`(asc/desc)、`is_online`(MySQL 预过滤)
|
||||
- `_SORT_COLUMNS` 白名单:id/name/domain/status/heartbeat/agent_version/category
|
||||
- `is_online` 过滤时 over-fetch +10 补偿 Redis overlay 修正
|
||||
|
||||
### 后端 — servers.py
|
||||
- `list_servers()` 新增 Query 参数:search/sort_by/sort_order/is_online
|
||||
- Redis post-filter:`is_online` 过滤时移除 Redis 状态不匹配的记录
|
||||
- 修复 12 个预先存在的 ruff 错误:F401 unused imports、B904 raise from、S110 try-except-pass、F541 f-string
|
||||
- 修复 F821 bug:`upgrade_agent()` 中 `agent_port` 变量未定义
|
||||
|
||||
### 前端 — servers.html
|
||||
- 服务端分页:50/100/200 条/页,首页/上一页/数字页码/下一页/末页
|
||||
- 防抖搜索(300ms):名称/地址 LIKE 模糊搜索
|
||||
- 可点击列排序:Agent 状态/名称/地址/分类/Agent/最后心跳,▲▼ 指示器
|
||||
- 状态筛选下拉:全部/在线/离线
|
||||
- 自动刷新:30 秒间隔,弹窗编辑时自动暂停
|
||||
- 键盘快捷键:↑↓/jk 移动、Esc 关闭面板、/ 搜索、r 刷新
|
||||
- CSV 导出:当前筛选条件下导出最多 5000 条
|
||||
- 骨架屏加载:8 行脉冲动画替代"加载中..."
|
||||
- 空状态优化:区分"无数据"和"无匹配结果"
|
||||
|
||||
## 是否需迁移
|
||||
否
|
||||
|
||||
## 是否需重启
|
||||
是 — supervisorctl restart nexus
|
||||
|
||||
## 验证方式
|
||||
1. ruff check: ✅ All checks passed
|
||||
2. bandit: ✅ No issues
|
||||
3. import: ✅ 成功
|
||||
4. Playwright 浏览器验证:✅ 搜索/排序/筛选/分页全部通过
|
||||
|
||||
## Commit
|
||||
84282e8
|
||||
@@ -0,0 +1,55 @@
|
||||
# Changelog — Settings 页面巡检 + Telegram Bot Token 脱敏
|
||||
|
||||
**日期**: 2026-05-30
|
||||
**类型**: 安全修复 + 功能验证
|
||||
**涉及文件**:
|
||||
- `server/api/settings.py` — SENSITIVE_KEYS 加入 telegram_bot_token + reveal-token 端点
|
||||
- `web/app/settings.html` — Telegram 区域独立渲染 + Bot Token 脱敏 UI
|
||||
|
||||
## 变更摘要
|
||||
|
||||
### 1. Telegram Bot Token 脱敏 (安全修复)
|
||||
**问题**: GET /api/settings/ 以明文返回 Bot Token `8743165040:AAH_YGffcj2mdqSAx1sHXjspvUFzJ_kVC2I`,违反安全铁律(API 响应不得返回敏感凭据明文)。
|
||||
|
||||
**修复**:
|
||||
- 后端 `SENSITIVE_KEYS` 新增 `telegram_bot_token`,GET 响应返回脱敏值 `87431650...`
|
||||
- 新增 `POST /api/settings/telegram/reveal-token` 端点(密码验证 + 审计日志)
|
||||
- 前端 Telegram 区域从通用 SECTIONS 循环中分离,单独渲染:
|
||||
- Bot Token 默认脱敏显示 + 「显示」按钮(需密码验证)
|
||||
- 验证后切换为 password 类型输入框 + 显示/隐藏切换
|
||||
- Chat ID 保持正常编辑(非敏感信息)
|
||||
|
||||
### 2. Settings 页面功能巡检结果
|
||||
| 功能 | 状态 | 备注 |
|
||||
|------|------|------|
|
||||
| 品牌设置保存 | ✅ 正常 | PUT system_name → 200 |
|
||||
| 告警阈值保存 | ✅ 正常 | PUT cpu_alert_threshold → 200 + 热更新 |
|
||||
| 通知 toggle 开关 | ✅ 正常 | 乐观 UI 更新 + API 同步 |
|
||||
| 手动 IP 添加/删除 | ✅ 正常 | POST + DELETE → 200 |
|
||||
| IP 白名单开关 | ✅ 正常 | confirm 弹窗 + toggle |
|
||||
| 订阅地址保存 | ✅ 正常 | 触发后台刷新 |
|
||||
| Telegram Chat ID 检测 | ✅ 正常 | getUpdates → 2 个对话 |
|
||||
| 不可变 Key 拒绝 | ✅ 正常 | PUT api_key → 403 |
|
||||
| TOTP 状态显示 | ✅ 正常 | 已启用卡片 + 重新绑定/禁用按钮 |
|
||||
| Bot Token 脱敏 | ✅ 正常 | 87431650... + 显示按钮 |
|
||||
| API Key 脱敏+复制 | ✅ 正常 | de3a155d... + 显示/复制 |
|
||||
| 订阅 IP 显示 | ✅ 正常 | 6 个节点 IP |
|
||||
| 手动 IP 空状态 | ✅ 正常 | 显示「(空)」 |
|
||||
| 告警通知 8 项 toggle | ✅ 正常 | 全部可切换 |
|
||||
|
||||
### 3. 已排除的假问题
|
||||
- **heartbeat_timeout 映射**: 已存在于 DB_OVERRIDE_MAP (config.py:115),Task 2 关闭
|
||||
- **手动 IP 与订阅 IP 重叠**: 旧页面缓存(ip-allowlist 路由修复前),刷新后正常
|
||||
- **「等待首次刷新...」**: 服务重启后时序问题,refresh 完成后刷新页面正常显示
|
||||
|
||||
## 动机
|
||||
安全审计发现 Telegram Bot Token 明文暴露,需要与 API_KEY/SECRET_KEY 同等级别保护。
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重启后端服务(新增 API 端点 + SENSITIVE_KEYS 变更)
|
||||
|
||||
## 验证方式
|
||||
1. `GET /api/settings/` → telegram_bot_token 值为 `87431650...`
|
||||
2. `POST /api/settings/telegram/reveal-token` + 密码 → 返回完整 token
|
||||
3. 前端 settings 页面 → Bot Token 显示脱敏 + 「显示」按钮可正常揭示
|
||||
4. ruff check → 0 errors
|
||||
@@ -0,0 +1,83 @@
|
||||
# Changelog — Settings 安全加固 + UX 迭代
|
||||
|
||||
**日期**: 2026-05-30
|
||||
**Commit**: a50f179
|
||||
**类型**: 安全修复 + UX 改进
|
||||
**涉及文件**:
|
||||
- `server/api/settings.py` — SSRF 防护 + key 白名单 + 值校验 + IP 审计
|
||||
- `server/api/auth.py` — 密码修改要求 TOTP
|
||||
- `server/application/services/auth_service.py` — _verify_totp staticmethod
|
||||
- `web/app/settings.html` — UX 全面改进 + 密码修改重定向
|
||||
- `web/app/login.html` — 密码修改后提示
|
||||
|
||||
## 安全修复 (5 项)
|
||||
|
||||
### S-01 SSRF 防护 (P0)
|
||||
`POST /api/settings/ip-allowlist/parse-subscription` 可被利用探测内网。
|
||||
- 新增 `_is_private_host()` 函数:IP 直检 + DNS 解析后二次校验
|
||||
- 封禁:loopback/link-local/private/reserved IP 段
|
||||
- `follow_redirects=False`,手动跟随最多 3 次重定向,每次检查目标
|
||||
- 响应大小限制 1MB
|
||||
|
||||
### S-02 设置值校验 (P0)
|
||||
`PUT /api/settings/{key}` 接受任意值。
|
||||
- 新增 `MUTABLE_KEYS` 白名单(27 个已知 key),未知 key → 403
|
||||
- 新增 `SETTING_VALIDATORS`:6 个 INT 设置项的类型+范围校验
|
||||
- db_pool_size/db_max_overflow: 1-1000
|
||||
- heartbeat_timeout: 10-600
|
||||
- cpu/mem/disk_alert_threshold: 1-100
|
||||
- 非整数值 → 400 "必须是整数",范围越界 → 400 "范围: X-Y"
|
||||
|
||||
### S-05 改密码后重登录 (P1)
|
||||
密码修改成功后前端仅清空表单,用户留在 stale JWT 页面。
|
||||
- 成功后跳转 `/app/login.html?msg=password_changed`
|
||||
- login.html 显示绿色提示 "密码已修改,请重新登录"
|
||||
|
||||
### S-07 IP 操作审计 (P1)
|
||||
`add_manual_ips` 和 `remove_allowlist_ip` 缺少审计日志。
|
||||
- 两个端点添加 `request: Request` 参数
|
||||
- 添加标准 AuditLog 写入(action/detail/ip_address)
|
||||
|
||||
### S-09 改密码要求 TOTP (P1)
|
||||
已启用 TOTP 的管理员改密码只需密码,可绕过 TOTP 保护。
|
||||
- `ChangePasswordRequest` 新增 `totp_code: Optional[str]`
|
||||
- `change_password` handler 检查:TOTP 已启用 + 未提供 code → 400
|
||||
- `AuthService._verify_totp` 改为 `@staticmethod` 供 auth.py 调用
|
||||
|
||||
## UX 改进 (5 项)
|
||||
|
||||
### UX-01 输入框样式+类型
|
||||
- 密码输入框添加 `focus:outline-none focus:ring-2 focus:ring-brand`
|
||||
- 设置项 input type 映射:数值→`type="number" min="1"`,URL→`type="url"`
|
||||
- 所有动态渲染 input 统一添加 focus ring
|
||||
|
||||
### UX-02 改密码 loading 状态
|
||||
- 按钮点击后 `disabled=true + textContent='提交中...'`
|
||||
- 请求完成 `finally` 块恢复
|
||||
|
||||
### UX-03 加载失败 toast
|
||||
- `loadSettings` / `loadTotpStatus` / `loadAllowlist` 的空 catch 改为 `console.error + toast`
|
||||
|
||||
### UX-04 保存失败回滚
|
||||
- `saveSetting()` 失败时调用 `loadSettings()` 恢复原值
|
||||
|
||||
### UX-05 IP 格式校验
|
||||
- 前端正则校验:IP (`x.x.x.x`) / CIDR (`x.x.x.x/n`) / 域名
|
||||
- 后端 `IpAllowlistRequest` Pydantic `model_validator` 校验
|
||||
|
||||
## 验证结果
|
||||
|
||||
| 测试 | 结果 |
|
||||
|------|------|
|
||||
| SSRF localhost | ✅ 400 "不允许访问内网地址" |
|
||||
| SSRF metadata | ✅ 400 "不允许访问内网地址" |
|
||||
| 未知 key | ✅ 403 |
|
||||
| INT 范围 99999 | ✅ 400 "范围: 1-1000" |
|
||||
| INT 类型 "abc" | ✅ 400 "必须是整数" |
|
||||
| 有效值 75 | ✅ 200 |
|
||||
| IP 审计日志 | ✅ "添加 1 条手动 IP: 192.0.2.1" |
|
||||
| TOTP schema | ✅ totp_code Optional 字段 |
|
||||
| Input types | ✅ number/url/text 正确映射 |
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重启后端服务(新增白名单+校验+SSRF 逻辑)
|
||||
@@ -0,0 +1,61 @@
|
||||
# 2026-05-30 终端页面全覆盖迭代
|
||||
|
||||
## 变更摘要
|
||||
terminal.html 全面重构,新增 11 项运维效率特性:
|
||||
|
||||
1. **多标签会话**: 浏览器内多标签管理,每标签独立 xterm + WebSocket,支持新建/切换/关闭,最多 10 标签。恢复时自动重建上次会话
|
||||
2. **快速命令栏**: 右侧面板内置 5 条命令 + 自定义命令(名称+内容),localStorage 持久化。点击发送到终端。弹窗编辑/删除
|
||||
3. **字体调节**: 工具栏 A−/A+ 按钮 + Ctrl+/- 快捷键,范围 10-24,localStorage 持久化
|
||||
4. **Ctrl+L 清屏**: 发送 clear\r + term.clear() 双重保障
|
||||
5. **命令历史持久化**: sendCmd 后存 localStorage(最多 200 条),刷新恢复,ArrowUp/Down 浏览
|
||||
6. **右键菜单**: 自定义右键菜单(复制/粘贴/清屏/全选)
|
||||
7. **断开自动重连**: WebSocket 异常断开指数退避重连(1s→2s→4s→max30s),倒计时显示。手动断开不重连
|
||||
8. **连接信息栏**: 工具栏显示服务器名 + 在线状态 + 连接时长(HH:MM:SS) + PING RTT
|
||||
9. **终端标题路径**: 后端注入 PROMPT_COMMAND(OSC title),前端 onTitleChange 捕获显示
|
||||
10. **滚动缓冲区调节**: 下拉选择器 1K/5K/10K/50K 行,xterm 5.x 热改
|
||||
11. **快速切换服务器**: 右侧面板搜索框实时过滤
|
||||
|
||||
## 动机
|
||||
terminal.html 仅具备基础 WebSSH,缺乏频繁运维所需的效率特性。用户一次连接内需切换多台服务器、快速执行常用命令、持久化历史等。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 后端
|
||||
- `server/api/webssh.py` — 轻量改动
|
||||
- 新增 `MSG_PING`/`MSG_PONG` 消息类型
|
||||
- shell 创建后注入 `PROMPT_COMMAND`(OSC title escape)用于路径跟踪
|
||||
- PING handler: 收到 PING 立即回复 PONG(RTT 测量)
|
||||
|
||||
### 前端
|
||||
- `web/app/terminal.html` — 全面重构(~500行新增)
|
||||
- 多标签架构: sessions[] 数组 + 动态 term div
|
||||
- localStorage: 字体/回滚/历史/命令/标签 5 个键
|
||||
- 全新 UI: TabBar + 工具栏(字体/回滚/全屏) + 右侧面板(搜索+快速命令)
|
||||
- ContextMenu: 浮层右键菜单
|
||||
- AutoReconnect: 指数退避定时器
|
||||
|
||||
## 安全影响
|
||||
- PING/PONG: 无关安全的回声确认
|
||||
- PROMPT_COMMAND: 后端注入固定字符串,不走用户输入
|
||||
- 快速命令: 已有 WebSocket DATA 通道,无新攻击面
|
||||
- 右键菜单: 纯前端 UI,无后端交互
|
||||
- 多标签: 每标签独立 WebSSH token 认证,不共享凭据
|
||||
|
||||
## 需要迁移/重启
|
||||
- 是:后端 Python 需重启
|
||||
- 否:无数据库迁移
|
||||
|
||||
## 验证方式
|
||||
1. 多标签: 新建标签 → 切换 → 关闭 → 每标签独立 WS
|
||||
2. 快速命令: 点击 → 终端执行
|
||||
3. 字体: Ctrl+/− 调节,刷新保留
|
||||
4. Ctrl+L: 清屏 + sterm clear
|
||||
5. 历史: ArrowUp/Down,刷新保留
|
||||
6. 右键: 复制/粘贴/清屏/全选
|
||||
7. 重连: 异常断开 → 倒计时 → 自动重连
|
||||
8. 连接信息: 时长 + PING 显示
|
||||
9. 路径: cd 后工具栏更新路径
|
||||
10. 回滚: 下拉切换生效
|
||||
|
||||
## 进度条
|
||||
☑实现 ☑WSL验证 ☐审计8步 ☑部署 ☑健康检查 ☑浏览器验证 ☑changelog
|
||||
@@ -0,0 +1,80 @@
|
||||
# Changelog: TerminalPage全面迭代 + 快捷命令MySQL持久化 + 登录页调整
|
||||
|
||||
**日期**: 2026-05-31
|
||||
**变更摘要**: TerminalPage 25项问题修复、快捷命令从localStorage迁移到MySQL、命令输入栏Shell语法高亮、登录页居中+删Logo
|
||||
|
||||
## 动机
|
||||
|
||||
TerminalPage.vue 是 WebSSH 终端的核心页面,代码审查发现 25 个问题(6 P0 + 6 P1 + 13 P2),包括功能回退(Ctrl+L清屏缺失、命令历史不恢复)、重连逻辑多标签冲突、快捷命令仅存localStorage无法跨设备同步。用户还要求Shell语法高亮和快捷命令MySQL持久化。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `server/domain/models/__init__.py` | 新增 | QuickCommand表(quick_commands) |
|
||||
| `server/api/terminal.py` | 新建 | 快捷命令CRUD API(4端点) |
|
||||
| `server/main.py` | 修改 | 注册terminal_router + seed内置命令 |
|
||||
| `frontend/src/pages/TerminalPage.vue` | 重写 | 25项修复 + Shell语法高亮 + MySQL API对接 |
|
||||
| `frontend/src/pages/LoginPage.vue` | 修改 | 居中布局(flexbox)+ 删除Logo区域 |
|
||||
|
||||
## 详细变更
|
||||
|
||||
### 后端
|
||||
|
||||
1. **新增 `quick_commands` MySQL 表**
|
||||
- 字段: id, name, cmd, is_builtin, sort_order, created_by, created_at, updated_at
|
||||
- 内置命令 `is_builtin=True`,不可删除/编辑
|
||||
- 启动时自动 seed 5 条内置命令
|
||||
|
||||
2. **新增 API 端点**
|
||||
- `GET /api/terminal/quick-commands` — 列出所有快捷命令
|
||||
- `POST /api/terminal/quick-commands` — 创建自定义命令
|
||||
- `PUT /api/terminal/quick-commands/{id}` — 编辑命令(内置不可编辑)
|
||||
- `DELETE /api/terminal/quick-commands/{id}` — 删除命令(内置不可删除)
|
||||
- 所有CUD操作带审计日志
|
||||
|
||||
### 前端 — TerminalPage P0修复
|
||||
|
||||
1. **Ctrl+L 清屏** — `onKeydown` 添加 `e.ctrlKey && e.key === 'l'`:preventDefault + term.clear() + ws.send('clear\r')
|
||||
2. **命令历史恢复** — `newSession()` 从 localStorage 恢复全局历史到 session.cmdHistory
|
||||
3. **重连逻辑 per-session** — reconnectAttempt/reconnectTimer 移入 TermSession 接口,消除多标签干扰
|
||||
4. **ws.onopen 不忽略非活跃标签** — 移除 `if (idx !== activeIdx) return`
|
||||
5. **ping per-session** — startPing/stopPing 操作 session.pingTimer
|
||||
6. **uptime per-session** — connectionStartTime/uptimeTimer 移入 session
|
||||
|
||||
### 前端 — TerminalPage P1修复
|
||||
|
||||
7. **关键空 catch 处理** — sendCmd JSON.parse兜底、termPaste/termCopy try/catch + snackbar、webssh-token错误透传
|
||||
8. **termRefs 竞态修复** — Map key 从数组索引改为 session.id
|
||||
9. **initialCdSent per-session** — 移入 TermSession
|
||||
10. **剪贴板权限处理** — termPaste 捕获 DOMException + snackbar提示
|
||||
|
||||
### 前端 — TerminalPage P2修复
|
||||
|
||||
11. **右键菜单补回"全选"** — 新增 termSelectAll() 调用 term.selectAll()
|
||||
12. **指数退避重连** — `Math.min(1000 * Math.pow(2, attempt - 1), 30000)`
|
||||
13. **快捷命令双击编辑** — `@dblclick` 触发 editQuickCmd()
|
||||
14. **硬编码高度修复** — `calc(100vh - 64px)` → `d-flex flex-column flex-grow-1`
|
||||
15. **快捷命令 MySQL API 对接** — loadQuickCmds/saveQuickCmd/deleteQuickCmd 改用 http API,fallback localStorage
|
||||
16. **Shell 语法高亮** — 命令输入栏实时着色:sudo→红色、关键字→蓝色、管道/重定向→黄色、字符串→绿色、flag→紫色
|
||||
|
||||
### 前端 — LoginPage 调整
|
||||
|
||||
17. **登录卡片居中** — 移除 v-container+v-row+v-col,改用 flexbox 居中
|
||||
18. **删除 Logo 区域** — 移除 v-avatar + "登录 Nexus" + "服务器运维管理平台"
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- ✅ 需重启 — 新增 MySQL 表 `quick_commands`,首次启动自动建表 + seed
|
||||
- ✅ 前端需重新构建部署
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开终端 `?server_id=8` → 连接成功
|
||||
2. 按 Ctrl+L → 终端清屏
|
||||
3. 输入几个命令 → 刷新页面 → ArrowUp 回溯历史命令
|
||||
4. 开两个标签连接不同服务器 → 断开一个 → 另一个不受影响
|
||||
5. 右键菜单 → 有"全选"选项
|
||||
6. 添加/编辑/删除自定义快捷命令 → 刷新后仍在(MySQL持久化)
|
||||
7. 命令输入栏输入 `sudo systemctl restart nginx` → 看到语法着色
|
||||
8. 登录页 → 卡片居中,无Logo
|
||||
@@ -0,0 +1,134 @@
|
||||
# Changelog: Vuetify 前端审计修复
|
||||
|
||||
**日期**: 2026-05-31
|
||||
**主题**: 5个严重问题修复 + DRY 重构 + 类型安全
|
||||
|
||||
## 变更摘要
|
||||
|
||||
### 1. 提取 useSnackbar composable(DRY 重构)
|
||||
- **新建** `src/composables/useSnackbar.ts` — 统一 snackbar 包装函数
|
||||
- **新建** `src/types/global.d.ts` — `Window.$snackbar` 全局类型声明
|
||||
- **修改 8 个页面** — 删除各自重复的 `function snackbar(...)` 定义,改为 `import { useSnackbar }`
|
||||
- **修改** `App.vue` — `(window as any).$snackbar` → `window.$snackbar`
|
||||
- **修改** `useWebSocket.ts` — 同上
|
||||
|
||||
### 2. API client FormData 支持(🔴 严重 #1 + 🟠 #12)
|
||||
- **修改** `src/api/index.ts`:
|
||||
- `body instanceof FormData` 时不设 `Content-Type`(浏览器自动加 boundary)
|
||||
- 新增 `http.upload()` 方法
|
||||
- **修改** `FilesPage.vue` `doUpload()`:
|
||||
- `fetch('/api/sync/upload')` → `http.upload('/sync/upload', form)`
|
||||
- 修复:文件上传现在自动携带 JWT Authorization header
|
||||
|
||||
### 3. 修复 6 个静默 catch 块(🔴 严重 #3)
|
||||
- `FilesPage.vue` — `loadServers()` / `browse()` catch 加 snackbar 错误提示
|
||||
- `SettingsPage.vue` — `loadSettings()` / `loadAllowlist()` catch 加 snackbar 错误提示
|
||||
- `DashboardPage.vue` — `loadStats()` / `loadServers()` catch 加 snackbar 错误提示
|
||||
|
||||
### 4. SettingsPage API Key 密码验证对话框(🔴 严重 #2)
|
||||
- **新增** `<v-dialog>` 密码输入对话框(与 TOTP Disable 对话框模式一致)
|
||||
- `revealApiKey()` → 先弹密码对话框 → `doRevealApiKey()` 用真实密码调用后端
|
||||
- 修复:不再发送空密码 `{ password: '' }` 绕过 re-auth 保护
|
||||
|
||||
### 5. DashboardPage 数据补全(🔴 严重 #5)
|
||||
- **新增** `loadAlerts()` — 从 `GET /alert-history/?limit=5` 加载告警历史
|
||||
- **新增** `loadSummary()` — 从 `GET /servers/?per_page=500` 计算:
|
||||
- Agent 覆盖率(有 agent_version 的服务器比例)
|
||||
- CPU 平均值(从 system_info.cpu_usage)
|
||||
- 内存平均值(从 system_info.mem_usage)
|
||||
- **新增** WebSocket 实时刷新 — 监听 ws.alerts 变化自动刷新 stats + alerts
|
||||
|
||||
### 6. 消除 as any 类型断言(🔴 严重 #4)
|
||||
- **新建** `src/types/api.ts` — 14 个 API 响应 interface:
|
||||
- `PaginatedResponse<T>`, `SettingsResponse`, `AllowlistResponse`, `BrowseResponse`
|
||||
- `FileEntry`, `ServerApiItem`, `AlertLogItem`, `AlertStatsResponse`
|
||||
- `CommandLogItem`, `RetryItem`, `ScriptItem`, `PushItem`, `ScheduleItem`, `AuditItem`
|
||||
- **修改 12 个页面** — 所有 `(res as any).items` 改为 `http.get<PaginatedResponse<T>>()`
|
||||
- **结果**: `as any` 从 33 个降到 0 个(src/ 目录完全清零)
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 操作 |
|
||||
|------|------|
|
||||
| `src/composables/useSnackbar.ts` | 新建 |
|
||||
| `src/types/global.d.ts` | 新建 |
|
||||
| `src/types/api.ts` | 新建 |
|
||||
| `src/api/index.ts` | 修改 |
|
||||
| `src/App.vue` | 修改 |
|
||||
| `src/composables/useWebSocket.ts` | 修改 |
|
||||
| `src/pages/FilesPage.vue` | 修改 |
|
||||
| `src/pages/SettingsPage.vue` | 修改 |
|
||||
| `src/pages/DashboardPage.vue` | 修改 |
|
||||
| `src/pages/CredentialsPage.vue` | 修改 |
|
||||
| `src/pages/RetriesPage.vue` | 修改 |
|
||||
| `src/pages/PushPage.vue` | 修改 |
|
||||
| `src/pages/ScriptsPage.vue` | 修改 |
|
||||
| `src/pages/SchedulesPage.vue` | 修改 |
|
||||
| `src/pages/ServersPage.vue` | 修改 |
|
||||
| `src/pages/CommandsPage.vue` | 修改 |
|
||||
| `src/pages/AlertsPage.vue` | 修改 |
|
||||
| `src/pages/AuditPage.vue` | 修改 |
|
||||
| `src/pages/TerminalPage.vue` | 修改 |
|
||||
|
||||
## 验证
|
||||
|
||||
- ✅ `vue-tsc --noEmit` — TypeScript 零错误
|
||||
- ✅ `vite build` — 生产构建通过 (1.53s, Monaco 懒加载)
|
||||
- ✅ 开发服务器正常启动
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Composables + UX + 核心功能 (同日)
|
||||
|
||||
### Composables 提取 (Step 7)
|
||||
- **新建** `src/composables/useServerList.ts` — 5 个页面复用
|
||||
- **新建** `src/composables/useServerPagination.ts` — ServersPage/DashboardPage 复用
|
||||
- **新建** `src/utils/status.ts` — statusChipColor/statusLabel/formatRelativeTime
|
||||
|
||||
### 表单验证 + 空状态 + Loading (Step 8)
|
||||
- **新建** `src/utils/validation.ts` — 8 个验证规则工厂
|
||||
- **12 个空状态** — 9 个文件的 v-data-table 加 `<template #no-data>`
|
||||
- **14 个表单字段** — SettingsPage/ServersPage/ScriptsPage/SchedulesPage/CredentialsPage 加 `:rules`
|
||||
- **~20 个 loading 态** — ScriptsPage/SettingsPage/CredentialsPage/SchedulesPage/FilesPage
|
||||
|
||||
### 核心功能补全 (Step 9)
|
||||
- ✅ Servers 批量操作(多选 + 健康检查 + 批量删除)
|
||||
- ✅ Servers CSV 导出
|
||||
- ✅ Servers 详情面板(系统信息 + 同步日志 tab)
|
||||
- ✅ Push 同步模式(增量/全量/校验和 + 警告提示)
|
||||
- ✅ Push ZIP 拖拽上传
|
||||
- ✅ Push WebSocket 实时进度(逐服务器状态 + 进度条)
|
||||
- ✅ Scripts 快捷执行面板(临时命令 + Ctrl+Enter)
|
||||
- ✅ Scripts 执行状态追踪(5s 自动轮询 + 停止按钮)
|
||||
- ✅ Files Monaco 编辑器(全屏 + 语法高亮 + Ctrl+S 保存 + 主题切换)
|
||||
- ✅ Dashboard WebSocket 实时刷新(watch alerts)
|
||||
|
||||
### 新建文件总计
|
||||
- `src/composables/useSnackbar.ts`
|
||||
- `src/composables/useServerList.ts`
|
||||
- `src/composables/useServerPagination.ts`
|
||||
- `src/types/global.d.ts`
|
||||
- `src/types/api.ts`
|
||||
- `src/utils/status.ts`
|
||||
- `src/utils/validation.ts`
|
||||
- `src/components/MonacoEditor.vue`
|
||||
|
||||
## 动机
|
||||
|
||||
审计发现 5 个严重问题(安全漏洞 + 数据错误)和大量 DRY 违规/类型安全问题,需要在对接真实 API 数据之前修复。
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- 不需要数据库迁移
|
||||
- 不需要后端重启(纯前端修改)
|
||||
- 需要重新构建前端:`cd frontend && npx vite build`
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. TypeScript: `cd frontend && npx vue-tsc --noEmit`
|
||||
2. 构建: `cd frontend && npx vite build`
|
||||
3. 功能验证:
|
||||
- FilesPage 上传 → 携带 JWT(不再 401)
|
||||
- SettingsPage API Key → 弹出密码输入对话框
|
||||
- Dashboard → 告警历史有数据、Agent 覆盖率/CPU/内存有值
|
||||
- 所有页面 snackbar 错误提示正常
|
||||
@@ -0,0 +1,75 @@
|
||||
# Changelog: Vuetify 前端重建
|
||||
|
||||
**日期**: 2026-05-31
|
||||
**变更摘要**: Nexus 前端从 Tailwind+Alpine.js 完全重建为 Vue 3 + Vuetify 4 + TypeScript SPA
|
||||
|
||||
## 动机
|
||||
旧版前端使用 Tailwind CSS + Alpine.js 的 12 个独立 HTML 页面,无组件化、无路由、无类型安全。
|
||||
随着功能增长,维护成本高,页面间状态共享困难。Vue 3 + Vuetify 4 提供:
|
||||
- 组件化架构(Composition API + TypeScript)
|
||||
- 统一设计系统(Vuetify Material Design)
|
||||
- SPA 路由 + 路由守卫
|
||||
- Pinia 状态管理(JWT auth store)
|
||||
- 生产级构建工具链(Vite)
|
||||
|
||||
## 变更内容
|
||||
|
||||
### 新增:frontend/ 目录(Vue 3 + Vuetify 4 SPA)
|
||||
- **技术栈**: Vue 3.5 + Vuetify 4 + TypeScript 5.9 + Vue Router 4 + Pinia 3 + Vite 8
|
||||
- **14 个页面组件**: src/pages/ 下所有页面
|
||||
- LoginPage — JWT + TOTP 双因素登录,429 锁定处理
|
||||
- DashboardPage — 统计卡片 + 服务器分页表格 + 告警/资源面板
|
||||
- ServersPage — CRUD + 搜索 + 分页 + 编辑/删除对话框
|
||||
- TerminalPage — xterm.js WebSSH 终端(@xterm/xterm + FitAddon + WebLinksAddon)
|
||||
- FilesPage — 远程文件浏览器(浏览/上传/下载/新建目录/预览)
|
||||
- PushPage — 文件推送 + 进度条 + 推送历史
|
||||
- ScriptsPage — 脚本 CRUD + 执行
|
||||
- CredentialsPage — 密码预设/SSH 密钥/DB 凭据
|
||||
- SchedulesPage — Cron 调度 CRUD + 启用/禁用
|
||||
- RetriesPage — 重试队列 + 状态过滤
|
||||
- CommandsPage — 命令日志/SSH 会话双视图
|
||||
- AlertsPage — 告警统计 + 历史记录
|
||||
- AuditPage — 审计日志 + 操作过滤
|
||||
- SettingsPage — 系统设置/Telegram/TOTP/API Key/IP 白名单
|
||||
- **App.vue**: 全局布局 — Vuetify App Bar + Navigation Drawer + Snackbar + 全局搜索
|
||||
- **全局搜索**: App Bar 搜索框,对接 /api/search/,跨服务器/脚本/凭据/调度搜索
|
||||
- **Auth Store**: Pinia composition store,JWT + admin profile,localStorage 持久化
|
||||
- **API 工具**: src/api/index.ts — fetch 封装 + JWT 自动附加 + 401/429/202 处理
|
||||
- **WebSocket**: src/composables/useWebSocket.ts — ws://alerts?token=JWT + 断线重连
|
||||
- **主题持久化**: 主题选择保存到 localStorage(nexus_theme),刷新后恢复
|
||||
- **Vite 代理**: /api → https://api.synaglobal.vip, /ws → wss://api.synaglobal.vip
|
||||
- **生产构建**: base=/app/,输出到 web/app/dist/
|
||||
|
||||
### 设计规范
|
||||
- 严格遵循 Vuetify 官方 Gallery 源码模式
|
||||
- 统计卡片:v-list border two-line rounded-lg + v-icon :color size="30"
|
||||
- 内容卡片:v-card elevation="0" border rounded="lg"
|
||||
- 状态标签:v-chip label variant="tonal" size="x-small" border="current sm"
|
||||
- 不自创配色,使用 Vuetify 默认 light/dark 主题
|
||||
|
||||
## 涉及文件
|
||||
- `frontend/` — 全新目录,约 30 个文件
|
||||
- `src/pages/TerminalPage.vue` — WebSSH 终端 Vue 化(xterm.js + Koko 协议)
|
||||
- `src/App.vue` — 全局布局 + 搜索 + 主题切换 + WebSocket
|
||||
- `src/plugins/vuetify.ts` — 主题持久化
|
||||
- `src/router/index.ts` — 14 条 hash 路由 + /terminal 新增 + 路由守卫
|
||||
- `src/api/index.ts` — HTTP 客户端 + JWT
|
||||
- `src/stores/auth.ts` — Pinia auth store
|
||||
- `src/composables/useWebSocket.ts` — WebSocket composable
|
||||
- `vite.config.mts` — base + outDir + proxy 配置
|
||||
- `package.json` — 依赖声明
|
||||
- `web/app/dist/` — 构建产物输出目录
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 不影响后端,不需要重启 Python 服务
|
||||
- 前端是独立的 SPA,部署时将 web/app/dist/ 内容放到 web/app/ 即可
|
||||
- CORS 需包含 http://localhost:3000(开发环境)
|
||||
|
||||
## 验证方式
|
||||
1. `cd frontend && npm run dev` → http://localhost:3000
|
||||
2. 登录页 → 输入用户名/密码 → 跳转仪表盘
|
||||
3. 逐页验证所有页面布局和交互
|
||||
4. 主题切换 → 刷新页面 → 确认主题持久化
|
||||
5. 全局搜索 → 输入关键词 → 显示搜索结果
|
||||
6. 终端页 → 点击服务器的"终端"按钮 → xterm.js 连接 WebSSH
|
||||
7. `npx vite build` → 确认构建成功
|
||||
@@ -0,0 +1,47 @@
|
||||
# 2026-05-31 Vuetify Gallery 重设计 + 后端对接
|
||||
|
||||
## 变更摘要
|
||||
前端从自创配色/布局全面迁移到 Vuetify 官方 Component Gallery 设计模式,同时打通后端 API 链路。
|
||||
|
||||
## 动机
|
||||
- 原有设计使用自定义色值(#0F1117, #1A1D27, #60A5FA 等)和 Rail 侧边栏,与 Vuetify 官方 Gallery 风格不一致
|
||||
- Dashboard 使用静态 mock 数据,无法展示真实服务器状态
|
||||
- 缺少 Vite proxy,开发时无法对接后端
|
||||
- 路由守卫被注释,WebSocket 未集成
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 主题
|
||||
- `frontend/src/plugins/vuetify.ts` — 删除所有自定义色值,使用 Vuetify 默认 light/dark 主题
|
||||
|
||||
### 布局
|
||||
- `frontend/src/App.vue` — 全宽侧边栏 250px bg-surface + App Bar bg-primary + 用户菜单(v-menu) + 主题切换 + 登出
|
||||
|
||||
### 页面 (全部 13 个)
|
||||
- `frontend/src/pages/DashboardPage.vue` — 完全重写:v-list--border 统计卡片 + v-card--border 表格 + 告警列表 + 摘要进度条,对接 /api/servers/stats 和 /api/servers/
|
||||
- `frontend/src/pages/ServersPage.vue` — 统计改 v-list--border, 表格改 v-card--border, chip 改 x-small label tonal border
|
||||
- `frontend/src/pages/AlertsPage.vue` — 统计改 v-list--border + blue/green/orange/red 配色
|
||||
- 其余 9 页:v-card→elevation=0 border, dialog→border, chip→size=x-small label tonal border=current sm
|
||||
- `frontend/src/pages/LoginPage.vue` — rounded=lg + border
|
||||
|
||||
### 基础设施
|
||||
- `frontend/vite.config.mts` — proxy /api→api.synaglobal.vip, /ws→wss://api.synaglobal.vip
|
||||
- `frontend/src/api/index.ts` — 新增 202(TotpRequiredError) + 429 处理
|
||||
- `frontend/src/stores/auth.ts` — login() 处理 access_token+admin 响应, fetchProfile 仅在 401 时 forceLogout
|
||||
- `frontend/src/router/index.ts` — 路由守卫恢复
|
||||
- `frontend/src/composables/useWebSocket.ts` — 新建:ws://alerts?token=JWT, ping/pong, 断线重连, alert→snackbar
|
||||
- `frontend/src/App.vue` — watch isLoggedIn 自动 connect/disconnect WebSocket
|
||||
|
||||
## 是否需迁移/重启
|
||||
- 需要重新 `npm run dev` 或 `npm run build`
|
||||
- 后端 CORS 需包含 `http://localhost:3000`(开发环境)
|
||||
- 部署时需将 dist/ 放到 web/app/dist/
|
||||
|
||||
## 验证方式
|
||||
1. `npm run build` 成功
|
||||
2. `npm run dev` → 自动跳转 /login(路由守卫生效)
|
||||
3. 登录后 Dashboard 显示真实服务器数据
|
||||
4. WebSocket 连接后告警推送到浏览器
|
||||
5. 所有卡片为 elevation=0 border(无阴影,有边框)
|
||||
6. 统计卡片为 v-list--border two-line(不是 v-card)
|
||||
7. 颜色使用语义类(blue/green/orange/red)
|
||||
@@ -0,0 +1,25 @@
|
||||
# 2026-06-01 修复 SFTP API:create_sftp_client → start_sftp_client
|
||||
|
||||
## 摘要
|
||||
|
||||
修复文件上传/下载/写入时 `SSH 连接失败: 'SSHClientConnection' object has no attribute 'create_sftp_client'`。
|
||||
|
||||
## 动机
|
||||
|
||||
asyncssh 2.x 的 `SSHClientConnection` 仅提供 `start_sftp_client()`,不存在 `create_sftp_client()`。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `server/infrastructure/ssh/asyncssh_pool.py` | 新增 `sftp_session()` 上下文管理器 |
|
||||
| `server/api/sync_v2.py` | upload / download / write-file 改用 `sftp_session` |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
需重启 `nexus` Supervisor 进程。
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 文件管理 → 编辑并保存 → 应成功(`/api/sync/write-file`)。
|
||||
2. 上传、下载文件不再返回 502 与上述 AttributeError。
|
||||
@@ -0,0 +1,14 @@
|
||||
# 2026-06-01 审计日志操作中文展示
|
||||
|
||||
## 摘要
|
||||
|
||||
补全审计日志 `action` / `target_type` 中文映射(覆盖登录、服务器、脚本、文件、设置等全部后端动作);未登记动作按词段智能兜底为中文。审计页筛选下拉同步为中文标签。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/utils/auditLabels.ts`
|
||||
- `frontend/src/pages/AuditPage.vue`
|
||||
|
||||
## 验证
|
||||
|
||||
打开 `/#/audit`,「操作」列与筛选器应显示中文(如「登录成功」「文件推送」「写入远程文件」),不再出现 `create_server` 等英文蛇形命名。
|
||||
@@ -0,0 +1,38 @@
|
||||
# Changelog — 生产浏览器验收(续)
|
||||
|
||||
**日期**: 2026-06-01
|
||||
**类型**: 验收 / 文档
|
||||
|
||||
## 摘要
|
||||
|
||||
继续单负责人执行计划:对 `https://api.synaglobal.vip` 做 SPA 与 WebSSH 浏览器抽测,并确认 Redis 心跳与 Layer3 健康检查 cron。
|
||||
|
||||
## 动机
|
||||
|
||||
完成上轮未做的「浏览器验证」与部分 L5 抽测项,留下可复查证据。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/reports/2026-06-01-browser-verification.md`(新)
|
||||
- `docs/reports/2026-06-01-production-verification.md` — 勾选浏览器项
|
||||
- `docs/handoff-2026-06-01.md` §八 — 验收状态更新
|
||||
- `docs/design/plans/2026-06-01-single-owner-execution.md` — 第二阶段 B1–B5
|
||||
|
||||
## 验证结果(摘要)
|
||||
|
||||
| 项 | 结果 |
|
||||
|----|------|
|
||||
| 仪表盘 / 服务器 / 告警 / 推送 | ✅ |
|
||||
| WebSSH server_id=8 | ✅ 已连接 ~228ms |
|
||||
| Redis `heartbeat:*` | 1 条 |
|
||||
| health_monitor cron | ✅ 每分钟 |
|
||||
| 批量选择条自动化 | ⚠️ 需人工 30 秒 |
|
||||
| Telegram / 正式 Push / kill 守护 | 未执行(见报告) |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 回滚
|
||||
|
||||
仅文档,git revert 即可。
|
||||
@@ -0,0 +1,77 @@
|
||||
# Changelog: 全站 bug 审查修复 (前端9项 + 后端5项)
|
||||
|
||||
**日期**: 2026-06-01
|
||||
**变更摘要**: 全站巡检发现并修复 14 个 bug(1 P0 + 9 P1 + 4 P2),涵盖前端全局搜索崩溃、TOTP登录流程断裂、服务器编辑域名污染、后端Redis阻塞、并发session竞态等
|
||||
|
||||
## 动机
|
||||
|
||||
对 Nexus 全站进行系统性 bug 审查,发现多个影响生产使用的问题。最严重的 3 个:全局搜索因 `http` 未导入直接崩溃、TOTP 双因素登录完全不可用、编辑服务器后域名被端口字符串污染导致后续连接失败。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `frontend/src/App.vue` | 修改 | 添加 `http` 导入 |
|
||||
| `frontend/src/pages/LoginPage.vue` | 修改 | TOTP异常捕获 + 锁定倒计时响应式 |
|
||||
| `frontend/src/pages/ServersPage.vue` | 修改 | 编辑时 domain 不再拼接端口 |
|
||||
| `frontend/src/pages/PushPage.vue` | 修改 | Set 索引 → 迭代器 |
|
||||
| `frontend/src/pages/SettingsPage.vue` | 修改 | API字符串值转数字 |
|
||||
| `frontend/src/api/index.ts` | 修改 | 删除重复 getList 定义 |
|
||||
| `frontend/src/pages/TerminalPage.vue` | 修改 | 路径验证正则放松 |
|
||||
| `frontend/src/composables/useWebSocket.ts` | 修改 | 关闭旧连接再建新连接 |
|
||||
| `server/api/websocket.py` | 修改 | redis.keys → scan_iter |
|
||||
| `server/application/services/auth_service.py` | 修改 | TTL 仅在无TTL时设置 |
|
||||
| `server/api/servers.py` | 修改 | 批量操作加 asyncio.Lock |
|
||||
| `server/api/settings.py` | 修改 | 弃用API替换 |
|
||||
|
||||
## 详细变更
|
||||
|
||||
### 前端 P0 修复
|
||||
|
||||
1. **App.vue `http` 未导入** — 第320行 `doSearch()` 调用 `http.get()` 但只导入了 `api`,导致 `ReferenceError: http is not defined`。修复:`import { api, http } from '@/api'`
|
||||
|
||||
### 前端 P1 修复
|
||||
|
||||
2. **TOTP 登录流程断裂** — `api()` 在后端返回 202 时抛 `TotpRequiredError`(非 `ApiError` 子类),`LoginPage` catch 只检查 `e instanceof ApiError`,TOTP 用户看到"网络错误"无法登录。修复:导入 `TotpRequiredError` 并在 catch 中优先检查
|
||||
|
||||
3. **ServersPage 编辑域名污染** — `editServer()` 设置 `form.address = domain:port`,保存时 `domain: form.address` 发送含端口字符串。修复:`form.address = item.domain`
|
||||
|
||||
4. **PushPage Set 索引无效** — `selectedIds.value[0]` 对 `Set<number>` 返回 `undefined`。修复:`selectedIds.value.values().next().value`
|
||||
|
||||
5. **SettingsPage 类型转换** — API 返回 `value: "80"` 字符串直接赋值给 number 字段。修复:正则检测纯数字字符串并 `Number()` 转换
|
||||
|
||||
### 前端 P2 修复
|
||||
|
||||
6. **api/index.ts 重复定义** — `getList` 方法定义两次,删除第二个
|
||||
|
||||
7. **LoginPage 锁定倒计时** — `remainingMinutes` computed 依赖 `Date.now()` 非响应式。修复:使用 `ref(Date.now())` + `watch(lockoutUntil)` + 30秒定时器
|
||||
|
||||
8. **TerminalPage 路径验证过严** — 正则 `/^[a-zA-Z0-9/._\-]+$/` 排除空格/中文/~。修复:`/^[^\x00-\x1f\x7f"']+$/` 仅排除控制字符和引号
|
||||
|
||||
9. **useWebSocket 连接泄漏** — CONNECTING/CLOSING 状态时创建新连接但不关闭旧连接。修复:关闭旧连接后再创建新的
|
||||
|
||||
### 后端 P1 修复
|
||||
|
||||
10. **websocket.py redis.keys() 阻塞** — `KEYS` 命令 O(N) 扫描整个键空间。修复:`scan_iter(match="ws:count:*")` 非阻塞迭代
|
||||
|
||||
11. **auth_service.py TTL 重置** — 每次 `sadd` 后 `expire` 重置 TTL,频繁刷新的管理员 refresh token 键永不过期。修复:`ttl = await redis.ttl(key)`,仅在 `ttl < 0` 时设置 expire
|
||||
|
||||
12. **servers.py 并发 session commit** — 批量操作中 `asyncio.gather` 并发协程共享同一 db session 并 `commit()`。修复:添加 `asyncio.Lock()` 序列化 commit
|
||||
|
||||
### 后端 P2 修复
|
||||
|
||||
13. **settings.py asyncio.get_event_loop()** — Python 3.10+ 弃用。修复:`asyncio.get_running_loop()`
|
||||
|
||||
14. **settings.py datetime.utcfromtimestamp()** — Python 3.12+ 弃用。修复:`datetime.fromtimestamp(ts, tz=timezone.utc)`
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- ✅ 需重启 — 后端代码变更
|
||||
- ✅ 前端需重新构建部署
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 登录页 → 输入 admin/Nexus@2026 → 成功跳转仪表盘 ✓
|
||||
2. 全局搜索框 → 输入"机器人" → 无崩溃(之前会 ReferenceError)✓
|
||||
3. 控制台零错误 ✓
|
||||
4. 健康检查 → `ok` ✓
|
||||
@@ -0,0 +1,77 @@
|
||||
# Changelog: 2026-06-01 全量 Bug 修复
|
||||
|
||||
## 变更摘要
|
||||
|
||||
一次性修复交接文件第八节 P0~P3 项及审查发现的认证/数据流问题。
|
||||
|
||||
## 动机
|
||||
|
||||
- Vue `ServersPage` 批量 Agent 操作因 Vuetify `item-value` 返回 ID 而非对象导致 `server_ids` 无效
|
||||
- Access Token 30 天与 Refresh Cookie 架构不一致;SPA 未使用 `credentials` 与自动 refresh
|
||||
- Agent 心跳直写 MySQL 与 Redis→10min 刷库设计冲突
|
||||
- script-callback 缺少 `X-API-Key` 层防护
|
||||
|
||||
## 涉及文件
|
||||
|
||||
### 前端
|
||||
- `frontend/src/pages/ServersPage.vue`
|
||||
- `frontend/src/api/index.ts`
|
||||
- `frontend/src/stores/auth.ts`
|
||||
- `frontend/src/router/index.ts`
|
||||
- `frontend/src/pages/TerminalPage.vue`
|
||||
- `frontend/src/utils/serverSelection.ts`(新)
|
||||
- `frontend/src/utils/apiError.ts`(新)
|
||||
|
||||
### 后端
|
||||
- `server/api/agent.py`
|
||||
- `server/application/services/auth_service.py`
|
||||
- `server/config.py`
|
||||
- `server/application/services/script_jobs.py`
|
||||
- `server/application/services/script_service.py`
|
||||
- `server/domain/models/__init__.py`
|
||||
- `server/infrastructure/database/admin_repo.py`
|
||||
- `server/infrastructure/database/migrations.py`
|
||||
- `server/main.py`
|
||||
- `server/api/settings.py`(ruff 清理)
|
||||
|
||||
### 测试
|
||||
- `tests/test_api.py`
|
||||
- `tests/test_nexus_v6.py`
|
||||
- `tests/test_step1to5_features.py`
|
||||
|
||||
### 文档
|
||||
- `docs/design/specs/2026-06-01-bug-remediation-design.md`
|
||||
- `docs/design/plans/2026-06-01-bug-remediation.md`
|
||||
- `docs/audit/2026-06-01-bug-remediation.md`
|
||||
|
||||
## 是否需迁移/重启
|
||||
|
||||
- **需要**重启 Nexus 后端(认证、Agent、迁移 DROP 列)
|
||||
- **需要**重新构建并部署前端 SPA
|
||||
- 启动时自动执行 `admins` 表 DROP 废弃列(已存在则跳过)
|
||||
|
||||
## 生产部署记录(2026-06-01)
|
||||
|
||||
- Git: `8311821` → `origin/main`
|
||||
- 后端: `supervisorctl restart nexus`,`/health` → `ok`
|
||||
- 前端: Vite build + tar/scp,`index.html` → `index-ngtHwr8t.js`,`ServersPage-Vzm34hgn.js`
|
||||
- 浏览器: 登录成功进入仪表盘与服务器列表
|
||||
- API: `expires_in=3600`(60 分钟 access token)
|
||||
|
||||
## 验证方式
|
||||
|
||||
```bash
|
||||
ruff check server/
|
||||
python -c "import server.main"
|
||||
cd frontend && npm run type-check
|
||||
pytest tests/test_nexus_v6.py tests/test_step1to5_features.py -q
|
||||
```
|
||||
|
||||
- 服务器页:勾选多台 → 批量安装 Agent → 提示 `N/M 成功`
|
||||
- 登录后刷新页面:HttpOnly refresh 恢复会话(无 localStorage token)
|
||||
- 未知 server_id 心跳:200 `discarded`,日志 debug 级 422
|
||||
|
||||
## 未改项说明
|
||||
|
||||
- **WebSSH JWT 在 URL**:仍用 15 分钟专用 token + query(需 WS 协议改造,单独迭代)
|
||||
- **sysctl 命令注入**:当前 `sync_engine_v2` 已无 sysctl 拼接路径
|
||||
@@ -0,0 +1,24 @@
|
||||
# 2026-06-01 远程压缩权限与 tar 路径优化
|
||||
|
||||
## 摘要
|
||||
|
||||
修复文件管理「压缩」在网站目录无写权限时报 `Permission denied` / `Broken pipe`:改用 `tar -C` 相对路径、先写 `/tmp` 再 `mv`,并支持 `sudo -n` 回退。
|
||||
|
||||
## 动机
|
||||
|
||||
原 `tar czf /abs/dest /abs/path...` 在目录不可写时失败,且产生 `Removing leading '/' from member names` 警告。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `server/infrastructure/ssh/remote_archive.py` | 新建:压缩命令构建与 sudo 回退 |
|
||||
| `server/api/sync_v2.py` | `/compress` 接入新逻辑 |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
`supervisorctl restart nexus`
|
||||
|
||||
## 验证方式
|
||||
|
||||
在 `#/files` 选择文件 → 压缩 → 目标为当前目录下 `xxx.tar.gz`,应成功或给出明确权限提示。
|
||||
@@ -0,0 +1,33 @@
|
||||
# Changelog: 仪表盘/审计/调度/重试队列修复
|
||||
|
||||
**日期**: 2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
1. 仪表盘「最近操作」固定最近 10 条并中文展示
|
||||
2. 审计日志全中文标签 + 正确分页参数
|
||||
3. 仪表盘等业务页随侧栏自适应缩进
|
||||
4. 修复调度/重试队列无法打开(路由未卸载、server_ids 格式)
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/App.vue` — `router-view :key`、 `nexus-page-main`
|
||||
- `frontend/src/pages/DashboardPage.vue`
|
||||
- `frontend/src/pages/AuditPage.vue`
|
||||
- `frontend/src/pages/SchedulesPage.vue`
|
||||
- `frontend/src/pages/RetriesPage.vue`
|
||||
- `frontend/src/utils/auditLabels.ts`(新)
|
||||
- `frontend/src/utils/auditNormalize.ts`(新)
|
||||
- `server/api/settings.py` — 审计响应 `resource_*` 别名
|
||||
|
||||
## 验证
|
||||
|
||||
1. `#/` 最近操作 ≤10 条,中文描述
|
||||
2. `#/audit` 操作/目标列为中文
|
||||
3. 打开侧栏 → 仪表盘表格区域随动缩进
|
||||
4. 从 `#/terminal` 点「调度」「重试队列」可正常切换页面
|
||||
5. `cd frontend && npm run type-check`
|
||||
|
||||
## 部署
|
||||
|
||||
仅前端构建上传;后端 `settings.py` 小改需 `supervisorctl restart nexus`(可选,前端 normalize 已兼容)。
|
||||
@@ -0,0 +1,32 @@
|
||||
# Changelog — 文件管理/POSIX 生产部署
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 部署 |
|
||||
|
||||
## 摘要
|
||||
|
||||
生产部署:`git pull` 至 `bd6467d`,`supervisorctl restart nexus`,Windows Node 22 构建前端并 scp 至 `web/app/`,prune 732 个过期 assets。`/health` → ok,`/app/` → 200。
|
||||
|
||||
## 动机
|
||||
|
||||
主计划 Phase B 部署闭环。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- 生产:`/www/wwwroot/api.synaglobal.vip/`(后端 + `web/app/index.html` + `assets/`)
|
||||
- 本地构建产物(gitignore,未提交)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 已 `supervisorctl restart nexus`
|
||||
- 已删除生产机遗留目录 `server/server/`(bandit 误扫)
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
ssh nexus "curl -s http://127.0.0.1:8600/health"
|
||||
ssh nexus "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8600/app/"
|
||||
# 浏览器:#/files → 归属列、权限对话框、顶栏提权提示
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
# 2026-06-01 编辑器深色底 + SFTP 写入修复
|
||||
|
||||
## 摘要
|
||||
|
||||
1. **编辑器**:固定 `nexus-dark` 深色主题,移除透明背景 CSS 与主题切换按钮,编辑区 `#1e1e1e` 底,避免白底看不清字。
|
||||
2. **保存**:`sftp.put(BytesIO)` 不符合 asyncssh API,改为 `sftp.open(path, 'wb')` + `write()`;`write-file` 增加绝对路径校验。
|
||||
|
||||
## 动机
|
||||
|
||||
- 用户反馈编辑器白底、文字不可见。
|
||||
- 保存仍失败:`put()` 只接受本地路径,内存内容需用 `open` 写入。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `frontend/src/monaco/nexusThemes.ts` | 固定深色主题 |
|
||||
| `frontend/src/components/FileEditorWorkbench.vue` | 样式、去掉浅色切换 |
|
||||
| `server/infrastructure/ssh/asyncssh_pool.py` | `sftp_write_bytes()` |
|
||||
| `server/api/sync_v2.py` | upload / write-file 写入方式 |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
前端构建部署 + 后端 `supervisorctl restart nexus`。
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开编辑器 → 深灰底、浅色字、语法高亮可见。
|
||||
2. 修改文件 Ctrl+S / 保存 → 提示「文件已保存」,刷新后内容一致。
|
||||
@@ -0,0 +1,32 @@
|
||||
# 2026-06-01 浮动编辑器语法高亮增强
|
||||
|
||||
## 摘要
|
||||
|
||||
- 注册 **Nexus 深色/浅色主题**(`nexus-dark` / `nexus-light`),强化关键字、字符串、注释等 token 配色。
|
||||
- 扩展 **语言自动识别**(PHP/Shell/INI/Nginx/Docker 等运维常见后缀与文件名)。
|
||||
- 新增 **Nginx** Monarch 语法;工具栏 **语法** 下拉可手动切换或选「自动」。
|
||||
- 启用括号配色、缩进参考线、语义高亮、选区高亮等 Monaco 选项。
|
||||
|
||||
## 动机
|
||||
|
||||
用户反馈编辑器需要语法高亮;统一 Monaco 初始化与主题,避免 Teleport 面板下样式冲突导致「全灰」观感。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `frontend/src/monaco/initMonaco.ts` | 统一初始化、编辑器默认选项 |
|
||||
| `frontend/src/monaco/nexusThemes.ts` | 自定义主题 |
|
||||
| `frontend/src/utils/editorLanguage.ts` | 语言检测与下拉项 |
|
||||
| `frontend/src/components/FileEditorWorkbench.vue` | 语法选择器、接入新主题 |
|
||||
| `frontend/src/components/MonacoEditor.vue` | 同步接入 |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
仅前端构建部署。
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开文件管理 → 编辑 `.php` / `.sh` / `.json` / `nginx.conf` → 应看到彩色语法。
|
||||
2. 工具栏「语法」切换为其他语言 → 高亮规则即时更新。
|
||||
3. 主题切换按钮 → 深色/浅色主题下对比度正常。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 文件管理:300KB 预加载与编辑上限
|
||||
|
||||
**日期**:2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
进入目录后,对 ≤300KB 的普通文件在后台并发预读;双击编辑/查看时优先使用内存缓存。超过 300KB 的文件在客户端直接提示,不再发起 read-file。
|
||||
|
||||
## 动机
|
||||
|
||||
用户要求「文件要预加载 300K」,减少双击打开编辑器时的等待,并统一浏览器内可读文件大小上限。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/utils/filePreload.ts`(新建)
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
|
||||
## 行为说明
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| 预加载/编辑上限 | 300 × 1024 字节 |
|
||||
| 每目录最多预加载文件数 | 40 |
|
||||
| 并发预读 | 4 |
|
||||
| read-file 请求 | 显式传 `max_size: 307200` |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 仅前端构建部署,无需数据库迁移
|
||||
- 后端 `read-file` 仍支持至 5MB(schema 未改),由前端限制传参
|
||||
|
||||
## 验证
|
||||
|
||||
1. 进入含小文件的目录,状态栏显示「N 个 ≤300KB 可预加载」
|
||||
2. 稍等后双击小文件,编辑器应更快打开(Network 无重复 read-file)
|
||||
3. 双击 >300KB 文件应 toast 提示无法编辑/查看
|
||||
@@ -0,0 +1,37 @@
|
||||
# 2026-06-01 文件管理浏览修复
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
修复 SPA 文件管理页(`#/files`)目录列表为空、breadcrumb 无法点击、路径不一致等问题;统一 `/api/sync/browse` 响应为 `items` 字段。
|
||||
|
||||
## 2026-06-01 追加(全面巡检)
|
||||
- 面包屑:Vuetify 4 `#item` 槽 + `onBreadcrumbClick`,当前层级禁用
|
||||
- 新增 `remotePath.ts`:路径规范化、拼接、面包屑数据、上级目录
|
||||
- 工具栏「上级」按钮;未选服务器时提示
|
||||
- `parseBrowseResponse`:空目录 `items: []` 不再误判
|
||||
- 预览截断 800 字;批量删除反馈成功/失败数
|
||||
|
||||
## 动机
|
||||
- 后端 `POST /api/sync/browse` 返回 `{ entries: [...] }`(字段 `is_dir`、`perms`)
|
||||
- 前端 `FilesPage.vue` 仅读取 `res.items`,且条目需要 `type: directory|file`
|
||||
- 生产环境表现为「暂无文件」,浏览/进入子目录均不可用
|
||||
|
||||
## 涉及文件
|
||||
- `server/api/sync_v2.py` — 返回 `items`(含 `type`/`permissions`),过滤 `.` / `..`
|
||||
- `frontend/src/utils/fileBrowse.ts` — 解析 `items`,兼容旧版 `entries`
|
||||
- `frontend/src/pages/FilesPage.vue` — 使用 `parseBrowseResponse`,展示 SSH `error`
|
||||
- `frontend/src/types/api.ts` — `BrowseResponse` 类型与后端对齐
|
||||
- `frontend/src/pages/FilesPage.vue` — 表格 `return-object`、面包屑/上级/路径规范化
|
||||
- `frontend/src/utils/remotePath.ts` — 远程路径与面包屑工具
|
||||
- `docs/audit/2026-06-01-files-page-inspection.md` — 巡检审计
|
||||
|
||||
## 迁移 / 重启
|
||||
- 需重新构建并部署前端至 `web/app/`
|
||||
- 需部署后端并 `supervisorctl restart nexus`(或仅前端亦可临时兼容旧 API)
|
||||
|
||||
## 验证方式
|
||||
1. 打开 `app/#/files`,选择一台可 SSH 的服务器
|
||||
2. 应看到 `/` 下文件列表;点击目录可进入
|
||||
3. `curl -H "Authorization: Bearer …" -d '{"server_id":1,"path":"/"}' …/api/sync/browse` 响应含 `items` 数组
|
||||
@@ -0,0 +1,33 @@
|
||||
# 2026-06-01 文件浏览目录 sudo 提权回退
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
`POST /api/sync/browse` 与写文件/chmod 一致,在 `files_elevation=auto_sudo`(默认)且 `ls` 遇 Permission denied 时自动 `sudo -n bash -c`。
|
||||
|
||||
## 动机
|
||||
- 非 root SSH 用户无法直接 `ls /www/wwwroot`,前端表现为「暂无文件」
|
||||
- 浏览此前仅用 `exec_ssh_command`,**从未走 sudo**;与「去掉 sudo」无关,应**启用**提权而非关闭
|
||||
|
||||
## 涉及文件
|
||||
- `server/api/sync_v2.py` — `browse_directory`
|
||||
- `frontend/src/utils/fileMode.ts` — 提示文案含「浏览」
|
||||
|
||||
## 迁移 / 重启
|
||||
- 需 `supervisorctl restart nexus` 部署后端
|
||||
|
||||
## 验证方式
|
||||
1. 服务器 SSH 用户为非 root,`files_elevation` 为 `auto_sudo`(默认)
|
||||
2. 目标机已安装 `docs/deploy/nexus-files-sudoers.example`(含 bash)
|
||||
3. 打开 `#/files?server_id=8`,应列出 `/www/wwwroot` 内容
|
||||
|
||||
## 运维说明(不要关掉 sudo)
|
||||
|
||||
| 策略 | 适用 |
|
||||
|------|------|
|
||||
| `auto_sudo`(推荐) | 先普通 `ls`,失败再 sudo |
|
||||
| `off` | 仅当 SSH 已是 root 或目录对当前用户可读 |
|
||||
| `always_sudo` | 所有命令先 sudo;sudoers 未配全时反而更易失败 |
|
||||
|
||||
若 `always_sudo` 且 sudo 未配好:在「服务器」编辑里改回 **自动提权 (auto_sudo)**,并安装 sudoers 示例。
|
||||
@@ -0,0 +1,39 @@
|
||||
# Changelog — 文件管理提权检测与 elevation 策略
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 功能(Phase 4) |
|
||||
|
||||
## 摘要
|
||||
|
||||
新增 `GET /api/servers/{id}/files-capability` 探测目标机免密 sudo 与 chmod/chown 白名单;服务器级 `files_elevation`(`off` / `auto_sudo` / `always_sudo`)存入 `extra_attrs` 并驱动 `remote_shell` 提权行为;前端权限对话框与服务器编辑表单对接。
|
||||
|
||||
## 动机
|
||||
|
||||
完成文件管理设计文档 Phase 4:运维可在改权限前检测提权能力,并按服务器关闭/自动/始终 sudo。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/utils/files_elevation.py`(新建)
|
||||
- `server/infrastructure/ssh/files_capability.py`(新建)
|
||||
- `server/infrastructure/ssh/remote_shell.py`
|
||||
- `server/api/servers.py`、`server/api/schemas.py`
|
||||
- `frontend/src/components/FilePermissionDialog.vue`
|
||||
- `frontend/src/pages/FilesPage.vue`、`ServersPage.vue`
|
||||
- `frontend/src/types/api.ts`
|
||||
- `tests/test_files_elevation.py`(新建)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 无 DB 迁移;默认 `auto_sudo`(未配置 `extra_attrs` 时等同历史行为)。
|
||||
- 需重启 Nexus 后端;前端需重新 `vite build` 部署。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_files_elevation.py tests/test_unix_ls.py -q
|
||||
ruff check server/utils/files_elevation.py server/infrastructure/ssh/files_capability.py server/infrastructure/ssh/remote_shell.py
|
||||
```
|
||||
|
||||
浏览器:服务器编辑 → 文件管理提权;`#/files` → 右键修改权限 →「检测提权能力」。
|
||||
@@ -0,0 +1,48 @@
|
||||
# 文件管理:剪切 / 复制 / 粘贴
|
||||
|
||||
**日期**:2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
实现 v2 设计中的远程文件剪贴板:`POST /api/sync/file-clipboard`(`cp -r` / `mv -t`),前端 Ctrl+C/X/V、工具栏粘贴、右键菜单与状态芯片。
|
||||
|
||||
## 动机
|
||||
|
||||
用户要求补齐此前因缺少后端 API 未实现的剪切/复制/粘贴。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/api/schemas.py` — `FileClipboardApply`
|
||||
- `server/api/remote_path_validation.py`(新建)
|
||||
- `server/api/sync_v2.py` — `/file-clipboard`
|
||||
- `frontend/src/composables/useFilesClipboard.ts`(新建)
|
||||
- `frontend/src/composables/useFilesHotkeys.ts`
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `tests/test_remote_path_validation.py`(新建)
|
||||
- `docs/design/specs/2026-06-01-files-clipboard-transfer-design.md`
|
||||
|
||||
## 安全
|
||||
|
||||
- 绝对路径 + 禁止 `..`
|
||||
- 禁止粘贴到源目录子路径
|
||||
- 审计 `file_copy` / `file_move`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
需部署**后端 + 前端**(新 API)。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_remote_path_validation.py -q
|
||||
```
|
||||
|
||||
浏览器:复制→进入另一目录→粘贴;剪切→粘贴后源消失;跨服务器粘贴应提示。
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-01 补充:跨目录粘贴
|
||||
|
||||
- 粘贴目标:默认当前目录;**单选一个文件夹**时粘贴到该文件夹;**右键目录**「粘贴到 xxx」;**目录树右键**粘贴到该节点
|
||||
- 状态栏显示剪贴板内容与粘贴目标路径
|
||||
- 前端 `validatePasteTarget` 与后端一致,禁止粘贴到源目录内部
|
||||
@@ -0,0 +1,40 @@
|
||||
# 文件编辑器:分屏 IDE 面板(替代默认全屏)
|
||||
|
||||
**日期**:2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
按 `docs/design/specs/2026-06-01-files-editor-panel-design.md` 与历史 v2 / 2026-05-30 IDE 设计,将 Monaco 从全屏 `v-dialog` 改为文件页底部内嵌面板:多标签、状态栏、最小化标签条、可选全屏。
|
||||
|
||||
## 动机
|
||||
|
||||
用户反馈「编辑器现在是全屏」,与设计文档(分屏 + 多文件标签 + 状态栏)不一致。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/components/FileEditorWorkbench.vue`(新建)
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `docs/design/specs/2026-06-01-files-editor-panel-design.md`
|
||||
- `frontend/src/components/MonacoEditor.vue`(保留未删,暂无引用)
|
||||
|
||||
## 行为
|
||||
|
||||
| 操作 | 效果 |
|
||||
|------|------|
|
||||
| 双击/编辑 | 底部打开面板,文件表缩至约半屏 |
|
||||
| 多文件 | 标签切换,各文件独立修改状态 |
|
||||
| 状态栏 | 行、列、语言、远程路径 |
|
||||
| 最小化 | 底部芯片条,点击恢复 |
|
||||
| 全屏按钮 | 覆盖视口编辑,可退出 |
|
||||
| Ctrl+S / Esc | 保存 / 退出全屏或关闭面板 |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
仅前端构建部署。
|
||||
|
||||
## 验证
|
||||
|
||||
1. 打开编辑器后仍可见上方文件列表并可进目录
|
||||
2. 打开两个文件,标签切换内容正确
|
||||
3. 移动光标,状态栏行列更新
|
||||
4. 最小化、全屏、关闭未保存确认
|
||||
@@ -0,0 +1,31 @@
|
||||
# 2026-06-01 文件管理:浮动可拖动编辑器 + 目录树内置
|
||||
|
||||
## 摘要
|
||||
|
||||
- **文件管理页**不再显示左侧目录树,文件列表全宽展示。
|
||||
- **编辑器**改为 `Teleport` 到 `body` 的浮动面板,支持标题栏拖动、右下角缩放,位置与尺寸持久化到 `localStorage`。
|
||||
- **目录树**移入 `FileEditorWorkbench` 左侧栏;在树中导航会同步文件管理页的当前路径;树右键仍可触发粘贴目标。
|
||||
|
||||
## 动机
|
||||
|
||||
用户反馈:① 应是「编辑器带文件树」,而非「文件管理带文件树」;② 编辑器应可浮动、可拖动,而非固定在页面底部内嵌分屏。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
| 文件 | 变更 |
|
||||
|------|------|
|
||||
| `frontend/src/components/FileEditorWorkbench.vue` | 浮动面板、内置 `FileDirectoryTree`、`path-change` / `paste-target` |
|
||||
| `frontend/src/components/FileDirectoryTree.vue` | 新增 `embedded` 紧凑模式 |
|
||||
| `frontend/src/composables/useFloatingPanel.ts` | 拖动/缩放与 localStorage 持久化 |
|
||||
| `frontend/src/pages/FilesPage.vue` | 移除左侧树与 `files-browser--split` |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 仅前端构建部署,无需后端重启。
|
||||
|
||||
## 验证方式
|
||||
|
||||
1. 打开 `#/files`,选择服务器并浏览目录——页面无左侧树,列表全宽。
|
||||
2. 点击「编辑」——出现居中浮动编辑器,左侧有目录树,可拖动标题栏移动、拖右下角缩放。
|
||||
3. 在编辑器目录树点击文件夹——文件管理页路径与列表同步更新。
|
||||
4. 最小化后底部出现标签 dock;全屏按钮仍可铺满视口。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 2026-06-01 文件编辑器 Monaco 适配修复
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
修复文件管理页 Monaco 全屏编辑器空白/高亮失效、切换文件内容不刷新、保存失败等问题。
|
||||
|
||||
## 动机
|
||||
- Vite 打包未配置 `MonacoEnvironment` Worker → 语法高亮与语言服务异常
|
||||
- 仅监听 `visible`,打开另一文件时仍显示旧内容
|
||||
- 后端 `write-file` 使用 `echo|base64` 受命令行长度限制,大文件或保存易失败
|
||||
|
||||
## 涉及文件
|
||||
- `frontend/src/monaco/setupMonacoEnv.ts` — Vite Worker 配置(新建)
|
||||
- `frontend/src/components/MonacoEditor.vue` — 布局、换行归一化、Esc/Ctrl+S、ResizeObserver
|
||||
- `frontend/src/pages/FilesPage.vue` — `editorSessionKey` 强制按路径重建编辑器
|
||||
- `server/api/sync_v2.py` — `write-file` 改为 SFTP 写入
|
||||
|
||||
## 迁移 / 重启
|
||||
- 前端构建部署 `web/app/`
|
||||
- 后端需 `supervisorctl restart nexus`
|
||||
|
||||
## 验证方式
|
||||
1. 双击 `.php` / `.js` 文件,编辑器有语法高亮、可编辑
|
||||
2. 修改后 Ctrl+S 保存,刷新目录内容已更新
|
||||
3. 连续打开两个不同文件,内容互不串台
|
||||
4. 关闭时未保存会提示确认
|
||||
@@ -0,0 +1,40 @@
|
||||
# Changelog:文件管理归属列 + 权限对话框 + 统一 sudo 回退
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 设计 | `docs/design/specs/2026-06-01-files-root-ownership-ui-design.md` |
|
||||
|
||||
## 变更摘要
|
||||
|
||||
- 新增 `remote_shell.py`:`exec_ssh_command_with_fallback`(`sudo -n` 自动重试)
|
||||
- `browse` 返回 `owner` / `group` / `mode_octal`
|
||||
- `chmod` API 支持可选 `owner`/`group`,走 sudo 回退;审计 `file_permission_change`
|
||||
- `file-ops`、剪贴板复制/移动、解压接入 sudo 回退
|
||||
- 前端:「归属」列 + `FilePermissionDialog`(预设 644/755 等)
|
||||
- 运维示例:`docs/deploy/nexus-files-sudoers.example`
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/infrastructure/ssh/remote_shell.py`(新)
|
||||
- `server/utils/unix_ls.py`(新)
|
||||
- `server/infrastructure/ssh/remote_archive.py`
|
||||
- `server/api/sync_v2.py`
|
||||
- `server/api/schemas.py`
|
||||
- `frontend/src/components/FilePermissionDialog.vue`(新)
|
||||
- `frontend/src/utils/fileMode.ts`(新)
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `frontend/src/types/api.ts`
|
||||
- `frontend/src/utils/fileBrowse.ts`
|
||||
- `tests/test_unix_ls.py`(新)
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_unix_ls.py tests/test_posix_paths.py -q
|
||||
ruff check server/infrastructure/ssh/remote_shell.py server/utils/unix_ls.py server/api/sync_v2.py
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
- 后端 + 前端构建上传;目标机按需安装 `nexus-files.sudoers.example`
|
||||
@@ -0,0 +1,40 @@
|
||||
# 2026-06-01 文件管理页迭代 v2
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
`#/files` 第二轮迭代:修复下载、预览对话框、URL 状态、筛选/拖拽上传、符号链接展示。
|
||||
|
||||
## 动机
|
||||
- 下载 API 返回二进制流,原 `http.post` JSON 解析导致「下载已开始」但实际失败
|
||||
- 预览用 snackbar 无法阅读;刷新丢失当前路径
|
||||
- 对标历史 file-manager v2 规划中的状态栏、拖拽、筛选能力
|
||||
|
||||
## 涉及文件
|
||||
- `frontend/src/api/index.ts` — `fetchAuthed` / `http.download`
|
||||
- `frontend/src/utils/fileDownload.ts` — 浏览器保存 Blob
|
||||
- `frontend/src/pages/FilesPage.vue` — UI/交互迭代
|
||||
- `frontend/src/utils/remotePath.ts` — `validatePathSegment`
|
||||
- `frontend/src/utils/fileBrowse.ts` / `types/api.ts` — symlink
|
||||
- `server/api/sync_v2.py` — browse 返回 `symlink` + `link_target`
|
||||
- `docs/design/plans/2026-06-01-files-page-iteration.md`
|
||||
|
||||
## 迁移 / 重启
|
||||
- 前端构建部署至 `web/app/`
|
||||
- 后端 `sync_v2.py` 需部署并 `supervisorctl restart nexus`(symlink 字段)
|
||||
|
||||
## 追加(默认路径)
|
||||
- 文件管理器初始/切换服务器时默认打开 `/www/wwwroot`(`DEFAULT_FILES_ROOT`)
|
||||
|
||||
## 追加(交互)
|
||||
- 进入目录/符号链接:单击行即可(不再要求双击)
|
||||
- 普通文件:双击行打开 Monaco 编辑
|
||||
|
||||
## 验证方式
|
||||
1. 选服务器进入子目录,URL 含 `?server_id=&path=`,刷新后位置保持
|
||||
2. 双击目录进入;面包屑/上级可用
|
||||
3. 下载文件可本地打开
|
||||
4. 查看文本文件弹出预览对话框
|
||||
5. 拖拽文件到列表区域触发上传
|
||||
6. 筛选框过滤当前目录文件名
|
||||
@@ -0,0 +1,24 @@
|
||||
# 2026-06-01 文件管理行点击与自动选服
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
修复 `#/files` 无法进入子目录、打开页无列表等体验问题:Vuetify 4 改用 `row-props` 绑定行点击;进入页自动选中服务器并浏览默认目录。
|
||||
|
||||
## 动机
|
||||
- Vuetify 4 已移除 `v-data-table` 的 `@click:row` / `@dblclick:row` / `@contextmenu:row`,原事件静默失效,单击目录无法进入
|
||||
- 未带 `?server_id=` 时不会调用 `browse()`,用户看到空表且多数按钮禁用
|
||||
|
||||
## 涉及文件
|
||||
- `frontend/src/pages/FilesPage.vue` — `fileRowProps`、自动选服、`sessionStorage` 记忆上次服务器
|
||||
- `frontend/src/composables/useServerList.ts` — `http.getList` 兼容列表响应
|
||||
|
||||
## 迁移 / 重启
|
||||
- 需重新构建并部署前端至 `web/app/`(建议执行 `deploy/prune_frontend_assets.py`)
|
||||
|
||||
## 验证方式
|
||||
1. 打开 `https://…/app/#/files`(无需 URL 参数)
|
||||
2. 应自动选中服务器并列出 `/www/wwwroot`(或站点根)内容
|
||||
3. 单击目录行进入子目录;双击普通文件打开编辑器
|
||||
4. 右键行弹出上下文菜单
|
||||
@@ -0,0 +1,38 @@
|
||||
# Changelog — 文件管理二期(递归 chmod + 不可读提示)
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 功能 |
|
||||
|
||||
## 摘要
|
||||
|
||||
- `POST /sync/chmod` 支持 `recursive`:仅目录、`test -d` 校验,禁止对 `/` 等系统路径递归;超时 300s。
|
||||
- 权限对话框:目录可勾选「同时修改子项」并二次确认。
|
||||
- 文件页:根据 `files-capability`、读失败与 ls 权限显示顶部提示条;不可读文件行内 `mdi-shield-lock`。
|
||||
|
||||
## 动机
|
||||
|
||||
完成设计文档 §4.5 二期与 L1 不可读场景可观测性。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/utils/files_chmod_policy.py`(新建)
|
||||
- `server/api/schemas.py`、`server/api/sync_v2.py`
|
||||
- `frontend/src/utils/fileMode.ts`
|
||||
- `frontend/src/components/FilePermissionDialog.vue`
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `tests/test_file_permissions.py`
|
||||
- `docs/design/plans/2026-06-01-files-phase2-recursive-read-hint.md`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 无 DB 变更;需重启后端并重新构建前端。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_file_permissions.py -q
|
||||
```
|
||||
|
||||
浏览器:选非 root 服务器 → 顶栏提示;目录 chmod 勾选递归;打开无读权限文件触发提示更新。
|
||||
@@ -0,0 +1,34 @@
|
||||
# Changelog — 文件管理 Phase 5 加固
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 加固 / 测试 |
|
||||
|
||||
## 摘要
|
||||
|
||||
完成设计文档 Phase 5:`ls -la` 解析抽到 `unix_ls.py` 并补单测;browse 优先 `--time-style=long-iso`;`read-file` 走 `exec_ssh_command_with_fallback`;`remote_write_bytes` 尊重 `files_elevation`(off 不 sudo、always 跳过 SFTP 直走 sudo tee)。
|
||||
|
||||
## 动机
|
||||
|
||||
统一可测的列表解析、可排序的 ISO 时间、只读文件在 root 属主下可读,并与服务器提权策略一致。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/utils/unix_ls.py`
|
||||
- `server/api/sync_v2.py`(browse、read-file、write-file、upload)
|
||||
- `server/infrastructure/ssh/asyncssh_pool.py`
|
||||
- `tests/test_file_permissions.py`(新建)
|
||||
- `tests/test_unix_ls.py`
|
||||
- `docs/audit/2026-06-01-posix-path-batch-review.md`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 无 DB 变更;需重启后端;前端无必改项(时间列展示 ISO 字符串更易排序)。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_file_permissions.py tests/test_unix_ls.py tests/test_files_elevation.py -q
|
||||
ruff check server/utils/unix_ls.py server/infrastructure/ssh/asyncssh_pool.py
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# 2026-06-01 文件管理 sudo 提示与白名单示例
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
区分「无免密 sudo / 白名单不全 / 已就绪」三种提示;sudoers 示例补充 `bash`(与 `sudo -n bash -c` 提权路径一致)。
|
||||
|
||||
## 动机
|
||||
- 用户看到「部分操作将自动尝试 sudo」但操作仍失败:多为目标机未装 sudoers 或白名单缺少 `bash`
|
||||
- 原示例未包含 `bash`,与 `remote_shell.exec_ssh_command_with_fallback` 行为不一致
|
||||
|
||||
## 涉及文件
|
||||
- `frontend/src/utils/fileMode.ts`
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `docs/deploy/nexus-files-sudoers.example`
|
||||
|
||||
## 迁移 / 重启
|
||||
- 仅前端文案 + 文档;目标机需按新示例更新 `/etc/sudoers.d/nexus-files` 后无需重启 Nexus
|
||||
|
||||
## 验证方式
|
||||
1. 非 root SSH + 无 sudo:文件页顶部红色提示,含「免密 sudo」与示例路径
|
||||
2. 有 `sudo -n true` 但无 bash:黄色提示「白名单可能不完整」
|
||||
3. 配置完整后:无多余横幅(除非目录内有不可读文件)
|
||||
@@ -0,0 +1,45 @@
|
||||
# 文件管理 v2 功能补齐
|
||||
|
||||
**日期**:2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
补齐历史设计(`2026-05-18-file-manager-v2`、`2026-05-30-monaco-ide-editor`)中 SPA 尚未落地的能力:目录树、新建文件、压缩/解压、排序与类型筛选、增强右键菜单与快捷键。
|
||||
|
||||
## 动机
|
||||
|
||||
用户要求「尚未实现的全部要去做」。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/components/FileDirectoryTree.vue`(新建)
|
||||
- `frontend/src/composables/useFilesHotkeys.ts`(新建)
|
||||
- `frontend/src/utils/fileSort.ts`(新建)
|
||||
- `frontend/src/pages/FilesPage.vue`
|
||||
- `docs/design/plans/2026-06-01-files-v2-complete.md`
|
||||
- `docs/design/specs/2026-06-01-files-editor-panel-design.md`
|
||||
|
||||
## 功能列表
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 目录树 | 左侧懒加载子目录,点击跳转 |
|
||||
| 新建文件 | `write-file` 空内容,并打开编辑器 |
|
||||
| 压缩 | 多选或右键,`/sync/compress` tar.gz/zip |
|
||||
| 解压 | 归档文件,`/sync/decompress` |
|
||||
| 排序 | 名称 / 大小 / 时间 |
|
||||
| 类型筛选 | conf/log/sh/py/json/yml/txt 等 |
|
||||
| 右键 | 打开目录、复制路径、终端、压缩、解压 |
|
||||
| 快捷键 | F5 刷新、F2 重命名、Del 删除、Backspace 上级、Ctrl+A 全选 |
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
仅前端部署。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `#/files` 左侧目录树展开/点击路径
|
||||
2. 新建文件 → 列表出现且编辑器打开
|
||||
3. 多选压缩、对 .tar.gz 解压
|
||||
4. 排序与类型筛选生效
|
||||
5. 右键复制路径、F5 刷新
|
||||
@@ -0,0 +1,35 @@
|
||||
# Changelog — 全站功能自动化验收
|
||||
|
||||
**日期**: 2026-06-01
|
||||
|
||||
## 摘要
|
||||
|
||||
按用户要求由 Agent 对**每项可安全自动化的功能**做全量验证(非抽测):生产 API 53 项 + Playwright 15 路由;修复 `/health/detail` 401。
|
||||
|
||||
## 动机
|
||||
|
||||
用户要求「每个功能自己都试」,不接受抽测式交付。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `server/main.py` — `/health/detail` 纳入 DB 中间件
|
||||
- `tests/test_api.py` — `run_tests()`,避免 import 副作用
|
||||
- `tests/test_full_site_api.py`(新)— 28 项扩展 API
|
||||
- `frontend/e2e/full-acceptance.spec.mjs`(新)— 14 路由 UI
|
||||
- `frontend/playwright.config.mjs`、`frontend/package.json` — `@playwright/test`、`test:e2e`
|
||||
- `scripts/run_full_e2e.sh`(新)
|
||||
- `docs/reports/2026-06-01-full-acceptance-report.md`(新)
|
||||
- `.cursorrules`、`docs/project/testing-workflow-ai-first.md`(流程,前序提交)
|
||||
|
||||
## 验证
|
||||
|
||||
- 生产:`test_api.py` 25/25、`test_full_site_api.py` 28/28
|
||||
- 本地:`npx playwright test` 15/15 vs api.synaglobal.vip
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 已 `supervisorctl restart nexus` 部署 `main.py` 修复
|
||||
|
||||
## 回滚
|
||||
|
||||
- 还原 `server/main.py` DbSessionMiddleware 条件并重启
|
||||
@@ -0,0 +1,27 @@
|
||||
# Changelog — 主待办长计划文档
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 文档 |
|
||||
|
||||
## 摘要
|
||||
|
||||
新增 `docs/project/2026-06-01-master-backlog-plan.md`,将 POSIX 审计延续、文件管理 L1~L4 验收、部署门控、8 步审计、全站 T1~T8 与增强项统一为可勾选任务表。
|
||||
|
||||
## 动机
|
||||
|
||||
避免多文档分叉;用户要求长计划一次性收录全部待办。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `docs/project/2026-06-01-master-backlog-plan.md`(新建)
|
||||
- `docs/project/AI-HANDOFF-2026-05-23.md`(增加 1b 指针)
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
无。
|
||||
|
||||
## 验证
|
||||
|
||||
打开主计划,确认 Phase A~G 任务 ID 完整、进度总表与已完成清单不矛盾。
|
||||
@@ -0,0 +1,25 @@
|
||||
# Changelog — 主计划长任务首轮执行
|
||||
|
||||
| 项 | 内容 |
|
||||
|----|------|
|
||||
| 日期 | 2026-06-01 |
|
||||
| 类型 | 执行 / 部署 |
|
||||
|
||||
## 摘要
|
||||
|
||||
执行 [2026-06-01-master-backlog-plan.md](../project/2026-06-01-master-backlog-plan.md):Phase A 单测 44 通过;提交并推送 `d93c518`~`e704b01`;生产拉取、`supervisorctl restart nexus`、health `ok`;门控 6/7(bandit 报生产机 `server/server/` 旧路径 HIGH);修复 `pre_deploy_check.sh` 默认 `DEPLOY_DIR` 与最新 audit 选取。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- 79+ 文件功能提交;`deploy/pre_deploy_check.sh`;`docs/audit/2026-06-01-master-execution-audit.md`;主计划进度更新
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 生产已 `supervisorctl restart nexus`
|
||||
- 前端 `npm run build` 待服务器 Node 环境确认
|
||||
|
||||
## 验证
|
||||
|
||||
- 本地:`pytest` 44 passed;`ruff`;`import server.main`
|
||||
- 生产:`curl http://127.0.0.1:8600/health` → `ok`
|
||||
- 门控:`bash deploy/pre_deploy_check.sh` → 6/7(Security 待清 `server/server` 重复目录或 bandit 路径)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 2026-06-01 全功能验收脚本 run_nexus_acceptance
|
||||
|
||||
## 日期
|
||||
2026-06-01
|
||||
|
||||
## 变更摘要
|
||||
新增统一验收入口 `scripts/run_nexus_acceptance.py`:自动跑核心/扩展 API 测试、按功能模块汇总 PASS/FAIL,可选单元测试与 Playwright UI。
|
||||
|
||||
## 动机
|
||||
用户需要一键脚本识别各功能是否正常,而非分散执行 `test_api.py` 与 Playwright。
|
||||
|
||||
## 涉及文件
|
||||
- `scripts/run_nexus_acceptance.py` — 主入口
|
||||
- `scripts/run_full_acceptance.sh` — WSL/生产包装(API + 可选 UI)
|
||||
- `tests/acceptance_catalog.py` — 功能模块与检查项映射
|
||||
|
||||
## 迁移 / 重启
|
||||
无
|
||||
|
||||
## 2026-06-01 修复
|
||||
|
||||
- `tests/test_full_site_api.py`:`api_test_plain_text` / `api_test` 与 `test_api.py` 对齐
|
||||
|
||||
## 验证方式
|
||||
```bash
|
||||
# 本地(后端需已启动,.env 含测试账号)
|
||||
python scripts/run_nexus_acceptance.py
|
||||
|
||||
# 含 UI(需 frontend npm install + NEXUS_E2E_PASSWORD)
|
||||
python scripts/run_nexus_acceptance.py --with-ui
|
||||
|
||||
# 含单元测试(无需后端)
|
||||
python scripts/run_nexus_acceptance.py --with-unit --skip-api
|
||||
|
||||
# JSON 报告
|
||||
python scripts/run_nexus_acceptance.py --json reports/acceptance.json
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user