feat(upstreams): add batch test-all / check-now-all endpoints

- POST /api/upstreams/test-all: batch connection test for all enabled
  upstreams (no snapshot, no webhook); updates last_status, balance
- POST /api/upstreams/check-now-all: full batch sync (snapshot, diff,
  webhook, key sync, priority sync); mirrors single check-now behavior
- Both routes are registered before /{uid} to avoid path capture
- Skips disabled upstreams (status=skipped); single failure does not
  abort subsequent upstreams (serial execution)
- Returns UpstreamBatchActionResponse with per-item detail and summary

Refactor: extract _test_upstream_core(db, u) and _check_now_core(db, u)
- All four routes (single + batch × 2) now share the same core helpers
- Eliminates duplicate logic and future divergence risk

Frontend:
- Add UpstreamBatchActionResponse / Item / Summary TS types
- Add upstreamsApi.testAll() and upstreamsApi.checkNowAll()
- Add '一键测试' and '一键同步' buttons in Upstreams.vue toolbar
  (order: 一键测试 → 一键同步 → 刷新 → 新增上游)
- Buttons disabled when list is empty or another batch op is running
- On completion: refresh list + ElMessageBox with per-item failure detail
This commit is contained in:
liumangmang
2026-06-01 16:46:42 +08:00
parent a949969c4d
commit 871557e4ae
4 changed files with 358 additions and 107 deletions
+24
View File
@@ -139,6 +139,28 @@ export interface GenerateKeysByGroupsForm {
endpoint: string
}
export interface UpstreamBatchActionItem {
upstream_id: number
upstream_name: string
status: 'success' | 'failed' | 'skipped'
message: string
detail?: string | null
}
export interface UpstreamBatchActionSummary {
total: number
success: number
failed: number
skipped: number
}
export interface UpstreamBatchActionResponse {
success: boolean
message: string
summary: UpstreamBatchActionSummary
items: UpstreamBatchActionItem[]
}
export const upstreamsApi = {
list: () => api.get<UpstreamData[]>('/api/upstreams'),
create: (data: UpstreamForm) => api.post<UpstreamData>('/api/upstreams', data),
@@ -152,6 +174,8 @@ export const upstreamsApi = {
latestSnapshot: (id: number) => api.get(`/api/upstreams/${id}/snapshots/latest`),
listSnapshots: (id: number, limit = 20, offset = 0) =>
api.get<any[]>(`/api/upstreams/${id}/snapshots`, { params: { limit, offset } }),
testAll: () => api.post<UpstreamBatchActionResponse>('/api/upstreams/test-all'),
checkNowAll: () => api.post<UpstreamBatchActionResponse>('/api/upstreams/check-now-all'),
}
// ——— Websites ———