diff --git a/src/config/settings.py b/src/config/settings.py index ae37520..e875eb2 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -119,6 +119,11 @@ class Settings(BaseSettings): default=SecretStr("your-encryption-key-change-in-production") ) + # CPA 上传配置 + cpa_enabled: bool = Field(default=False) + cpa_api_url: str = Field(default="") # 例如: https://cpa.example.com + cpa_api_token: SecretStr = Field(default=SecretStr("")) + # 全局配置实例 _settings: Optional[Settings] = None diff --git a/src/web/routes/settings.py b/src/web/routes/settings.py index b3b9242..329d1c4 100644 --- a/src/web/routes/settings.py +++ b/src/web/routes/settings.py @@ -624,3 +624,74 @@ async def disable_proxy(proxy_id: int): if not proxy: raise HTTPException(status_code=404, detail="代理不存在") return {"success": True, "message": "代理已禁用"} + + +# ============== CPA 设置 ============== + +class CPASettings(BaseModel): + """CPA 设置""" + enabled: bool = False + api_url: str = "" + api_token: str = "" + + +class CPATestRequest(BaseModel): + """CPA 测试请求""" + api_url: str + api_token: str + + +@router.get("/cpa") +async def get_cpa_settings(): + """获取 CPA 设置""" + settings = get_settings() + + return { + "enabled": settings.cpa_enabled, + "api_url": settings.cpa_api_url, + "has_token": bool(settings.cpa_api_token and settings.cpa_api_token.get_secret_value()), + } + + +@router.post("/cpa") +async def update_cpa_settings(request: CPASettings): + """更新 CPA 设置""" + update_dict = { + "cpa_enabled": request.enabled, + "cpa_api_url": request.api_url, + } + + # 只有提供了 token 才更新 + if request.api_token: + update_dict["cpa_api_token"] = request.api_token + + update_settings(**update_dict) + + return {"success": True, "message": "CPA 设置已更新"} + + +@router.post("/cpa/test") +async def test_cpa_connection(request: CPATestRequest): + """测试 CPA 连接""" + from ...core.cpa_upload import test_cpa_connection as do_test + + settings = get_settings() + proxy = settings.proxy_url + + # 如果传入 'use_saved_token',使用已保存的 token + api_token = request.api_token + if api_token == 'use_saved_token' or not api_token: + if settings.cpa_api_token: + api_token = settings.cpa_api_token.get_secret_value() + else: + return { + "success": False, + "message": "未配置 API Token" + } + + success, message = do_test(request.api_url, api_token, proxy) + + return { + "success": success, + "message": message + } diff --git a/static/css/style.css b/static/css/style.css index 2f711e7..b6e7818 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -280,6 +280,15 @@ body { padding: var(--spacing-lg); } +/* 工具栏卡片允许下拉菜单溢出 */ +.card.toolbar-card { + overflow: visible; +} + +.card-body.toolbar { + overflow: visible; +} + /* ============================================ 表单元素 ============================================ */ @@ -710,6 +719,7 @@ body { align-items: center; flex-wrap: wrap; gap: var(--spacing-md); + overflow: visible; } .toolbar-left, diff --git a/static/js/accounts.js b/static/js/accounts.js index 2373b14..edfd81c 100644 --- a/static/js/accounts.js +++ b/static/js/accounts.js @@ -23,6 +23,7 @@ const elements = { refreshBtn: document.getElementById('refresh-btn'), batchRefreshBtn: document.getElementById('batch-refresh-btn'), batchValidateBtn: document.getElementById('batch-validate-btn'), + batchUploadCpaBtn: document.getElementById('batch-upload-cpa-btn'), batchDeleteBtn: document.getElementById('batch-delete-btn'), exportBtn: document.getElementById('export-btn'), exportMenu: document.getElementById('export-menu'), @@ -40,6 +41,7 @@ document.addEventListener('DOMContentLoaded', () => { loadStats(); loadAccounts(); initEventListeners(); + updateBatchButtons(); // 初始化按钮状态 }); // 事件监听 @@ -83,6 +85,9 @@ function initEventListeners() { // 批量验证Token elements.batchValidateBtn.addEventListener('click', handleBatchValidate); + // 批量上传CPA + elements.batchUploadCpaBtn.addEventListener('click', handleBatchUploadCpa); + // 批量删除 elements.batchDeleteBtn.addEventListener('click', handleBatchDelete); @@ -181,7 +186,7 @@ async function loadAccounts() { // 显示加载状态 elements.table.innerHTML = `