Commit Graph

83 Commits

Author SHA1 Message Date
qinglong 6c6c88b22a fix: 插件API路由统一加面板前缀(/SenSu),解决反代后插件页面空白 2026-06-14 20:14:22 +08:00
qinglong d626a0631a fix: RotatingFileHandler强制UTF-8编码避免Windows GBK日志乱码 2026-06-14 19:06:32 +08:00
qinglong 2c5cd57ad4 fix: venv改为懒加载,兼容嵌入式CPython无venv模块 2026-06-14 19:02:27 +08:00
qinglong f40b87ea52 fix: 仪表盘版本号从version.json读取,修复硬编码v0.6.0 2026-06-14 16:24:41 +08:00
qinglong 3f5b01f817 refactor: 版本号从version.json读取 配置模板删version字段 2026-06-14 16:03:04 +08:00
qinglong b708c210c7 fix: 静态HTML添加no-cache头 + app.js→app_v2.js 2026-06-14 11:55:06 +08:00
qinglong ae553b564d fix: 静态文件no-cache + app.js重命名防缓存 2026-06-14 11:54:18 +08:00
qinglong f532a63a49 fix: 插件页面强制no-cache + app.js v0712 2026-06-14 11:45:07 +08:00
qinglong cb5f900c39 feat: 更新页面实时日志 — SSE轮询 + logs/updates_logs 文件记录 2026-06-14 10:45:08 +08:00
qinglong b3a6f73db4 fix: 更新后同步版本号到base_config.yaml 2026-06-14 10:42:01 +08:00
qinglong 27115942ea fix: InitService自动合并模板新增字段(解决旧config缺branch问题) 2026-06-14 10:39:03 +08:00
qinglong fb8bf393f2 fix: UpdateService默认分支 main→dev (version.json在dev) 2026-06-14 10:37:39 +08:00
qinglong c65a9916c2 fix: Windows负载 — psutil.getloadavg() + 全0显示N/A 2026-06-14 10:32:51 +08:00
qinglong 7fe9e4f6cd fix: 插件加载失败计入统计 — LOAD_FAILURES + 合并fail_details 2026-06-14 10:26:36 +08:00
qinglong 00c97e4af2 fix: PluginService自动从.example模板创建config.yaml (Windows兼容)
根因: Sentinel的config.yaml被gitignore, 首次运行时
load_plugin()要求config.yaml存在才能继续, 但Sentinel的
自动复制逻辑在initialize()中, 太晚了。

修复: PluginService.load_plugin()检测缺失时自动复制模板

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-14 10:24:08 +08:00
qinglong cb6beca067 Revert "fix: Windows分区设备名 — 用盘符(C:)替代设备路径"
This reverts commit 7cdae4d954.
2026-06-14 10:02:09 +08:00
qinglong 7cdae4d954 fix: Windows分区设备名 — 用盘符(C:)替代设备路径 2026-06-14 09:58:50 +08:00
qinglong 528410d3e9 feat: 磁盘分区列 — 多分区/进度条/overflow/过滤虚拟FS
SystemInfoCollector:
- _get_partitions(): psutil disk_partitions + 过滤虚拟FS
- 跳过 tmpfs/devtmpfs/proc/sysfs/cgroup等22种
- 无psutil时 fallback到根分区

前端4列: 160px 1fr 0.8fr 1fr
- col-disk: 分区名/挂载点/进度条/百分比
- max-height:200px overflow:auto
- 跨平台兼容

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-14 09:31:52 +08:00
qinglong b1b7cd0563 feat: 横版col-extra — 负载/SWAP/磁盘/温度/流量/进程 6项指标
SystemInfoCollector 新增:
- swap: SWAP用量 (psutil + /proc/meminfo fallback)
- disk: 磁盘用量
- temperature: CPU温度 (psutil + /sys/class/thermal fallback)

