Files
SmartUp/backend/app/schemas/upstream.py
T
liumangmang 871557e4ae 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
2026-06-01 16:46:42 +08:00

153 lines
4.0 KiB
Python

from datetime import datetime
from typing import Optional, Any
from pydantic import BaseModel, Field
class AuthConfigBearer(BaseModel):
token: str = ""
class AuthConfigApiKey(BaseModel):
key: str = ""
header: str = "Authorization"
class AuthConfigLoginPassword(BaseModel):
email: str = ""
password: str = ""
login_path: str = "/auth/login"
class UpstreamCreate(BaseModel):
name: str
base_url: str
api_prefix: str = "/api/v1"
auth_type: str = "login_password" # none | bearer | api_key | login_password
auth_config: dict[str, Any] = {}
rate_endpoint: str = "/groups/rates"
groups_endpoint: str = "/groups/available"
enabled: bool = True
check_interval_seconds: int = 600
timeout_seconds: int = 30
balance_endpoint: str = ""
balance_response_path: str = ""
balance_divisor: float = 1.0
balance_alert_threshold: Optional[float] = None
class UpstreamUpdate(BaseModel):
name: Optional[str] = None
base_url: Optional[str] = None
api_prefix: Optional[str] = None
auth_type: Optional[str] = None
auth_config: Optional[dict[str, Any]] = None
rate_endpoint: Optional[str] = None
groups_endpoint: Optional[str] = None
enabled: Optional[bool] = None
check_interval_seconds: Optional[int] = None
timeout_seconds: Optional[int] = None
balance_endpoint: Optional[str] = None
balance_response_path: Optional[str] = None
balance_divisor: Optional[float] = None
balance_alert_threshold: Optional[float] = None
class UpstreamResponse(BaseModel):
id: int
name: str
base_url: str
api_prefix: str
auth_type: str
auth_config_masked: dict[str, Any] # secrets replaced with ***
rate_endpoint: str
groups_endpoint: str
enabled: bool
check_interval_seconds: int
timeout_seconds: int
last_status: str
last_checked_at: Optional[datetime]
last_error: Optional[str]
balance: Optional[float] = None
balance_updated_at: Optional[datetime] = None
balance_endpoint: str = ""
balance_response_path: str = ""
balance_divisor: float = 1.0
balance_alert_threshold: Optional[float] = None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class SnapshotResponse(BaseModel):
id: int
upstream_id: int
snapshot: dict[str, Any]
captured_at: datetime
model_config = {"from_attributes": True}
class TestResult(BaseModel):
success: bool
message: str
detail: Optional[str] = None
class GenerateKeysByGroupsRequest(BaseModel):
group_ids: list[str] = Field(default_factory=list)
name_prefix: str = "SmartUp"
quota: float = Field(default=0, ge=0)
expires_in_days: Optional[int] = Field(default=None, ge=1)
rate_limit_5h: float = Field(default=0, ge=0)
rate_limit_1d: float = Field(default=0, ge=0)
rate_limit_7d: float = Field(default=0, ge=0)
endpoint: str = "/keys"
class GeneratedUpstreamKeyResponse(BaseModel):
id: Optional[int] = None
upstream_id: int
group_id: str
group_name: str = ""
key_id: Optional[str] = None
key_name: str
key_value: Optional[str] = None
masked_key: str = ""
status: str
error: Optional[str] = None
imported_website_id: Optional[int] = None
imported_account_id: Optional[str] = None
imported_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
has_key_value: bool = False
class GenerateKeysByGroupsResponse(BaseModel):
success: bool
message: str
items: list[GeneratedUpstreamKeyResponse]
class UpstreamBatchActionItem(BaseModel):
upstream_id: int
upstream_name: str
status: str # success | failed | skipped
message: str
detail: Optional[str] = None
class UpstreamBatchActionSummary(BaseModel):
total: int
success: int
failed: int
skipped: int
class UpstreamBatchActionResponse(BaseModel):
success: bool
message: str
summary: UpstreamBatchActionSummary
items: list[UpstreamBatchActionItem]