feat(pool,ui,trace): 号池按页配额刷新、配额倒计时、Trace 全量候选与 UI 用语统一

- 号池管理支持按当前页 Key 刷新配额,后端 refresh-quota 接口支持 key_ids 参数筛选
- 配额进度条 tooltip 展示重置倒计时,前端解析后端重置时间并实时倒计时
- Provider 选择器在无号池提供商时禁用,切换/刷新后保持选中状态对齐
- 启停账号后立即更新调度标签并刷新列表
- Trace 监控展示全量候选记录,不再过滤 available/unused 状态
- 请求时间线号池节点与 Provider 节点去重
- 全局 UI 用语统一:已停用/已禁用 -> 停用/禁用
- 导航菜单调整模型管理与号池管理顺序
This commit is contained in:
fawney19
2026-03-03 11:32:41 +08:00
parent 022aec5720
commit 7ebce161e8
24 changed files with 359 additions and 75 deletions

View File

@@ -147,7 +147,33 @@ async def test_refresh_provider_quota_no_active_keys_returns_empty() -> None:
"failed": 0,
"total": 0,
"results": [],
"message": "没有活跃的 Key",
"message": "没有可刷新的 Key",
}
@pytest.mark.asyncio
async def test_refresh_provider_quota_empty_key_ids_returns_empty() -> None:
provider = SimpleNamespace(
id="p1",
provider_type=ProviderType.CODEX,
endpoints=[SimpleNamespace(api_format="openai:cli", is_active=True)],
)
key = SimpleNamespace(id="k1", name="K1", upstream_metadata={})
db = _FakeDB(provider=provider, keys=[key])
result = await refresh_provider_quota_for_provider(
db=cast(Any, db),
provider_id="p1",
codex_wham_usage_url="https://example.test/wham/usage",
key_ids=[],
)
assert result == {
"success": 0,
"failed": 0,
"total": 0,
"results": [],
"message": "未提供可刷新的 Key",
}

View File

@@ -42,7 +42,7 @@ def _context() -> SimpleNamespace:
)
def test_trace_prefers_attempted_subset(monkeypatch: object) -> None:
def test_trace_returns_all_candidates_including_unused(monkeypatch: object) -> None:
candidates = [
_candidate(status="available"),
_candidate(status="unused"),
@@ -57,13 +57,13 @@ def test_trace_prefers_attempted_subset(monkeypatch: object) -> None:
adapter = AdminGetRequestTraceAdapter(request_id="req-1")
response = asyncio.run(adapter.handle(_context()))
assert response.total_candidates == 1
assert len(response.candidates) == 1
assert response.candidates[0].status == "failed"
assert response.total_candidates == 3
assert len(response.candidates) == 3
assert {c.status for c in response.candidates} == {"available", "unused", "failed"}
assert response.total_latency_ms == 123
def test_trace_falls_back_to_all_when_no_attempted(monkeypatch: object) -> None:
def test_trace_returns_unattempted_candidates(monkeypatch: object) -> None:
candidates = [
_candidate(status="available"),
_candidate(status="unused"),