Releases: l17728/pairproxy
v2.24.6 — Smart Probe 全面修复 + reportgen UTC 时区归一化
v2.24.6
🐛 Bug Fixes
internal/lb — Smart Probe 7 项修复
1. WithTimeout 未同步 prober 客户端
调用 WithTimeout 仅更新了 hc.client 和 hc.timeout,但 hc.prober 持有独立的 http.Client,导致 health_check_timeout 配置对智能探活(Discover 阶段)的 HTTP 请求完全无效。
修复:WithTimeout 现同时重建 hc.prober = NewProber(d, h.logger),三者超时严格一致。
2. 空白 API Key 被当作有效凭证
injectCredential 原先检查 cred.APIKey == "",纯空格或制表符组成的 key 会通过检查并注入无效的 Authorization header,向上游发出带噪声认证头的请求。
修复:先 strings.TrimSpace() 再判空,空白字符串视为"无凭证",不注入任何认证头。
3. buildProbeURL 未过滤查询参数和 Fragment
当目标地址(addr)包含 ?query 或 #fragment 时,探活 URL 会被拼接成无效路径(如 http://host/v1?key=val/models)。
修复:使用 url.Parse 提取路径,并用 strings.IndexAny(addrBase, "?#") 在构建 addrBase 前截断。
4. Discover 端点超时 vs 连接拒绝未区分
原 Discover 将所有 definitivelyUnhealthy() 错误均视为连接拒绝(unreachable=true),但 HTTP 请求超时也会触发此分支,导致临时超时的目标被错误地标记为不可达。
修复:新增 isEndpointTimeout() helper,区分两类失败:
- 端点超时(
*url.Error.Timeout()、context.DeadlineExceeded)→continue尝试下一路径 - 硬连接失败(拒绝连接/DNS 错误)→ 返回
unreachable=true
5. 所有路径超时时错误标记 unreachable
若 Discover 期间所有探活路径均超时(无 HTTP 响应),或上层 context 预算耗尽,原实现语义不清晰,可能与硬连接失败混淆。
修复:通过 gotHTTPResponse 标志和 budgetExhausted 标志明确区分三种结果:
- 有 HTTP 响应但不匹配 →
nil, false(服务在线,路径不符) - 所有路径超时 →
nil, false(网络不确定,不标记 unreachable) - 硬连接失败 →
nil, true(unreachable)
6. Start() 重复调用导致 close(stopCh) panic
loop() 退出时关闭 stopCh,若再次调用 Start() 使用同一已关闭的 channel,recovery goroutine 的 select 会在关闭的 channel 上立即返回,错误退出。
修复:Start() 首行重建 stopCh = make(chan struct{})。
7. recoveryDelay goroutine 阻塞进程关闭
进程关闭时,等待 recoveryDelay(可达数分钟)的 goroutine 会阻塞 wg.Wait(),导致进程无法及时退出。
修复:time.Sleep(hc.recoveryDelay) → select { case <-time.After(hc.recoveryDelay): case <-hc.stopCh: return },关闭信号到达后毫秒级退出。
附加修复
UpdateHealthPaths缓存失效:目标失去显式health_check_path时,现立即调用probeCache.invalidate(),与UpdateCredentials行为对齐checkAll()数据竞争:hc.healthPath现在锁内快照为globalHealthPath,消除并发读写竞争- API 清理:移除
probeEntry.unreachable字段和ProbeCache.set()的unreachable bool参数(死代码);ProbeCache.mu从sync.RWMutex简化为sync.Mutex
🐛 Bug Fixes
tools/reportgen — SQLite 时区归一化(17 个查询函数)
新增 toUTC() helper,在所有接受 from, to time.Time 参数的查询函数入口统一转 UTC。
根因:SQLite 以字符串存储时间戳,字符串比较是字典序。当调用方传入本地时区时间(如 +08:00),与数据库中 UTC 格式的记录比较会产生错误的过滤结果,导致图表数据为空或范围错误。
修复的函数(queries.go):
QueryKPI、QueryDailyTrend、QueryHeatmap、QueryLatencyBoxPlotByModel、QueryLatencyPercentileTrend、QueryDailyLatencyTrend、QueryUserRequestBoxPlot
修复的函数(queries_phase8.go):
QueryLatencyHistogram、QueryLatencyScatter、QueryTokenThroughputHeatmap、QueryUpstreamShare、QueryUpstreamLatencyTrend、QueryCostPerTokenTrend、QueryIORatioTrend、QueryPeakRPM、QueryModelDailyTrend、QuerySourceNodeDist
🧪 新增测试(13 个)
| 测试 | 覆盖场景 |
|---|---|
TestInjectCredential_WhitespaceKey |
纯空格 key 不产生认证头 |
TestInjectCredential_TabWhitespaceKey |
制表符 key 不产生认证头 |
TestBuildProbeURL_QueryParams |
含查询参数、Fragment 的地址正确构建探活 URL |
TestWithTimeout_UpdatesProberClient |
WithTimeout 同步更新 prober HTTP client 超时 |
TestWithTimeout_Default |
默认超时下 hc.client 与 prober 一致 |
TestIsEndpointTimeout |
6 种错误类型的分类正确性 |
TestUpdateHealthPaths_InvalidatesCache |
失去显式路径的目标缓存被清除 |
TestUpdateHealthPaths_NoInvalidationForNewTargets |
新增目标不触发无效缓存清除 |
TestDiscover_CtxExpiry_NotUnreachable |
预取消 context → nil,false 而非 unreachable |
TestIsEndpointTimeout_ContextCanceled |
context.Canceled 语义文档化 |
TestProbe_Discover_ContinuesPastEndpointTimeout |
端点超时后继续尝试下一路径 |
TestProbe_Discover_ConnectionRefusedIsUnreachable |
连接拒绝 → unreachable=true |
TestProbe_Discover_AllMethodsTimeout_NotUnreachable |
全部超时 → unreachable=false |
📊 测试统计
- 主模块:2,144 个测试,25 个包,0 FAIL
- 回归:全量
go test ./...通过
Full Changelog: v2.24.5...v2.24.6
v2.24.5 — SQLite Timezone Fix + reportgen Parity Tests
🔧 Gateway (sproxy/cproxy)
SQLite Timezone Bug Fix
-
Root cause: SQLite stores
time.Timeas RFC3339 strings. On non-UTC systems,CreatedAtstored as"2026-04-09T20:00:00+08:00"(local) but queries compared against"2026-04-09T12:00:00Z"(UTC). String comparison is lexicographic → wrong ordering → 0 tokens returned. -
Fix 1 — Storage normalization: Added
UsageLog.BeforeCreateGORM hook to normalize all writes to UTCfunc (u *UsageLog) BeforeCreate(tx *gorm.DB) error { if !u.CreatedAt.IsZero() { u.CreatedAt = u.CreatedAt.UTC() } return nil }
-
Fix 2 — Query normalization: Added
toUTC()helper applied to 9 time-filtered methodsQuery,SumTokens,GlobalSumTokens,UserStats,ExportLogs,SumCostUSD,DailyTokens,DailyCost,DeleteBefore- Ensures bounds are UTC before SQLite string comparison
-
Impact: Token statistics on non-UTC systems (Asia/Shanghai, etc.) now return correct values
E2E Test Fixes
- Updated
seedTokensin sproxy_e2e_test.go and f10_features_e2e_test.go to usetime.Now().UTC()
📊 reportgen Tool
26 New Unit Tests (queries_extra_test.go)
Previously 36 of 39 query functions had zero test coverage. Now all critical functions tested:
QueryTopUsers,QueryGroupComparison,QueryStreamingRatio,QueryDailyTrendQueryEngagement,QueryQuotaUsage,QueryGroupTokenDistribution,QueryModelTokenBoxPlotsQuerySourceNodeDist,QueryUpstreamStats,QueryStatusCodeDist,QueryPeakRPMQuerySlowRequests,QueryErrorRequests
SQLite vs PostgreSQL Parity Tests (pg_parity_test.go)
Major addition: Every Query* function now has a deterministic parity test ensuring identical output across backends.
23 tests:
TestPGParity_<QueryName>for 22 functions — seeds identical data into both SQLite and PostgreSQL, runs query, asserts field-by-field equalityTestPGParity_AllQueries— smoke test running all 23 Query* functions in parallel
Example assertion:
for i, pg := range pgRows {
sq := sqRows[i]
if pg.TotalTokens != sq.TotalTokens {
t.Errorf("[%d] TotalTokens: PG=%d SQLite=%d", i, pg.TotalTokens, sq.TotalTokens)
}
}How to run:
POSTGRES_TEST_DSN="postgres://user:pass@localhost:5432/reportgen_test?sslmode=disable" \
go test -v -run TestPGParity ./tools/reportgen/Deterministic Query Ordering
Fixed 2 bugs where ties in primary sort key caused different output order:
-
QueryStatusCodeDist: Added
status_code ASCto tie-break whencntis identicalORDER BY cnt DESC, status_code ASC
-
QueryQuotaUsage: Added
u.username ASCto tie-break whenmonthly_usedis identicalORDER BY monthly_used DESC, u.username ASC
Error Visibility
generator.go: Changed silent_, _ =query discards towarnQueryErr()— logsWARNING: <QueryName> failed: <err>to stderrqueries.go loadMaps: ExplicitWARNING:on Scan errors instead of continuing silentlyintegration_test.go: Fail-fast onsql.Open/NewQueriererrors
📚 Documentation Refresh
8 files updated for v2.24.5:
- CHANGELOG.md — Full v2.24.5 entry with timezone root cause, GORM hook, toUTC() helper explanation
- UPGRADE.md — v2.24.5 section with schema/code changes/verification steps + rollback guidance
- TEST_REPORT.md — Date/version updated; test count: 2,078 (main) + 43 (reportgen) = 2,121
- README.md — Added 3 new feature rows for v2.24.2/v2.24.3/v2.24.5
- CONFIGURATION.md — New "reportgen 工具参数" section (basic, PostgreSQL, LLM flags, examples)
- DEPLOYMENT.md — New "reportgen 部署" section (build, usage, crontab, fault-tolerance)
- TESTING.md — Added reportgen file structure, test count inventory
- test/e2e/README.md — Added
group_target_set_e2e_test.goto file list; 17 test files total
✅ Test Coverage
Main modules: 2,078 tests (25 packages) — ALL PASS
github.com/l17728/pairproxy/internal/dbgithub.com/l17728/pairproxy/internal/proxygithub.com/l17728/pairproxy/test/e2e- ... and 22 others
reportgen (SQLite only): 59 tests — ALL PASS
queries_extra_test.go: 26 testsintegration_test.go: 17 tests- LLM, utility tests: 16 tests
reportgen (SQLite + PostgreSQL): 78 tests — ALL PASS
- All 59 SQLite tests
- 19 new PostgreSQL parity tests
Total: 2,156 tests across both gateway and reportgen. 0 FAIL, 0 SKIP (with POSTGRES_TEST_DSN set).
🔗 Commits
- 4da6c42 feat(v2.24.5): SQLite timezone normalization + reportgen deterministic ordering + dual-driver parity
🚀 Installation
Binary Release
Available on Releases
From Source
git clone https://github.com/l17728/pairproxy.git
cd pairproxy
git checkout v2.24.5
make buildUpgrade
- Backup database:
cp pairproxy.db pairproxy.db.bak - Stop sproxy:
systemctl stop sproxy - Replace binary:
cp sproxy-v2.24.5 /usr/local/bin/sproxy - Start:
systemctl start sproxy(AutoMigrate runs automatically) - Verify:
curl http://localhost:9000/health
See UPGRADE.md for detailed steps.
Full Changelog: v2.24.4...v2.24.5
v2.24.4 — Reportgen: Bug Fixes, Chart Tooltips & Enhanced Tests
本版本说明
本版本仅包含 reportgen 报告生成工具的更新,不涉及网关(pairproxy)核心代码,无需重新部署网关二进制。
下载说明
每个压缩包包含:
reportgen可执行文件(Windows 为reportgen.exe)templates/report.htmlHTML 模板(必须与可执行文件放在同一目录)
| 平台 | 文件 |
|---|---|
| Linux x86_64 | reportgen-v2.24.4-linux-amd64.tar.gz |
| Linux ARM64 | reportgen-v2.24.4-linux-arm64.tar.gz |
| macOS x86_64 | reportgen-v2.24.4-darwin-amd64.tar.gz |
| macOS Apple Silicon | reportgen-v2.24.4-darwin-arm64.tar.gz |
| Windows x86_64 | reportgen-v2.24.4-windows-amd64.zip |
快速开始
# 解压后,在同一目录执行:
./reportgen -db /path/to/pairproxy.db # 默认分析过去 30 天
./reportgen -db /path/to/pairproxy.db -from 2026-04-01 -to 2026-04-09 -output report.html
# PostgreSQL
./reportgen -pg-dsn "postgres://user:pass@host:5432/dbname"
# 启用 AI 洞察
./reportgen -db pairproxy.db -llm-url https://api.anthropic.com -llm-key sk-xxx -llm-model claude-haiku-4-5注意:
templates/report.html必须存在于运行目录的templates/子目录下,或通过-template参数指定路径。
v2.24.4 变更内容
🐛 Bug 修复
- Anthropic 路由修复:当
llm_targets表中同时设置了provider=anthropic和model字段时,请求错误地走 OpenAI/v1/chat/completions协议,现已修复为始终按provider字段判断 - 无效模型 ID 修复:默认 Anthropic 模型从
claude-haiku-4-5-20251001(不存在)修正为claude-haiku-4-5 - 空库 NULL 崩溃修复:
SUM(CASE WHEN...)在空表时返回 NULL 导致 Scan 崩溃,已对所有相关列加COALESCE(..., 0)保护 - HTTP 413 重试修复:LLM 网关返回 413(请求体过大)时,现在正确触发裁剪数据后重试
- 死图表修复:移除
initIORatio()无效调用(无对应 div),新增chart-upstream-share(上游请求占比饼图)并接入 3 列 Grid 布局 - 拼写错误修复:
stickness→stickiness,保留兼容 fallback - 日期参数优化:
-from/-to现为可选,默认分析过去 30 天至今
✨ 新增功能
- 图表悬停说明:所有 35 个图表标题旁新增 ❓ 图标,鼠标悬停即显示该图表的用途、关注点和使用方法,无需查阅文档
- Mock 数据生成器:新增
cmd/mockdata工具,生成含边界数据(零 Token 请求、NULL model、错误请求)的测试数据库
🧪 测试
新增 integration_test.go,共 32 个测试用例:
- LLM 路由(OpenAI / Anthropic / 默认模型 / 无 Key / 4xx / 413 重试)
- SQL 查询(空库、有数据、NULL model、零 Token、llm_targets JOIN)
- 工具函数(
isContextTooLong11 个子 case、endOfDay时区、safeKeyPrefix、truncate) - 驱动初始化(默认 sqlite、无效 DSN)
升级说明
| 组件 | 是否需要升级 |
|---|---|
| 网关二进制(pairproxy) | ❌ 无需升级 |
| reportgen 工具 | ✅ 建议升级 |
| 数据库 Schema | ❌ 无变更 |
| HTML 模板(templates/report.html) | ✅ 随二进制包一起更新 |
Full Changelog: v2.24.3...v2.24.4
v2.24.3 — Issue #6 关闭:同一 URL 多 API Key 正式可用
修复摘要
🐛 关键 Bug 修复:Issue #6 正式关闭
问题:同一 URL 配置多个不同 API Key 时,第二次创建总是失败。
根本原因:generateID() 使用 time.Now().UnixNano() base36 编码生成 LLM target ID。当两次创建请求在同一纳秒内执行(常见于测试和高并发场景),产生相同 ID,触发 UNIQUE constraint failed: llm_targets.id,与复合约束 (url, api_key_id) 完全无关。
修复:generateID() 改用 uuid.NewString(),从根本上消除 ID 碰撞。
复合约束一致性(举一反三全���修复)
通过系统性 3 轮递归分析发现并修复的相关问题:
| 问题 | 修复 |
|---|---|
GetByGroupID() 一对多建模为一对一 |
→ ListByGroupID(),返回全部集合 |
GetDefault() 不支持多个全局默认集 |
→ ListDefaults(),返回所有默认集 |
FindForUser() 使用 First() 有歧义风险 |
→ 防御性 Find() + slice 长度检查 |
FindByProviderAndValue() 多结果静默返回第一条 |
→ 多结果时返回明确错误 |
🧪 测试
- 共 2077 个测试用例,全部通过
- 新增并发竞态测试(10 goroutine 并发创建、2 goroutine 并发绑定)
- 新增 NULL 值处理回归测试
- 新增 Issue #6 专项回归测试
reportgen 工具增强
本 Release 包含最新 reportgen Windows/amd64 预编译版本(支持 SQLite + PostgreSQL)。
新功能
- 直接 LLM 配置:
-llm-url、-llm-key参数,可直接指定 LLM 端点和 API Key,无需数据库配置 - 命令行优先:若指定了
-llm-url和-llm-key,优先使用;否则从数据库查询
可靠性改进
-
完善的容错机制:
- 数据库查询失败 → 跳过,继续处理
- LLM 连接失败 → 降级为规则分析,仍可生成有意义的报告
- LLM 调用异常(panic)→ 捕获恢复,不影响主流程
- 模板文件缺失 → 使用内置最小模板渲染
- 无数据时段 → 智能检测并生成提示洞察
-
改进的错误日志:区分 HTTP 错误(如 429)与网络连接失败,提供更有针对性的诊断
版本兼容性
- 完整支持 v2.15.0+ 数据库架构,自动适配不同版本
跨平台版本由 CI 自动构建后附于 Release Assets。
升级
无 Schema 变更,无需数据迁移,直接替换二进制即可。
详见 UPGRADE.md。
Full Changelog: v2.24.2...v2.24.3
Full Changelog: v2.24.2...v2.24.3
Full Changelog: v2.24.2...v2.24.3
v2.24.2 - PostgreSQL Support
Features
- PostgreSQL Support: reportgen now supports both SQLite and PostgreSQL databases
- Backward Compatible: Existing SQLite deployments continue to work without changes
- Flexible Connection Options: Support for PostgreSQL DSN string or individual connection parameters
- SSL/TLS Support: Full SSL/TLS configuration options for secure database connections
Technical Details
Database Abstraction Layer
- Automatic SQL dialect conversion (SQLite ↔ PostgreSQL)
- Parameter binding transformation (? → $1, $2, ...)
- Helper functions for date/time operations across dialects
Connection Methods
SQLite (existing):
reportgen -db /path/to/database.db [other flags]PostgreSQL (DSN):
reportgen -pg-dsn "postgres://user:password@localhost:5432/dbname" [other flags]PostgreSQL (individual parameters):
reportgen -pg-host localhost -pg-port 5432 -pg-user user -pg-password pass -pg-dbname dbname [other flags]Deployment
- Stateless Architecture: No local YAML configuration files required
- Network-Agnostic: Can run on any node with database network access
- Multi-Node Support: Horizontal scaling without master/slave constraints
- CI/CD Ready: Works with Kubernetes, Docker Compose, Cron jobs
See DEPLOYMENT_GUIDE.md for comprehensive deployment patterns and best practices.
Documentation
- Updated README.md with PostgreSQL examples
- New DEPLOYMENT_GUIDE.md with architecture and deployment patterns
- Updated CHANGELOG.md
Compatibility
| Database | Support | Status |
|---|---|---|
| SQLite 3 | ✅ | Fully supported |
| PostgreSQL 12+ | ✅ | Fully supported |
Breaking Changes
None. This is a backward-compatible release.
v2.24.1
Full Changelog: v2.24.0...v2.24.1
v2.24.0
v2.24.0 — Model-Aware Routing(模型感知路由)
本版本引入 Model-Aware Routing 功能,让网关能够根据请求中的模型名称智能路由到支持该模型的目标,同时保持完全向后兼容。
新功能
F1 — Config-as-Seed(配置即种子)
配置文件中的 LLM target 现在作为初始种子写入数据库:
- 首次启动时,
sproxy.yaml中的 target 自动同步到数据库 - 若 target URL 已存在(WebUI 已添加或修改),跳过覆盖,保留 WebUI 的修改
- 配置文件负责"提供初始值",运行时由 WebUI 管理
F2 — Per-Target Supported Models(按目标过滤模型)
每个 LLM target 可声明自己支持哪些模型,网关路由时自动过滤:
# sproxy.yaml
llm:
targets:
- url: "https://api.anthropic.com/v1"
provider: "anthropic"
weight: 10
supported_models:
- "claude-3-opus-20250119"
- "claude-3-sonnet-20250219"
- "claude-3-haiku-*" # 前缀通配
- "claude-2.1" # 精确匹配
auto_model: "claude-3-sonnet-20250219"
- url: "https://api.openai.com/v1"
provider: "openai"
weight: 8
supported_models:
- "gpt-4-*"
- "gpt-3.5-turbo"
auto_model: "gpt-4-turbo"
- url: "http://localhost:11434"
provider: "ollama"
supported_models: [] # 空 = 接受所有模型
auto_model: "mistral"模式匹配规则:精确匹配、前缀通配(claude-3-*)、全通配(*)、空列表(接受所有)。
Fail-Open 策略:请求的模型不在任何 target 的配置中时,网关不拒绝请求,回退到所有健康 target,由 LLM 自行决定是否接受。
F3 — Auto Mode(自动模型选择)
客户端发送 "model": "auto" 时,网关自动为每个目标选择合适的模型并改写请求体:
客户端: {"model": "auto", ...}
→ Anthropic target: {"model": "claude-3-sonnet-20250219", ...}
→ OpenAI target: {"model": "gpt-4-turbo", ...}
降级策略:auto_model 字段 → supported_models[0] → 透传原始值。
API 与 CLI 支持
REST API(POST / PUT /api/admin/llm/targets)新增字段:
supported_models: 字符串数组,模型过滤列表auto_model: 字符串,auto 模式默认模型
CLI 命令(sproxy admin llm target add / update)新增 flags:
--supported-models "claude-3-*,claude-2.1"— 逗号分隔,支持通配符--auto-model "claude-3-sonnet-20250219"— auto 模式目标模型
测试
- 新增 17 个单元测试,覆盖:模式匹配、请求体改写、模型过滤、auto 模型选择
- 全部 1991 个测试回归通过,0 失败,0 数据竞争
文档
docs/manual.md §43— 完整运维指南(配置、API、CLI、故障排查、升级步骤).sisyphus/CONFIGURATION_GUIDE.md(552 行)— 场景说明 + 完整 sproxy.yaml 示例 + 故障排查映射.sisyphus/LOGGING_SPECIFICATION.md— 所有路由决策的结构化日志规范
升级指南
无数据库手动操作,Schema 自动迁移(新增 supported_models、auto_model 两列)。
- 替换二进制:
./sproxy version应输出sproxy v2.24.0 - (可选)在
sproxy.yaml各 target 中添加supported_models和auto_model - 重启 sproxy,查看日志确认迁移完成
完全向后兼容:未配置 supported_models 的 target 行为不变;未配置 auto_model 时 "auto" 模式透传。
Full Changelog: v2.23.0...v2.24.0
v2.23.0
v2.22.1: Complete Documentation Update
v2.22.1 — Documentation & Project Docs Comprehensive Update
Documentation Updates
This release includes comprehensive documentation refresh across all project documents to v2.22.0:
User Manual
- docs/manual.md: Updated to v2.22.0
- §39 WebUI Phase 1: Group-Target Set Management (dual-panel UI, 6 API endpoints, member operations)
- §40 WebUI Phase 2: Alert Management Enhancement (Live/Active/History tabs, batch operations, SSE)
- §41 WebUI Phase 3: Quick Operations Panel (3 summary cards, async loading, graceful degradation)
- +334 lines of detailed operational guidance with configuration examples
Project Reports & Guides
- ACCEPTANCE_REPORT.md: Updated to v2.22.0 (WebUI Expansion Phase 1/2/3)
- TEST_REPORT.md: Updated test version; 1,956 total tests, 34 E2E WebUI tests, all passing
- UPGRADE.md: Current version updated from v2.20.0 to v2.22.0
- OPENCLAW_OPS_GUIDE.md: Updated to v2.5, compatible with v2.22.0+
No Code Changes
This is a documentation-only patch. All features are already available in v2.22.0. No binaries needed.
What's Included
- ✅ Complete WebUI Phase 1/2/3 operational documentation
- ✅ API endpoint specifications for all new features
- ✅ Configuration examples and use case scenarios
- ✅ Updated test statistics and acceptance criteria
- ✅ Comprehensive upgrade guidance
Deployment
If already running v2.22.0, no action needed. Simply update your documentation reference to include these new chapters for complete feature guidance.
Full Changelog: v2.22.0...v2.22.1
Full Changelog: v2.22.0...v2.22.1
v2.22.0: WebUI Expansion (Phase 1/2/3)
PairProxy v2.22.0 Release Notes
Release Date: March 28, 2026
Git Tag: v2.22.0
Base Version: v2.21.1
📝 Overview
v2.22.0 introduces the WebUI Expansion Phase 1/2/3, bringing comprehensive new management capabilities to the PairProxy dashboard. This release adds group-based target set management, enhanced alert dashboard, and quick operations panel for improved operational visibility.
Key Metrics:
- ✅ 34 E2E integration tests passing
- ✅ 197 unit test files
- ✅ 3 major feature phases completed
- ✅ Zero breaking changes
🚀 Phase 1: Group-Target Set Management
What's New
A new GroupTargetSet resource enables administrators to:
- Group multiple LLM targets into logical sets
- Bind target sets to user groups for flexible routing
- Manage target set members independently
- Track usage and health per target set
User Interface
Dual-panel layout in the LLM dashboard:
- Left panel: Target sets list with quick actions (edit, delete)
- Right panel: Member list, group binding, and statistics
- Tab navigation: Easy switching between Targets, Target Sets, and Bindings
API Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /dashboard/llm/targetsets |
Create new target set |
| POST | /dashboard/llm/targetsets/{id}/update |
Update target set details |
| POST | /dashboard/llm/targetsets/{id}/delete |
Delete target set |
| POST | /dashboard/llm/targetsets/{id}/members |
Add member to target set |
| POST | /dashboard/llm/targetsets/{id}/members/update |
Update member configuration |
| POST | /dashboard/llm/targetsets/{id}/members/delete |
Remove member from target set |
Features
- Validation: Target set IDs must match
[a-zA-Z0-9_-]+pattern - Audit logging: All operations logged for compliance
- Worker node protection: Read-only mode for worker nodes
- Flash messages: User feedback with proper error encoding
Database Schema
CREATE TABLE group_target_sets (
id TEXT PRIMARY KEY,
group_id TEXT NOT NULL,
name TEXT NOT NULL,
strategy TEXT,
created_at TIMESTAMP,
FOREIGN KEY (group_id) REFERENCES groups(id)
);
CREATE TABLE group_target_set_members (
id TEXT PRIMARY KEY,
target_set_id TEXT NOT NULL,
target_url TEXT NOT NULL,
created_at TIMESTAMP,
FOREIGN KEY (target_set_id) REFERENCES group_target_sets(id)
);🎯 Phase 2: Alert Management Enhancement
What's New
Complete alert dashboard redesign with three-tab interface:
-
Live Tab
- Real-time event streaming
- Level filtering (All, Error, Warning)
- Updates without page reload
-
Active Tab
- Alert statistics with severity cards
- Checkbox selection for batch operations
- Quick resolution with visual feedback
- Shows: Critical count, Error count, Warning count
-
History Tab
- 90-day historical query
- Time range selector (7, 30, 90 days)
- Level and source filtering
- Pagination (50 items per page)
API Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /dashboard/alerts/resolve |
Resolve single alert |
| POST | /dashboard/alerts/resolve-batch |
Batch resolve multiple alerts |
Features
- Non-blocking: Async data loading
- Pagination: 50 items per page with Previous/Next
- Filtering: By level, source, and time range
- Batch operations: Select multiple, resolve together
- Status badges: Color-coded severity indicators
⚡ Phase 3: Quick Operations Panel
What's New
Dashboard summary cards providing at-a-glance operational status.
Summary Cards
-
LLM 目标状态 (LLM Target Status)
- Healthy targets count
- Targets with alerts count
- Total target sets count
-
系统告警 (System Alerts)
- Unresolved alerts count
- Error count
- Warning count
- Status badge (green/yellow/red)
-
用户/分组 (Users/Groups)
- Active users count
- Total groups count
- New users count
Implementation
- Async loading: Data fetched via AJAX after page load
- Graceful degradation: Missing data shows as "—" or "未配置"
- Real-time updates: Refreshes on page initialization
- Zero latency: Non-blocking dashboard render
Data Sources
- Alert counts:
/api/dashboard/events - User/group stats:
/dashboard/api/user-stats
🐛 Critical Bug Fixes
1. GORM Zero-Value Bug
Issue: IsActive=false not persisting in AddMember operation
Cause: GORM treats false as empty value in insert
Fix: Explicit Update call after insert, comprehensive test coverage
Impact: Member activation state now reliable
2. Template Scope Issue
Issue: Panic when accessing SelectedSetID inside range context
Cause: Context shadowing in {{range .TargetSets}}
Fix: Changed to $.SelectedSetID to access parent scope
Impact: Target set selection now works correctly
3. Member Route Handling
Issue: Delete/update member operations return 404 for complex URLs
Cause: Go 1.22 router doesn't allow {param} spanning multiple / segments
Fix: Changed route from /members/{memberID}/delete to /members/delete with POST form fields
Impact: All member operations now functional
4. URL Encoding in Redirects
Issue: Malformed HTTP Location headers with special characters
Cause: Unencoded error messages containing &, =, ?, spaces
Fix: Wrapped all with neturl.QueryEscape()
Impact: Error messages display correctly
5. Modal Field Population
Issue: Edit modal opens blank, admin edits with empty fields
Cause: editTargetSet() didn't populate form before opening
Fix: Read data-* attributes and populate modal fields
Impact: Edit operations now show current values
6. Package Shadowing
Issue: Compiler error: url.QueryEscape undefined
Cause: Local url variable shadowed net/url package
Fix: Import alias neturl and rename variable to targetURL
Impact: Clean compilation
📊 Testing Coverage
E2E Tests
TestGroupTargetSetAPI✅TestAlertAPI✅TestCreateTargetSet✅TestAlertStats✅- All 34 integration tests passing
Unit Tests
- Database operations: ✅
- Authentication/JWT: ✅
- Usage logging: ✅
- Quota enforcement: ✅
Pre-Existing Issues
- LLM UI binding tests (13 failures) - Existing before v2.22.0
- Not introduced by Phase 1/2/3 changes
🔄 Migration Guide
For Users on v2.21.x
Step 1: Update Code
git pull origin main
git checkout v2.22.0Step 2: Build
go build ./cmd/cproxyStep 3: Run
./cproxy # Database migrations automaticNo Manual Steps Required:
- ✅ Schema updates automatic
- ✅ Existing data preserved
- ✅ No configuration changes needed
- ✅ Features available immediately
Breaking Changes
None. All changes are:
- ✅ Additive only
- ✅ Backward compatible
- ✅ Non-breaking to existing APIs
🔐 Security Enhancements
Session Protection
- All new endpoints require valid session
- CSRF protection via form tokens
- Session middleware chain validation
Access Control
- Worker nodes: Read-only mode for all operations
- Write protection: Only Admin nodes can modify
- Group-based routing: Enforced at handler level
Input Validation
- Target set ID format validation:
[a-zA-Z0-9_-]+ - URL encoding: All user input sanitized
- SQL injection prevention: GORM parameterized queries
Audit Logging
- All write operations logged
- Timestamp and user tracking
- Compliance-ready audit trail
📈 Performance
Dashboard Load Time
- Before: Full page render blocking
- After: Async card loading, sub-100ms render time
Database Queries
- Indexed queries for common operations
- Cached mappings during request
- Pagination limits result sets
Browser Experience
- Non-blocking initialization
- Graceful fallbacks
- Mobile-optimized layout
🛠️ Technical Details
Technology Stack
| Component | Version | Purpose |
|---|---|---|
| Go | 1.22+ | Backend runtime |
| SQLite | Latest | Data persistence |
| Tailwind CSS | v4 (CDN) | Styling |
| html/template | Built-in | Server-side rendering |
Architecture
Server-Side Rendering (SSR)
- No frontend framework required
- Tailwind CSS via CDN
- Go 1.22 net/http patterns
- Modal dialogs with CSS toggling
Database
- SQLite with WAL mode
- Automatic schema migrations
- Table-driven versioning
- Foreign key constraints
Middleware Chain
- Session validation
- Write-node protection
- Request logging
- Error handling
📚 Documentation
New/Updated Documents
CHANGELOG.md- Comprehensive change logdocs/ARCHITECTURE.md- Updated with v2.22 changesdocs/DATABASE.md- Schema documentation- API endpoint documentation
Available Resources
- Code comments for implementation details
- Test cases as usage examples
- Git history for debugging
🔗 Related Issues
Fixed by this release:
- Group-Target Set feature completion
- Alert dashboard enhancement
- Dashboard quick ops panel
- Package import shadowing
📋 Dependency Changes
No new dependencies added.
All features implemented with existing dependencies:
- ✅ No external packages
- ✅ No version bumps
- ✅ Same build time
🎉 What Users Get
For Administrators
- ✅ Flexible target set grouping
- ✅ Enhanced alert management
- ✅ Dashboard overview
- ✅ Audit trail
For Operations
- ✅ Real-time alert visibility
- ✅ Batch operations
- ✅ Historical query
- ✅ Health status at a glance
For Developers
- ✅ New API endpoints
- ✅ Database schema
- ✅ UI patterns
- ✅ Complete documentation
🚀 Known Limitations
- Quick Ops data: Requires alert service running
- History retention: Limited by database (typically 90 days UI limit)
- Real-time updates: Polling-based, not WebSocket
- *...