Fill84/procurve-webui
GitHub: Fill84/procurve-webui
为淘汰了 Java Applet 的 HP ProCurve 2810-24G 交换机提供单容器部署的现代浏览器 Web 管理界面。
Stars: 1 | Forks: 0
# procurve-webui
一个专为 **HP ProCurve Switch 2810-24G (J9021A)** 打造的现代、浏览器原生 Web UI
—— 也是对原版 Java applet 所使用协议的一次彻底重写。这些交换机出厂自带的 Web UI 依赖于一个 Java applet (`agent.jar`),而它早已无法在任何现代浏览器中运行。本项目使用单个 Docker 容器将其取代:包含一个带类型的 Python 协议客户端、一个 FastAPI 服务,以及一个 React SPA —— 完全无需触及老旧的 Java 技术栈。
## 为什么会有这个项目
HP ProCurve 2810-24G 是一款极其优秀的硬件,至今仍非常适合作为家庭、实验室和小型办公室中的可网管千兆交换机使用。遗憾的是,它的管理 UI 老化严重:
- Web UI 是以 Java applet (`agent.jar`, ~183 KB) 形式提供的。现代浏览器多年前就移除了对 Java applet 的支持 —— Chrome 在 2015 年放弃了 NPAPI,Firefox 是在 2017 年,而 Edge 则从未支持过。
- 仅仅为了与交换机通信就去安装老旧的 Java,会带来安全风险并增加维护负担。
- CLI (telnet/SSH) 倒是还可以,但大多数用户还是希望有一个 GUI 来进行日常监控和临时的端口调整。
`procurve-webui` 让你可以保留硬件,同时抛弃那个 applet。
你只需在任意一台能够连接到该交换机的主机上运行 **一个** 容器,通过浏览器访问
`http://localhost:8080`,使用交换机已有的凭据登录,即可获得一个快速、现代的 UI。
## 截图
| | |
|---|---|
|  |  |
| **登录** —— 仅需交换机凭据;无独立的用户数据库。 | **身份信息** —— 系统名称、型号、固件、运行时间、MAC、内存与 CPU。 |
|  |  |
| **状态** —— 实时交换机渲染图(带各端口 LED)、端口利用率图表,以及可确认/删除的告警日志。 | **支持** —— 用现代的 HPE 门户取代了老旧的 `procurve.com` 重定向,并支持复制粘贴型号和序列号以用于支持案例。 |
|  |  |
| **配置** —— IP、端口、设备功能、故障检测、监控、QoS。 | **安全** —— Web 访问、Web 管理员、端口级安全、入侵日志、SSL 状态。 |
|  |  |
| **诊断** —— Ping、链路测试、配置报告、设备重启。 | **备份** —— 列表、与实时配置进行 diff、下载 `.pcc`、恢复、获取新快照。 |
如果截图缺失,请参阅下文的[本地生成截图](#generating-screenshots-locally) ——
`tools/mock_demo/` 包提供了一个仅需一条命令即可运行的 demo 后端。
## 亮点
- **彻底告别 Java。** 容器与交换机之间纯粹通过 HTTP 通信。
- **单容器部署。** 运行 `docker compose up -d` 即可大功告成。
- **默认只读。** `.env.example` 中预设了 `READ_ONLY=true`;在明确启用之前,写操作端点都会抛出 `403 WriteDisabledError` 异常。
- **每次写入前自动备份。** 在执行任何改变状态的操作之前,都会自动捕获一份当前运行配置的 `.pcc` 快照。
- **交换机即是身份提供者。** 应用端无用户数据库;UI 的登录表单直接向交换机本身验证凭据。
- **端到端类型安全。** 后端使用 Pydantic v2 模型,前端通过 OpenAPI 生成 TypeScript 类型。无需手写 API 类型。
- **对硬件友好。** 轮询频率特意设置得较低(实时仪表盘默认 2 秒,标签页隐藏时自动暂停);因为 2810-24G 的管理 CPU 性能有限,高频探测可能会导致其崩溃。
- **开放的文档。** 每一个逆向工程得出的交换机端点都有专属的 `research/protocol//.md`,详细描述了 URL、查询字符串、响应结构和验证规则。
## 硬件支持
| | |
|---|---|
| **确认支持** | HP ProCurve Switch 2810-24G (J9021A),固件 **N.11.78** |
| **可能支持** | 运行同系列固件的其他 2810 系列 (2810-48G / J9022A) —— 相同的 applet,相同的 CGI 接口。**未经测试** —— 请提交一个 issue 告知我们结果。 |
| **不支持** | 2510、2610、2810-Plus (J9573A / J9574A) —— 它们运行不同的固件分支 (例如 K.x, R.x),且管理协议不同。 |
| **将不会支持** | 任何由不同供应商/固件家族提供 `agent.jar` 的交换机。 |
如果你成功将其用于其他 ProCurve 硬件,非常欢迎提交 PR 为此表格添加一行记录。
## 快速开始
### 选项 A —— Docker(推荐)
```
git clone https://github.com//procurve-webui.git
cd procurve-webui
cp .env.example .env
# 编辑 .env — 至少设置 SWITCH_HOST 和一个真实的 SESSION_SECRET。
# 使用以下命令生成 secret:
python -c "import secrets; print(secrets.token_urlsafe(32))"
docker compose up -d
```
浏览访问 `http://localhost:8080`,使用你的交换机凭据登录(出厂固件下可使用
空白/空白)。compose 文件默认仅绑定至
`127.0.0.1`;如果你需要局域网访问,请更改 `docker-compose.yml` 中的 `ports:`
(请先阅读[安全说明](#security-considerations))。
### 选项 B —— 本地开发
```
# Backend
cd backend
python -m venv .venv
source .venv/Scripts/activate # Git Bash / WSL on Windows
pip install -e ".[dev]"
uvicorn app.main:app --reload --port 8080
# Frontend(单独的终端)
cd frontend
npm install
npm run dev # Vite on http://localhost:5173,
# proxies /api/v1 → 8080
```
开发环境前端会与本地后端通信,后者会连接到 `.env` 中指定的
真实交换机。在你完全信任当前环境下的写入路径之前,请务必设置 `READ_ONLY=true`。
## 配置 (`.env`)
| 变量 | 必需 | 默认值 | 描述 |
|---|---|---|---|
| `SWITCH_HOST` | 是 | — | ProCurve 交换机的 IP 或主机名。 |
| `SWITCH_PORT` | 否 | `80` | 交换机管理端口 (HTTP)。 |
| `SESSION_SECRET` | 是 | — | ≥32 字符的随机字符串,用于签名 session cookie。可通过 `python -c "import secrets; print(secrets.token_urlsafe(32))"` 生成。 |
| `READ_ONLY` | 否 | `true` | 当为 `true` 时,所有 `@WRITE` 端点都会抛出 403。更改为 `false` 即可启用配置/安全/诊断的写入功能。 |
| `SESSION_TTL_HOURS` | 否 | `8` | 空闲会话存活时间。超过此时间后,用户必须重新登录。 |
| `POLL_INTERVAL_SECONDS` | 否 | `2.0` | 实时端口流量轮询频率 (WebSocket)。请勿低于 1.0 —— 交换机的管理 CPU 无法承受。 |
| `HOST` | 否 | `127.0.0.1` | FastAPI 服务的绑定地址。设置为 `0.0.0.0` 可在局域网中公开;请务必先了解其[安全隐患](#security-considerations)。 |
| `BACKUPS_DIR` | 否 | `/app/backups` | 存储 `.pcc` 备份的文件系统路径。compose 文件将 `./backups` 挂载到此处。 |
| `METRICS_ENABLED` | 否 | `false` | 开放 Prometheus `/metrics` 端点。 |
**交换机凭据绝对不会写入 `.env`。** 它们是在登录时于浏览器中输入的,
经过交换机验证后,仅保存在后端的 RAM 中(以 session cookie 为键)。
重启容器后,所有会话都必须重新进行身份验证。
## 架构
单个 Docker 容器,三个逻辑层:
```
┌────────────────────────────────────────────────────────────────┐
│ Docker container: procurve-webui │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ React 18 SPA │ ←→ │ FastAPI │ │
│ │ Vite + TS + Tailwind │ HTTP │ ┌──────────────────────┐ │ │
│ │ TanStack Router/Query│ /ws │ │ REST + WebSocket │ │ │
│ │ Recharts │ │ └──────────┬───────────┘ │ │
│ │ shadcn/ui │ │ │ │ │
│ │ (built ahead of time,│ │ ┌──────────▼───────────┐ │ │
│ │ served as static │ │ │ procurve_client │ │ │
│ │ assets by FastAPI) │ │ │ (typed Python, │ │ │
│ └──────────────────────┘ │ │ talks the switch's │ │ │
│ │ │ GET-based protocol) │ │ │
│ │ └──────────┬───────────┘ │ │
│ └────────────┼─────────────┘ │
└───────────────────────────────────────────────┼────────────────┘
│ HTTP/1.1
▼
┌──────────────────────────┐
│ ProCurve 2810-24G │
│ ($SWITCH_HOST:80) │
│ eHTTP v2.0 management │
└──────────────────────────┘
```
### 各层详情
- **`procurve_client/`** —— 纯 Python、异步、完全类型化 (Pydantic v2) 的协议库。每个 applet CGI 对应一个操作;函数接收一个 `ProcurveTransport` 并返回已验证的模型。**不依赖任何 FastAPI。** 可作为独立库在脚本和 Notebook 中复用。
- **`app/`** —— FastAPI 应用程序。作为轻量级层,负责将 HTTP 路由映射到 `procurve_client` 操作,处理身份验证/会话,管理 `BackupStore`,并提供预构建的 React bundle。WebSocket 端点 `/ws/port-traffic` 用于传输各端口的计数器。
- **`frontend/`** —— React 18 + TypeScript SPA。使用 TanStack Router 实现类型安全的路由,TanStack Query 获取数据,Tailwind + shadcn/ui 进行样式设计,Recharts 绘制图表。类型通过 `openapi-typescript` 从 FastAPI 的 `openapi.json` 生成 —— 无需手写 API 类型。
### 为什么这样拆分?
- **`procurve_client` 完全不依赖 FastAPI。** 你可以独立 `pip install` 该包,并在 Python 中编写与交换机通信的脚本。
- **前端完全不依赖交换机。** 它只知道内部的 REST 契约,这意味着同一个 UI 可以通过不同的传输方式工作(例如未来的 REST 代理、SNMP 网桥等)。
- **容器完全不接触凭据。** 交换机即是身份提供者;磁盘上唯一的 secret 是用于签发 cookie 的密钥 (`SESSION_SECRET`)。
完整设计请参阅 [`docs/specs/2026-04-23-procurve-webui-design.md`](docs/specs/2026-04-23-procurve-webui-design.md)。
## 仓库结构
```
procurve-webui/
├── backend/
│ ├── app/ FastAPI routes, auth, backup store, settings
│ │ ├── api/ one router per tab (status, security, …)
│ │ ├── ws/ WebSocket endpoints (port-traffic)
│ │ └── main.py app factory
│ ├── procurve_client/ Standalone protocol client library
│ │ ├── operations/ one module per tab; @READ / @WRITE decorators
│ │ ├── models/ Pydantic models per domain
│ │ ├── transport.py httpx wrapper; auth, error mapping
│ │ └── parsing.py tilde/sentinel response parsers
│ ├── tests/ unit + integration + byte-match write tests
│ └── pyproject.toml
├── frontend/
│ ├── src/
│ │ ├── api/ generated TS types + React Query hooks
│ │ ├── components/ layout shell, switch SVG, port LEDs
│ │ ├── features/ one folder per tab (identity, status, …)
│ │ ├── routes/ TanStack Router file-based routes
│ │ └── lib/ formatting, alert helpers
│ ├── openapi.json snapshot for type generation
│ └── vite.config.ts
├── docker/
│ ├── Dockerfile multi-stage; Node 22 → Python 3.12-slim
│ ├── entrypoint.sh
│ └── healthcheck.sh
├── docker-compose.yml single-service convenience file
├── docs/
│ ├── specs/ design specifications per phase
│ ├── plans/ step-by-step implementation plans
│ └── screenshots/ PNGs used in this README
├── research/ Phase 0 reverse-engineering output
│ ├── applet/agent.jar the original Java applet
│ ├── mirror/ full HTTP mirror of the switch
│ ├── decompiled/ CFR-decompiled .java sources (gitignored)
│ ├── protocol/ one .md per CGI operation
│ ├── fixtures/ live-captured response samples
│ └── backups/ reference `.pcc` config snapshot
├── tools/
│ └── mock_demo/ mock backend for demos and screenshots
└── README.md (this file)
```
## 协议层工作原理
原版 applet 通过少量 CGI 端点与交换机通信(约 50 个 GET 处理程序,
外加 2 个用于配置下载和上传的 POST 端点)。所有响应体均为纯 ASCII,
字段以 `~` 分隔,并在第一行包含 `OK~…` / `error~…` 标识。
`procurve_client` 将该接口映射为一个扁平化的函数 API:
```
import asyncio
from procurve_client import ProcurveTransport
from procurve_client.operations.identity import read_identity_page
from procurve_client.operations.status import get_port_status
from procurve_client.operations.backup import download_config
async def main():
async with ProcurveTransport(host="192.0.2.3") as t:
identity = await read_identity_page(t)
print(identity.system_name, identity.firmware_version)
port = await get_port_status(t, port=1)
print(port.link_state, port.speed_mbps, port.duplex)
cfg = await download_config(t)
print(f"running-config: {cfg.size} bytes, sha256={cfg.sha256[:16]}…")
asyncio.run(main())
```
基于逆向工程得出的协议具有以下几个不变性:
- **所有操作均为 `async`。** 一个 `ProcurveTransport` 包装了单个 `httpx.AsyncClient`;你为每个会话创建一个并将其传递给各项操作。
- **读取操作使用 `@READ` 装饰,写入操作使用 `@WRITE`。** 当 `READ_ONLY=true` 时,每个 `@WRITE` 都会在发起任何网络调用 *之前* 直接短路并引发 `WriteDisabledError`。
- **验证规则源自交换机固件,而非我们自定义的。** 每一个验证器(端口名长度、VLAN ID 范围、IP 格式等)均提取自反编译的 Java 代码,并与实际行为进行了交叉验证。如果交换机会拒绝某个值,Python 模型会先将其拦截并给出清晰的错误。
- **写入操作保持字节级精确。** 每一个设置器都有对应的单元测试,确保生成的 HTTP 请求与源自反编译 applet 的已知正确模板完全匹配。空格被编码为 `+`(applet 使用的是 `URLEncoder.encode`);查询键中的已知拼写错误(例如将 `indices` 写作 `indeces`)被原样保留,因为交换机固件就是按字面量去检查它们的。
## 功能概览
### 身份信息选项卡 — `/`
静态着陆页。从交换机获取 `/identity/identity.html`
并解析出型号、序列号、固件、基本 MAC、IPv4、运行时间、总/空闲内存以及 CPU 利用率。只读,无写操作。
### 状态选项卡 — `/status`
- 交换机机箱的实时 SVG 渲染图(24 个铜口 RJ45 分为 2 行各 12 个,右侧有 4 个 mini-GBIC 插槽),端口 LED 实时反映链路状态和活动情况。
- 通过 WebSocket `/ws/port-traffic` 按设定的 `POLL_INTERVAL_SECONDS` 频率刷新的各端口利用率图表。
- 包含详情对话框的告警日志,支持单条确认、批量确认、删除。
- `/status/ports/{port}` 深度链接,用于查看特定端口的详细信息。
### 配置选项卡 — `/configuration` *(写入)*
- 系统信息(名称、位置、联系人)
- IP 配置 (DHCP / 静态)
- 默认网关
- 端口级配置(管理状态、名称、速率/双工、流控)
- 设备功能(巨型帧、IGMP Snooping 等)
- 故障检测阈值
- 控(端口镜像)
- "Bob" SFP/mini-GBIC 端口分配
- QoS —— CoS 应用/用户优先级/VLAN 优先级,DSCP,DiffServ
### 安全选项卡 — `/security` *(写入)*
- Web 访问模式 (Web/SSL/WebSSL/禁用)
- Web 管理员 IP 白名单 (`web-managers`) —— **存在锁定风险**,需通过额外的确认步骤
- 端口级安全(管理/学习/限制/入侵告警)
- 入侵日志 + 重置标志
- SSL 状态检查
- 管理员 / 操作员密码
### 诊断选项卡 — `/diagnostics` *(写入)*
- ICMP Ping
- 链路测试 (环回)
- 配置报告(以人类可读格式转储完整的 running-config)
- 设备重置 / 重新加载
### 备份选项卡 — `/backups`
- 已存储 `.pcc` 文件的列表(时间戳、标签、大小、SHA256、触发来源:手动 / 写入前 / 定时)
- 立即获取新快照
- 将已存储的备份与交换机实时的 running-config 进行 diff
- 下载为 `.pcc`
- 恢复(会强制重启 —— 需明确的二次确认)
- 删除
### 支持选项卡 — `/support`
渲染交换机广播的、用户可配置的支持 URL(`set_support` 写入通过 配置 → 支持 页面进行)。该选项卡本身只是一个提供外部链接的简洁卡片。
## 安全考量
本应用专为运行在受信任主机上的单一受信任用户设计(通常是托管 Docker daemon 的同一台机器)。在将其暴露给局域网之前,请仔细阅读本节内容。
- **无持久化用户数据库。** 交换机是身份提供者;凭据仅存在于浏览器 session 和后端 RAM 中,以签名 cookie 作为索引。它们绝不会写入磁盘,也绝不会被记录在日志中。
- **`SESSION_SECRET` 必须是真正的随机值。** 容器会 *有意* 拒绝使用 `.env.example` 中占位符字符串启动。请使用 `python -c "import secrets; print(secrets.token_urlsafe(32))"` 生成一个全新的值。
- **默认绑定地址为 `127.0.0.1`。** compose 文件将发布端口绑定至 `127.0.0.1:8080`,因此只能从本地主机访问。若要暴露给局域网,请在 `docker-compose.yml` 中将其更改为 `8080:8080`,*并* 添加一个处理 TLS 终结的反向代理 (Caddy / Traefik / nginx) —— 目前尚未内置 HTTPS。如果没有代理,交换机凭据将以明文形式在网络中传输。
- **针对写入操作的 CSRF 防护。** 前端在每次改变状态的请求中都会提交一个双重提交 cookie token;严格限制同源,并执行严格的 CORS。
- **默认只读。** 即使凭据有效,全新安装的应用也会拒绝所有写操作。如需启用,请在 `.env` 中设置 `READ_ONLY=false`。
- **每次写入前自动备份。** 在运行任何 `@WRITE` 之前都会捕获 `.pcc` 快照;生成的 `BackupMeta` 会记录 SHA256 和一个 `pre-write` 触发标签。
- **存在锁定风险的操作受到门控保护。** 更改管理 IP、Web 管理员白名单或管理员密码时,要求用户必须重新输入新值作为确认,然后才会发出调用。
- **危险操作不在范围内。** 固件上传、恢复出厂设置以及任何可能导致交换机变砖的操作都被刻意屏蔽了。它们被列在未来的开发计划中,并将需要专门的用户体验规范。
## 本地生成截图
本 README 中的截图是由随仓库提供的一个小型模拟后端生成的,以便贡献者在没有真实交换机的情况下也能重新生成它们。
```
# 1. 构建 frontend
cd frontend && npm install && npm run build
# 2. 运行 demo backend(提供 dist/ 服务 + 预置的 switch 响应)
cd ../tools/mock_demo
python demo_server.py # listens on http://127.0.0.1:8080
# 3. 浏览 http://127.0.0.1:8080 并手动截图,
# 或运行 headless 捕获脚本:
python capture_screenshots.py # writes PNGs to docs/screenshots/
```
该模拟后端提供的是硬编码的 fixtures —— 它绝不会与交换机建立 TCP 连接 —— 因此在任何主机上运行都是安全的。
## 开发
### 运行测试套件
```
cd backend
pytest # unit + parser + write-template tests
pytest -m live # live read-only against the configured switch
pytest -m roundtrip # opt-in: write → verify → restore loop
# (requires user approval; never run in CI)
```
`live` 和 `roundtrip` **不会** 由 CI 运行。它们需要真实的交换机和凭据,并且 `roundtrip` 会在恢复参考基准之前执行一次写入操作。`procurve_client/` 的覆盖率目标是 ≥90%。
### Lint 和类型检查
```
cd backend
ruff check .
mypy .
cd ../frontend
npm run lint
npm run typecheck
```
### 重新生成 OpenAPI 类型
当你添加或更改后端端点时:
```
# Backend 在本地 :8080 上运行
curl http://localhost:8080/openapi.json > frontend/openapi.json
cd frontend
npm run gen:api # writes src/api/schema.d.ts
```
### 逆向工程文档
每个交换机端点在 `research/protocol//.md` 下都有对应的协议文档。其格式记录在 [`research/protocol/_conventions.md`](research/protocol/_conventions.md) 中。
实时的响应样本位于 `research/fixtures/` 中并被原样保留(未作格式化),以便写入模板的字节匹配测试可以依赖它们。
## 路线图
已实现:
- [x] 阶段 0 —— JAR 反编译、资产镜像、协议文档、fixtures
- [x] 阶段 1 —— Python `procurve_client` 库(读 + 写,覆盖率 ≥90%)
- [x] 阶段 2 —— Docker 脚手架、只读的身份信息 + 状态 UI、WebSocket 端口流量
- [x] 阶段 3 —— 配置 / 安全 / 诊断选项卡(写入)、备份选项卡、告警确认/删除
计划中:
- [ ] 通过 Caddy sidecar 实现 HTTPS(默认自签名,可替换为 ACME)
- [ ] “危险级别”的操作(固件上传、恢复出厂设置)及其专属的 UX 规范
- [ ] 多交换机仪表盘
- [ ] 可选的“记住我”长效 token
- [ ] SNMP 桥接模式(只读),以便同一 UI 能用于未开放 eHTTP 服务的交换机
## 许可证
基于 MIT 许可证发布。完整文本请参阅 [`LICENSE`](LICENSE)。
## 致谢
- 那些在论坛和 Reddit 帖子中让老旧 ProCurve 硬件继续焕发生机的社区成员 —— 特别是要感谢他们确认了 eHTTP v2 协议在整个 2810 固件系列中是一致的。
- [CFR](https://www.benf.org/other/cfr/) —— 用于阅读 `agent.jar` 的 Java 反编译工具。如果没有它,阶段 0 的工作将会艰难得多。
- [FastAPI](https://fastapi.tiangolo.com/)、[TanStack Router/Query](https://tanstack.com/)、[shadcn/ui](https://ui.shadcn.com/) 以及 [Recharts](https://recharts.org/) —— 技术栈中的每一层,正是它们使得我们能够在合理的时间内构建出一个精致的 UI。
标签:AV绕过, Docker, FastAPI, HP ProCurve, React, Syscalls, TCP SYN 扫描, Web UI, 安全防御评估, 网络设备管理, 请求拦截, 逆向工具