Sentinel col-extra 显示:
  负载 | SWAP | 磁盘 | 温度 | 累计流量 | 进程RSS

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-14 09:19:37 +08:00
qinglong 9818d1c965 fix: UpdateService启动时立即设置current_version 2026-06-14 09:08:41 +08:00
qinglong 70695b171e fix: 版本号显示 — 始终设 current_version + 顶栏按钮显示版本 2026-06-13 21:34:21 +08:00
qinglong 94d56d8210 fix: 版本检查 — 后端设置 checked 字段 + 前端 info 合并显示
- UpdateService.status 增加 checked/current_version/latest_version
- _check_version 成功后设置 checked=True
- 设置页 update-notes 合并版本信息和时间戳 (替代 innerHTML 覆盖)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 21:29:35 +08:00
qinglong 67d0f00525 fix: 版本更新 — 自动检查 + 手动备份 + 创建备份按钮
1. 设置页加载时自动触发版本检查
2. POST /api/updates/backup — 手动创建备份
3. 备份管理区新增「+ 创建备份」按钮
4. 顶栏版本按钮: checkTopUpdate 每10min轮询

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 21:08:54 +08:00
qinglong 279ddfbdc9 fix: SystemMonitor.refresh() 移除不存在的 _refresh_stats 调用 2026-06-13 20:54:35 +08:00
qinglong 899bef7781 feat: 设备卡片显示系统名+版本 + 项目根 version.json
- SystemInfoCollector.platform 新增 release 字段 (内核版本)
- Sentinel 卡片每台设备显示: Linux 6.12.23 | aarch64
- 数据刷新时同步更新系统信息行
- 项目根 version.json: 本地版本清单

当前版本检测:
  UpdateService 每6h从 {repo}/raw/branch/{branch}/version.json 拉取
  对比 base_config.yaml 的 framework.version
  远程 > 本地 → 顶栏绿色按钮 + 设置页更新提示

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 20:47:22 +08:00
qinglong cc50ecb813 fix: Windows兼容 + Textual 8.x + MD3 SVG图标替代emoji
1. Windows: reuse_port 仅非win32平台启用
2. Textual 8.2.7: refresh() 加 **kwargs 兼容 repaint 参数
3. WebUI: 设置页/插件页 emoji → SVG (fill:currentColor 日夜间适配)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 20:42:22 +08:00
qinglong 9a9a2975de feat: WebUI 版本检测 — 顶栏按钮 + 设置页备份/回滚/更新管理
顶栏:
- 版本号按钮, 有更新时变绿色并显示新版本号
- 每10分钟自动检查

设置页新增「版本与更新」区域:
- 当前版本 + 更新状态徽章
- 检查更新 / 立即更新 按钮
- 备份列表 + 手动回滚

