mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-05-07 04:42:47 +08:00
feat(account): 合并上传按钮
This commit is contained in:
@@ -160,6 +160,13 @@ def create_app() -> FastAPI:
|
||||
async def startup_event():
|
||||
"""应用启动事件"""
|
||||
import asyncio
|
||||
from ..database.init_db import initialize_database
|
||||
|
||||
# 确保数据库已初始化(reload 模式下子进程也需要初始化)
|
||||
try:
|
||||
initialize_database()
|
||||
except Exception as e:
|
||||
logger.warning(f"数据库初始化: {e}")
|
||||
|
||||
# 设置 TaskManager 的事件循环
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
@@ -787,6 +787,58 @@ async def batch_upload_accounts_to_cpa(request: BatchCPAUploadRequest):
|
||||
return results
|
||||
|
||||
|
||||
class Sub2ApiUploadRequest(BaseModel):
|
||||
"""单账号 Sub2API 上传请求"""
|
||||
service_id: Optional[int] = None
|
||||
concurrency: int = 3
|
||||
priority: int = 50
|
||||
|
||||
|
||||
@router.post("/{account_id}/upload-sub2api")
|
||||
async def upload_account_to_sub2api(account_id: int, request: Sub2ApiUploadRequest = None):
|
||||
"""上传单个账号到 Sub2API"""
|
||||
from ...core.sub2api_upload import upload_to_sub2api
|
||||
|
||||
service_id = request.service_id if request else None
|
||||
concurrency = request.concurrency if request else 3
|
||||
priority = request.priority if request else 50
|
||||
|
||||
api_url = None
|
||||
api_key = None
|
||||
if service_id:
|
||||
with get_db() as db:
|
||||
svc = crud.get_sub2api_service_by_id(db, service_id)
|
||||
if not svc:
|
||||
raise HTTPException(status_code=404, detail="指定的 Sub2API 服务不存在")
|
||||
api_url = svc.api_url
|
||||
api_key = svc.api_key
|
||||
else:
|
||||
with get_db() as db:
|
||||
svcs = crud.get_sub2api_services(db, enabled=True)
|
||||
if svcs:
|
||||
api_url = svcs[0].api_url
|
||||
api_key = svcs[0].api_key
|
||||
|
||||
if not api_url or not api_key:
|
||||
raise HTTPException(status_code=400, detail="未找到可用的 Sub2API 服务,请先在设置中配置")
|
||||
|
||||
with get_db() as db:
|
||||
account = crud.get_account_by_id(db, account_id)
|
||||
if not account:
|
||||
raise HTTPException(status_code=404, detail="账号不存在")
|
||||
if not account.access_token:
|
||||
return {"success": False, "error": "账号缺少 Token,无法上传"}
|
||||
|
||||
success, message = upload_to_sub2api(
|
||||
[account], api_url, api_key,
|
||||
concurrency=concurrency, priority=priority
|
||||
)
|
||||
if success:
|
||||
return {"success": True, "message": message}
|
||||
else:
|
||||
return {"success": False, "error": message}
|
||||
|
||||
|
||||
class BatchSub2ApiUploadRequest(BaseModel):
|
||||
"""批量 Sub2API 上传请求"""
|
||||
ids: List[int] = []
|
||||
|
||||
@@ -25,10 +25,8 @@ 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'),
|
||||
batchUploadSub2ApiBtn: document.getElementById('batch-upload-sub2api-btn'),
|
||||
batchUploadBtn: document.getElementById('batch-upload-btn'),
|
||||
batchCheckSubBtn: document.getElementById('batch-check-sub-btn'),
|
||||
batchUploadTmBtn: document.getElementById('batch-upload-tm-btn'),
|
||||
batchDeleteBtn: document.getElementById('batch-delete-btn'),
|
||||
exportBtn: document.getElementById('export-btn'),
|
||||
exportMenu: document.getElementById('export-menu'),
|
||||
@@ -95,17 +93,13 @@ function initEventListeners() {
|
||||
// 批量验证Token
|
||||
elements.batchValidateBtn.addEventListener('click', handleBatchValidate);
|
||||
|
||||
// 批量上传CPA
|
||||
elements.batchUploadCpaBtn.addEventListener('click', handleBatchUploadCpa);
|
||||
|
||||
// 批量检测订阅
|
||||
elements.batchCheckSubBtn.addEventListener('click', handleBatchCheckSubscription);
|
||||
|
||||
// 批量上传Sub2API
|
||||
elements.batchUploadSub2ApiBtn.addEventListener('click', handleBatchUploadSub2Api);
|
||||
|
||||
// 批量上传TM
|
||||
elements.batchUploadTmBtn.addEventListener('click', handleBatchUploadTm);
|
||||
// 上传下拉菜单
|
||||
document.getElementById('batch-upload-cpa-item').addEventListener('click', (e) => { e.preventDefault(); handleBatchUploadCpa(); });
|
||||
document.getElementById('batch-upload-sub2api-item').addEventListener('click', (e) => { e.preventDefault(); handleBatchUploadSub2Api(); });
|
||||
document.getElementById('batch-upload-tm-item').addEventListener('click', (e) => { e.preventDefault(); handleBatchUploadTm(); });
|
||||
|
||||
// 批量删除
|
||||
elements.batchDeleteBtn.addEventListener('click', handleBatchDelete);
|
||||
@@ -324,15 +318,12 @@ function renderAccounts(accounts) {
|
||||
<button class="btn btn-ghost btn-sm" onclick="refreshToken(${account.id})" title="刷新Token">
|
||||
🔄
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="uploadToCpa(${account.id})" title="上传到CPA">
|
||||
<button class="btn btn-ghost btn-sm" onclick="uploadAccount(${account.id})" title="上传账号">
|
||||
☁️
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="markSubscription(${account.id})" title="标记订阅">
|
||||
🏷️
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="uploadToTm(${account.id})" title="上传到Team Manager">
|
||||
🚀
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="viewAccount(${account.id})" title="查看详情">
|
||||
👁️
|
||||
</button>
|
||||
@@ -469,19 +460,15 @@ function updateBatchButtons() {
|
||||
elements.batchDeleteBtn.disabled = count === 0;
|
||||
elements.batchRefreshBtn.disabled = count === 0;
|
||||
elements.batchValidateBtn.disabled = count === 0;
|
||||
elements.batchUploadCpaBtn.disabled = count === 0;
|
||||
elements.batchUploadSub2ApiBtn.disabled = count === 0;
|
||||
elements.batchUploadBtn.disabled = count === 0;
|
||||
elements.batchCheckSubBtn.disabled = count === 0;
|
||||
elements.batchUploadTmBtn.disabled = count === 0;
|
||||
elements.exportBtn.disabled = count === 0;
|
||||
|
||||
elements.batchDeleteBtn.textContent = count > 0 ? `🗑️ 删除 (${count})` : '🗑️ 批量删除';
|
||||
elements.batchRefreshBtn.textContent = count > 0 ? `🔄 刷新 (${count})` : '🔄 刷新Token';
|
||||
elements.batchValidateBtn.textContent = count > 0 ? `✅ 验证 (${count})` : '✅ 验证Token';
|
||||
elements.batchUploadCpaBtn.textContent = count > 0 ? `☁️ 上传 (${count})` : '☁️ 上传CPA';
|
||||
elements.batchUploadSub2ApiBtn.textContent = count > 0 ? `🔗 Sub2API (${count})` : '🔗 上传Sub2API';
|
||||
elements.batchUploadBtn.textContent = count > 0 ? `☁️ 上传 (${count})` : '☁️ 上传';
|
||||
elements.batchCheckSubBtn.textContent = count > 0 ? `🔍 检测 (${count})` : '🔍 检测订阅';
|
||||
elements.batchUploadTmBtn.textContent = count > 0 ? `🚀 上传TM (${count})` : '🚀 上传TM';
|
||||
}
|
||||
|
||||
// 刷新单个账号Token
|
||||
@@ -816,6 +803,43 @@ function selectCpaService() {
|
||||
});
|
||||
}
|
||||
|
||||
// 统一上传入口:弹出目标选择
|
||||
async function uploadAccount(id) {
|
||||
const targets = [
|
||||
{ label: '☁️ 上传到 CPA', value: 'cpa' },
|
||||
{ label: '🔗 上传到 Sub2API', value: 'sub2api' },
|
||||
{ label: '🚀 上传到 Team Manager', value: 'tm' },
|
||||
];
|
||||
|
||||
const choice = await new Promise((resolve) => {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal active';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width:360px;">
|
||||
<div class="modal-header">
|
||||
<h3>☁️ 选择上传目标</h3>
|
||||
<button class="modal-close" id="_upload-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="display:flex;flex-direction:column;gap:8px;">
|
||||
${targets.map(t => `
|
||||
<button class="btn btn-secondary" data-val="${t.value}" style="text-align:left;">${t.label}</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
modal.querySelector('#_upload-close').addEventListener('click', () => { modal.remove(); resolve(null); });
|
||||
modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); resolve(null); } });
|
||||
modal.querySelectorAll('button[data-val]').forEach(btn => {
|
||||
btn.addEventListener('click', () => { modal.remove(); resolve(btn.dataset.val); });
|
||||
});
|
||||
});
|
||||
|
||||
if (!choice) return;
|
||||
if (choice === 'cpa') return uploadToCpa(id);
|
||||
if (choice === 'sub2api') return uploadToSub2Api(id);
|
||||
if (choice === 'tm') return uploadToTm(id);
|
||||
}
|
||||
|
||||
// 上传单个账号到CPA
|
||||
async function uploadToCpa(id) {
|
||||
const choice = await selectCpaService();
|
||||
@@ -849,8 +873,8 @@ async function handleBatchUploadCpa() {
|
||||
const confirmed = await confirm(`确定要将选中的 ${count} 个账号上传到CPA吗?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
elements.batchUploadCpaBtn.disabled = true;
|
||||
elements.batchUploadCpaBtn.textContent = '上传中...';
|
||||
elements.batchUploadBtn.disabled = true;
|
||||
elements.batchUploadBtn.textContent = '上传中...';
|
||||
|
||||
try {
|
||||
const payload = buildBatchPayload();
|
||||
@@ -994,8 +1018,8 @@ async function handleBatchUploadSub2Api() {
|
||||
const confirmed = await confirm(`确定要将选中的 ${count} 个账号上传到 Sub2API 吗?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
elements.batchUploadSub2ApiBtn.disabled = true;
|
||||
elements.batchUploadSub2ApiBtn.textContent = '上传中...';
|
||||
elements.batchUploadBtn.disabled = true;
|
||||
elements.batchUploadBtn.textContent = '上传中...';
|
||||
|
||||
try {
|
||||
const payload = buildBatchPayload();
|
||||
@@ -1017,6 +1041,26 @@ async function handleBatchUploadSub2Api() {
|
||||
|
||||
// ============== Team Manager 上传 ==============
|
||||
|
||||
// 上传单账号到 Sub2API
|
||||
async function uploadToSub2Api(id) {
|
||||
const choice = await selectSub2ApiService();
|
||||
if (choice === null) return;
|
||||
try {
|
||||
toast.info('正在上传到 Sub2API...');
|
||||
const payload = {};
|
||||
if (choice.service_id != null) payload.service_id = choice.service_id;
|
||||
const result = await api.post(`/accounts/${id}/upload-sub2api`, payload);
|
||||
if (result.success) {
|
||||
toast.success('上传成功');
|
||||
loadAccounts();
|
||||
} else {
|
||||
toast.error('上传失败: ' + (result.error || result.message || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error('上传失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 上传单账号到 Team Manager
|
||||
async function uploadToTm(id) {
|
||||
try {
|
||||
@@ -1039,8 +1083,8 @@ async function handleBatchUploadTm() {
|
||||
const confirmed = await confirm(`确定要将选中的 ${count} 个账号上传到 Team Manager 吗?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
elements.batchUploadTmBtn.disabled = true;
|
||||
elements.batchUploadTmBtn.textContent = '上传中...';
|
||||
elements.batchUploadBtn.disabled = true;
|
||||
elements.batchUploadBtn.textContent = '上传中...';
|
||||
|
||||
try {
|
||||
const result = await api.post('/payment/accounts/batch-upload-tm', buildBatchPayload());
|
||||
|
||||
@@ -125,18 +125,19 @@
|
||||
<button class="btn btn-info" id="batch-validate-btn" disabled title="批量验证Token">
|
||||
✅ 验证Token
|
||||
</button>
|
||||
<button class="btn btn-success" id="batch-upload-cpa-btn" disabled title="批量上传到CPA">
|
||||
☁️ 上传CPA
|
||||
</button>
|
||||
<button class="btn btn-info" id="batch-check-sub-btn" disabled title="批量检测订阅状态">
|
||||
🔍 检测订阅
|
||||
</button>
|
||||
<button class="btn btn-info" id="batch-upload-sub2api-btn" disabled title="批量上传到Sub2API">
|
||||
🔗 上传Sub2API
|
||||
</button>
|
||||
<button class="btn btn-success" id="batch-upload-tm-btn" disabled title="批量上传到Team Manager">
|
||||
🚀 上传TM
|
||||
</button>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-success" id="batch-upload-btn" disabled>
|
||||
☁️ 上传
|
||||
</button>
|
||||
<div class="dropdown-menu" id="upload-menu">
|
||||
<a href="#" class="dropdown-item" id="batch-upload-cpa-item">☁️ 上传到 CPA</a>
|
||||
<a href="#" class="dropdown-item" id="batch-upload-sub2api-item">🔗 上传到 Sub2API</a>
|
||||
<a href="#" class="dropdown-item" id="batch-upload-tm-item">🚀 上传到 Team Manager</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger" id="batch-delete-btn" disabled>
|
||||
🗑️ 批量删除
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user