API:
- GET /api/updates/backups — 备份列表
- POST /api/updates/rollback — 执行回滚

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 20:39:28 +08:00
qinglong c20417ff93 feat: 更新服务 — ZIP下载/备份/回滚/重启
UpdateService:
- 分支感知: 从 base_config 读取 branch, 拼接 archive/{branch}.zip
- 定时检查: 每6h 拉取 version.json 比对版本
- 更新前自动打包备份 (zip, 保留最新5个)
- 保护用户配置: base_config.yaml / plugins/*/config.yaml / data/
- 子进程 pip install 依赖

API:
- GET /api/updates — 更新状态
- POST /api/updates/check — 手动检查
- POST /api/updates/apply — 执行更新

回滚:
- python main.py --rollback  — 恢复到最新备份
- 无备份时提示

重启:
- .restart_flag 信号文件 → 优雅退出 → systemd 拉起

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 20:32:42 +08:00
qinglong d0206121b1 fix: 统计卡片'安装失败'→'启动失败' + 合并 error_count 计数 2026-06-13 18:44:20 +08:00
qinglong b2021a45ce feat: 插件管理 — 导入安装 + 统计面板
新增 API:
- GET /api/plugins/stats — 统计 (总计/已启用/已停用/安装失败)
- POST /api/plugins/import — ZIP上传 或 Git clone 安装
  - 自动 pip install -r requirements.txt (子进程隔离)
  - 自动加载插件 (isolated 模式, 崩溃不影响框架)
  - 安装失败记录到 _IMPORT_FAILS

前端:
- 横版统计卡片 (总数/启用/停用/失败)
- ZIP 上传按钮 + Git URL 输入
- 安装状态实时反馈

安全:
- 所有端点 panel_auth 保护
- 子进程安装 (pip/git), 超时120s
- 仅接受 .zip 文件
- 目录冲突检测

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 18:39:38 +08:00
qinglong 1d0f227267 fix: API Key 删除/创建去掉 CSRF — panel_token HttpOnly 前端无法读取
SameSite=Lax cookie 已防跨站 POST/DELETE, 无需额外 CSRF header
前端 JS 无法读取 HttpOnly cookie, 无法设置 X-CSRF-Token

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 18:07:47 +08:00
qinglong b53410bc77 fix: Sentinel SSE 每次轮询都推送 (实时性) + isolation 默认 false + 文档
Sentinel:
- poll_interval 3s→2s, 每次轮询都推 SSE (不再等状态变化)
- 解决稳定状态下数据更新不实时问题

插件系统:
- 默认 isolation: false (避免插件 WebUI 不显示)
- example_plugin 显式设置 isolation: false
- 插件开发指南: settings 示例增加 isolation 说明

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 17:54:39 +08:00
qinglong 5e06c17b52 fix: WebSocket 鉴权 — Cookie 优先 (支持 HttpOnly)
根因: panel_token 是 HttpOnly cookie, JS getCookie() 读不到
→ 前端 WS ?token= 参数为空 → 认证失败 → 仪表盘无数据

修复:
- _ws_auth_wrapper: Cookie 优先 → query token 备用 → API Key
- 浏览器同源 WS 自动发送 Cookie, 无需 ?token=
- dashboard.js / logs.js 移除多余的 getCookie+?token=

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 17:45:58 +08:00
qinglong 557135c376 security: 配置文件模板化 — base_config.yaml 不再入库
- base_config.yaml → .gitignore (含真实密码不入库)
- 新增 base_config.yaml.example 模板 (password: CHANGE_ME)
- InitService: 检测缺失时自动从模板复制
- 首次启动自动创建配置文件

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 15:20:14 +08:00
qinglong 634496d975 security: Sentinel 路由鉴权 + API Key CSRF + SSRF 防护
修复:
- Sentinel 路由 require_auth=False → True (严重: 未认证可访问)
- API Key 创建/删除加 csrf_protect=True
- SSRF 防护: 测试连接仅允许 http/https scheme
- 插件网络鉴权: 已加载插件自动放行 (不再要求 plugin.network.access)
- Sentinel 权限列表补充 plugin.network.access

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 15:13:56 +08:00
qinglong 5e74b0aad7 feat: 🛡️ Sentinel 集群监控插件 + API Key 系统 + 框架设置页
== API Key 系统 ==
- services/web_panel/routes/apikeys.py: CRUD + 权限模板 + 过期控制
- panel_auth + _check_plugin_auth 双重验证 (Session → API Key)
- 模板: readonly(只读) / monitor(监控) / full(管理)
- 持久化到 SenSuDB + 过期自动清理

== WebUI 框架设置页 ==
- static/web_panel/pages/settings.html: API Key 管理界面
- 创建对话框 (名称/权限/TTL), 列表表格, 一键删除

== 热重载修复 ==
- PluginService._reload_plugin: 路由器冻结时跳过 (避免破坏已注册路由)

== 🛡️ Sentinel 插件 ==
- 多节点 SenSu 集群性能监控
- 后台 urllib+run_in_executor 轮询 (避免事件循环死锁)
- SSE 实时推送仪表盘
- 节点管理页 (增删改+连接测试+备注)
- 路径: /sentinel/api/nodes

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 15:05:38 +08:00
qinglong b92b1cc4fc security: API Key 权限范围 + 过期时间 + 用量追踪
增强:
- 权限模板: readonly / monitor / full
- 每 key 独立权限列表, 注入 user['perms']
- 可选过期时间 (ttl 参数)
- 过期 key 自动清理 (加载时 + 验证时)
- request_count 用量追踪
- 标记 is_api_key = True

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 14:27:07 +08:00
qinglong 9f6eb29820 feat: API Key 系统 — CRUD + panel_auth 集成 + 持久化
新增:
- services/web_panel/routes/apikeys.py — 创建/列表/删除 API Key
- panel_auth() 双重验证: Session Store → API Key fallback
- _check_plugin_auth() 支持 API Key
- API Key 持久化到 SenSuDB (config_kv 表)
- 格式: sk- + 48 hex chars
- 脱敏显示 (前8后4)
- 删除后立即失效 (401)

WebPanelManager 注册 apikeys 路由

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 14:25:55 +08:00
qinglong 2da52e80d0 security: P3+ — 面板密码哈希 + 上传类型白名单 + CSRF Token
面板密码安全:
- 支持哈希存储 (password_hash/password_salt)
- 登录使用 secrets.compare_digest 时序安全比较
- 回退明文兼容旧配置
- 默认密码 admin 时打印 CRITICAL 警告

文件上传安全:
- 拒绝危险扩展名: .exe/.dll/.so/.sh/.bat/.ps1 等
- 拒绝敏感文件名: .htaccess/Makefile/Dockerfile 等
- 上传时检查并返回 403

CSRF 防护:
- panel_auth(csrf_protect=True) 参数
- 写操作需 X-CSRF-Token header 匹配 session token
- 保护范围: 文件删除/写入/创建/上传/重命名 + 项目启停 + 代理管理

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 14:07:38 +08:00
qinglong ff02b2e16d security: P3 — 插件隔离默认开启 + SenSuDB 注册 + 审计日志
- plugins.isolation 默认 true (生产环境进程隔离)
- main.py 注册 SenSuDB 服务
- WebPanelManager 注入 sensu_db 到 app context
- 文件删除/写入操作写入审计日志 (audit_log 表)
- 修复 Session 持久化所需的数据库依赖

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 13:56:58 +08:00
qinglong c1d2767f52 fix: web 命令端点 execute_command → process_command (方法名错误)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 13:54:37 +08:00
qinglong fcac02d920 security: P2 生产深度加固 — 路径/鉴权/脱敏/校验/持久化/过期
4.3 文件管理器路径收紧:
- 默认移除 Path('/') 全文件系统访问
- 仅允许项目目录 + data/ + 环境变量 SENSU_FILE_ROOTS 指定路径

3.4 插件路由鉴权修复:
- _check_plugin_auth 增加 panel_token 用户身份验证
- 先验证用户登录, 再检查插件权限

4.2 错误脱敏:
- security middleware 捕获异常 → 通用 'Internal server error'
- 堆栈详情仅写入日志, 不暴露给客户端

4.4 命令参数校验:
- POST /api/command 拒绝 shell 元字符 (;&|`$(){}!#~<>)
- 防止命令注入

4.5 Session 持久化:
- 登录/退出时保存到 SenSuDB.config_kv
- 框架重启后自动恢复已持久化会话

4.6 Token 过期: 24h → 2h

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 13:50:51 +08:00
qinglong 2f9063d6d4 fix: 登录频率限制 bug — lock_until=0 导致计数被清零
根因: 初始 lock_until=0 时 now >= 0 + 60 恒为真, 每次请求都清除计数
修复: lock_until 改用 None 表示未锁定, 仅非 None 时做过期判断

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 13:46:29 +08:00
qinglong f7b4908322 security: 公网生产环境加固 — P0/P1 全部修复
API 认证 (16 个未保护端点 → 全部加 panel_auth):
- 文件管理器: 11 端点 (list/mkdir/delete/upload/download/read/write/...)
- 项目管理: 6 端点 (list/run/stop/logs/stdin/page), 修复硬编码路径前缀
- 代理管理: 3 端点 (list/add/remove)
- 系统状态: 2 HTTP + 1 WS (token 校验)
- 插件页面: 3 端点 (page/sse/event)
- 内部路由: 3 端点 (plugins/commands/data)

认证系统加固:
- 密码哈希: 固定盐 → 每用户独立 secrets.token_hex(16) 随机盐
- 默认密码警告: 启动时检测并打印 critical 级别日志
- 登录频率限制: 5 次失败 / IP → 锁定 60 秒, 返回 429

基础设施:
- 安全响应头: X-Content-Type-Options/X-Frame-Options/X-XSS-Protection/Referrer-Policy
- WebSocket 鉴权: query string token 校验

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 13:37:14 +08:00
qinglong 2dc3c16787 fix: 系统监控改用 asyncio task 驱动,抗 Android 后台挂起
- SystemMonitor/PluginStatusPanel: 移除 set_interval, 改用外部 refresh()
- TuiService._refresh_monitors(): asyncio.sleep(2) 循环刷新
- asyncio.sleep 在 Android 恢复前台后能正常续跑
- shutdown 时 cancel refresh task

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 12:50:06 +08:00
qinglong 10d1a1fcb1 fix: 标题栏 背景改 + 行高加到2 增强可见性 2026-06-13 12:18:50 +08:00
qinglong feca81842b fix: 回到 grid 布局 + 顶部留空行 — 5行: 空/标题/状态栏/主区/输入
grid-rows: 1 1 auto 1fr 3
- Row 1: 空白行
- Row 2: 标题栏 (Static, bold)
- Row 3: 状态栏 (auto 高度)
- Row 4: 主显示区 (1fr 填满剩余)
- Row 5: 输入栏 (固定3行)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 12:17:30 +08:00
qinglong ae8586bc4a fix: 用 Static 标题栏替代 Textual Header,确保始终显示
- 自定义 #title-bar (dock top, height 1, bold)
- 移除不可靠的 Header widget
- _setup_title 更新 title_bar 内容

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 12:00:36 +08:00
qinglong 18f9fcfcd3 fix: Header dock top 防止被 monitor-area 遮挡 2026-06-13 11:56:58 +08:00
qinglong f7117825ae fix: 日志区改用 dock 布局自动占满剩余空间
- monitor-area: dock top
- input-area: dock bottom, height 3
- main-area: 自动填充中间所有空白
- 去掉 grid 布局避免高度塌缩

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 11:54:27 +08:00