mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-05-06 20:02:51 +08:00
feat(upload): #13 添加上传至sub2api
This commit is contained in:
@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_, desc, asc, func
|
||||
|
||||
from .models import Account, EmailService, RegistrationTask, Setting, Proxy, CpaService
|
||||
from .models import Account, EmailService, RegistrationTask, Setting, Proxy, CpaService, Sub2ApiService
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -583,4 +583,68 @@ def delete_cpa_service(db: Session, service_id: int) -> bool:
|
||||
return False
|
||||
db.delete(db_service)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Sub2API 服务 CRUD
|
||||
# ============================================================================
|
||||
|
||||
def create_sub2api_service(
|
||||
db: Session,
|
||||
name: str,
|
||||
api_url: str,
|
||||
api_key: str,
|
||||
enabled: bool = True,
|
||||
priority: int = 0
|
||||
) -> Sub2ApiService:
|
||||
"""创建 Sub2API 服务配置"""
|
||||
svc = Sub2ApiService(
|
||||
name=name,
|
||||
api_url=api_url,
|
||||
api_key=api_key,
|
||||
enabled=enabled,
|
||||
priority=priority,
|
||||
)
|
||||
db.add(svc)
|
||||
db.commit()
|
||||
db.refresh(svc)
|
||||
return svc
|
||||
|
||||
|
||||
def get_sub2api_service_by_id(db: Session, service_id: int) -> Optional[Sub2ApiService]:
|
||||
"""按 ID 获取 Sub2API 服务"""
|
||||
return db.query(Sub2ApiService).filter(Sub2ApiService.id == service_id).first()
|
||||
|
||||
|
||||
def get_sub2api_services(
|
||||
db: Session,
|
||||
enabled: Optional[bool] = None
|
||||
) -> List[Sub2ApiService]:
|
||||
"""获取 Sub2API 服务列表"""
|
||||
query = db.query(Sub2ApiService)
|
||||
if enabled is not None:
|
||||
query = query.filter(Sub2ApiService.enabled == enabled)
|
||||
return query.order_by(asc(Sub2ApiService.priority), asc(Sub2ApiService.id)).all()
|
||||
|
||||
|
||||
def update_sub2api_service(db: Session, service_id: int, **kwargs) -> Optional[Sub2ApiService]:
|
||||
"""更新 Sub2API 服务配置"""
|
||||
svc = get_sub2api_service_by_id(db, service_id)
|
||||
if not svc:
|
||||
return None
|
||||
for key, value in kwargs.items():
|
||||
setattr(svc, key, value)
|
||||
db.commit()
|
||||
db.refresh(svc)
|
||||
return svc
|
||||
|
||||
|
||||
def delete_sub2api_service(db: Session, service_id: int) -> bool:
|
||||
"""删除 Sub2API 服务配置"""
|
||||
svc = get_sub2api_service_by_id(db, service_id)
|
||||
if not svc:
|
||||
return False
|
||||
db.delete(svc)
|
||||
db.commit()
|
||||
return True
|
||||
@@ -144,6 +144,20 @@ class CpaService(Base):
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class Sub2ApiService(Base):
|
||||
"""Sub2API 服务配置表"""
|
||||
__tablename__ = 'sub2api_services'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False) # 服务名称
|
||||
api_url = Column(String(500), nullable=False) # API URL (host)
|
||||
api_key = Column(Text, nullable=False) # x-api-key
|
||||
enabled = Column(Boolean, default=True)
|
||||
priority = Column(Integer, default=0) # 优先级
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
class Proxy(Base):
|
||||
"""代理列表表"""
|
||||
__tablename__ = 'proxies'
|
||||
|
||||
@@ -10,6 +10,7 @@ from .settings import router as settings_router
|
||||
from .email_services import router as email_services_router
|
||||
from .payment import router as payment_router
|
||||
from .cpa_services import router as cpa_services_router
|
||||
from .sub2api_services import router as sub2api_services_router
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
@@ -20,3 +21,4 @@ api_router.include_router(settings_router, prefix="/settings", tags=["settings"]
|
||||
api_router.include_router(email_services_router, prefix="/email-services", tags=["email-services"])
|
||||
api_router.include_router(payment_router, prefix="/payment", tags=["payment"])
|
||||
api_router.include_router(cpa_services_router, prefix="/cpa-services", tags=["cpa-services"])
|
||||
api_router.include_router(sub2api_services_router, prefix="/sub2api-services", tags=["sub2api-services"])
|
||||
|
||||
@@ -785,3 +785,54 @@ async def batch_upload_accounts_to_cpa(request: BatchCPAUploadRequest):
|
||||
|
||||
results = batch_upload_to_cpa(ids, proxy, api_url=cpa_api_url, api_token=cpa_api_token)
|
||||
return results
|
||||
|
||||
|
||||
class BatchSub2ApiUploadRequest(BaseModel):
|
||||
"""批量 Sub2API 上传请求"""
|
||||
ids: List[int] = []
|
||||
select_all: bool = False
|
||||
status_filter: Optional[str] = None
|
||||
email_service_filter: Optional[str] = None
|
||||
search_filter: Optional[str] = None
|
||||
service_id: Optional[int] = None # 指定 Sub2API 服务 ID,不传则使用第一个启用的
|
||||
concurrency: int = 3
|
||||
priority: int = 50
|
||||
|
||||
|
||||
@router.post("/batch-upload-sub2api")
|
||||
async def batch_upload_accounts_to_sub2api(request: BatchSub2ApiUploadRequest):
|
||||
"""批量上传账号到 Sub2API"""
|
||||
from ...core.sub2api_upload import batch_upload_to_sub2api
|
||||
|
||||
# 解析指定的 Sub2API 服务
|
||||
api_url = None
|
||||
api_key = None
|
||||
if request.service_id:
|
||||
with get_db() as db:
|
||||
svc = crud.get_sub2api_service_by_id(db, request.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:
|
||||
ids = resolve_account_ids(
|
||||
db, request.ids, request.select_all,
|
||||
request.status_filter, request.email_service_filter, request.search_filter
|
||||
)
|
||||
|
||||
results = batch_upload_to_sub2api(
|
||||
ids, api_url, api_key,
|
||||
concurrency=request.concurrency,
|
||||
priority=request.priority,
|
||||
)
|
||||
return results
|
||||
|
||||
@@ -26,6 +26,7 @@ const elements = {
|
||||
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'),
|
||||
batchCheckSubBtn: document.getElementById('batch-check-sub-btn'),
|
||||
batchUploadTmBtn: document.getElementById('batch-upload-tm-btn'),
|
||||
batchDeleteBtn: document.getElementById('batch-delete-btn'),
|
||||
@@ -100,6 +101,9 @@ function initEventListeners() {
|
||||
// 批量检测订阅
|
||||
elements.batchCheckSubBtn.addEventListener('click', handleBatchCheckSubscription);
|
||||
|
||||
// 批量上传Sub2API
|
||||
elements.batchUploadSub2ApiBtn.addEventListener('click', handleBatchUploadSub2Api);
|
||||
|
||||
// 批量上传TM
|
||||
elements.batchUploadTmBtn.addEventListener('click', handleBatchUploadTm);
|
||||
|
||||
@@ -466,6 +470,7 @@ function updateBatchButtons() {
|
||||
elements.batchRefreshBtn.disabled = count === 0;
|
||||
elements.batchValidateBtn.disabled = count === 0;
|
||||
elements.batchUploadCpaBtn.disabled = count === 0;
|
||||
elements.batchUploadSub2ApiBtn.disabled = count === 0;
|
||||
elements.batchCheckSubBtn.disabled = count === 0;
|
||||
elements.batchUploadTmBtn.disabled = count === 0;
|
||||
elements.exportBtn.disabled = count === 0;
|
||||
@@ -474,6 +479,7 @@ function updateBatchButtons() {
|
||||
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.batchCheckSubBtn.textContent = count > 0 ? `🔍 检测 (${count})` : '🔍 检测订阅';
|
||||
elements.batchUploadTmBtn.textContent = count > 0 ? `🚀 上传TM (${count})` : '🚀 上传TM';
|
||||
}
|
||||
@@ -908,6 +914,107 @@ async function handleBatchCheckSubscription() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============== Sub2API 上传 ==============
|
||||
|
||||
// 弹出 Sub2API 服务选择框,返回 Promise<{service_id: number|null}|null>
|
||||
// null 表示用户取消,{service_id: null} 表示自动选择
|
||||
function selectSub2ApiService() {
|
||||
return new Promise(async (resolve) => {
|
||||
const modal = document.getElementById('sub2api-service-modal');
|
||||
const listEl = document.getElementById('sub2api-service-list');
|
||||
const closeBtn = document.getElementById('close-sub2api-modal');
|
||||
const cancelBtn = document.getElementById('cancel-sub2api-modal-btn');
|
||||
const autoBtn = document.getElementById('sub2api-use-auto-btn');
|
||||
|
||||
listEl.innerHTML = '<div style="text-align:center;color:var(--text-muted)">加载中...</div>';
|
||||
modal.classList.add('active');
|
||||
|
||||
let services = [];
|
||||
try {
|
||||
services = await api.get('/sub2api-services?enabled=true');
|
||||
} catch (e) {
|
||||
services = [];
|
||||
}
|
||||
|
||||
if (services.length === 0) {
|
||||
listEl.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:12px;">暂无已启用的 Sub2API 服务,将自动选择第一个</div>';
|
||||
} else {
|
||||
listEl.innerHTML = services.map(s => `
|
||||
<div class="sub2api-service-item" data-id="${s.id}" style="
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
">
|
||||
<div>
|
||||
<div style="font-weight:500;">${escapeHtml(s.name)}</div>
|
||||
<div style="font-size:0.8rem;color:var(--text-muted);">${escapeHtml(s.api_url)}</div>
|
||||
</div>
|
||||
<span class="badge" style="background:var(--primary);color:#fff;font-size:0.7rem;padding:2px 8px;border-radius:10px;">选择</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
listEl.querySelectorAll('.sub2api-service-item').forEach(item => {
|
||||
item.addEventListener('mouseenter', () => item.style.background = 'var(--surface-hover)');
|
||||
item.addEventListener('mouseleave', () => item.style.background = '');
|
||||
item.addEventListener('click', () => {
|
||||
cleanup();
|
||||
resolve({ service_id: parseInt(item.dataset.id) });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
modal.classList.remove('active');
|
||||
closeBtn.removeEventListener('click', onCancel);
|
||||
cancelBtn.removeEventListener('click', onCancel);
|
||||
autoBtn.removeEventListener('click', onAuto);
|
||||
}
|
||||
function onCancel() { cleanup(); resolve(null); }
|
||||
function onAuto() { cleanup(); resolve({ service_id: null }); }
|
||||
|
||||
closeBtn.addEventListener('click', onCancel);
|
||||
cancelBtn.addEventListener('click', onCancel);
|
||||
autoBtn.addEventListener('click', onAuto);
|
||||
});
|
||||
}
|
||||
|
||||
// 批量上传到 Sub2API
|
||||
async function handleBatchUploadSub2Api() {
|
||||
const count = getEffectiveCount();
|
||||
if (count === 0) return;
|
||||
|
||||
const choice = await selectSub2ApiService();
|
||||
if (choice === null) return; // 用户取消
|
||||
|
||||
const confirmed = await confirm(`确定要将选中的 ${count} 个账号上传到 Sub2API 吗?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
elements.batchUploadSub2ApiBtn.disabled = true;
|
||||
elements.batchUploadSub2ApiBtn.textContent = '上传中...';
|
||||
|
||||
try {
|
||||
const payload = buildBatchPayload();
|
||||
if (choice.service_id != null) payload.service_id = choice.service_id;
|
||||
const result = await api.post('/accounts/batch-upload-sub2api', payload);
|
||||
|
||||
let message = `成功: ${result.success_count}`;
|
||||
if (result.failed_count > 0) message += `, 失败: ${result.failed_count}`;
|
||||
if (result.skipped_count > 0) message += `, 跳过: ${result.skipped_count}`;
|
||||
|
||||
toast.success(message);
|
||||
loadAccounts();
|
||||
} catch (error) {
|
||||
toast.error('批量上传失败: ' + error.message);
|
||||
} finally {
|
||||
updateBatchButtons();
|
||||
}
|
||||
}
|
||||
|
||||
// ============== Team Manager 上传 ==============
|
||||
|
||||
// 上传单账号到 Team Manager
|
||||
|
||||
@@ -48,6 +48,15 @@ const elements = {
|
||||
cpaServiceForm: document.getElementById('cpa-service-form'),
|
||||
cpaServiceModalTitle: document.getElementById('cpa-service-modal-title'),
|
||||
testCpaServiceBtn: document.getElementById('test-cpa-service-btn'),
|
||||
// Sub2API 服务管理
|
||||
addSub2ApiServiceBtn: document.getElementById('add-sub2api-service-btn'),
|
||||
sub2ApiServicesTable: document.getElementById('sub2api-services-table'),
|
||||
sub2ApiServiceEditModal: document.getElementById('sub2api-service-edit-modal'),
|
||||
closeSub2ApiServiceModal: document.getElementById('close-sub2api-service-modal'),
|
||||
cancelSub2ApiServiceBtn: document.getElementById('cancel-sub2api-service-btn'),
|
||||
sub2ApiServiceForm: document.getElementById('sub2api-service-form'),
|
||||
sub2ApiServiceModalTitle: document.getElementById('sub2api-service-modal-title'),
|
||||
testSub2ApiServiceBtn: document.getElementById('test-sub2api-service-btn'),
|
||||
// Team Manager 设置
|
||||
tmForm: document.getElementById('tm-form'),
|
||||
testTmBtn: document.getElementById('test-tm-btn'),
|
||||
@@ -70,6 +79,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loadDatabaseInfo();
|
||||
loadProxies();
|
||||
loadCpaServices();
|
||||
loadSub2ApiServices();
|
||||
initEventListeners();
|
||||
});
|
||||
|
||||
@@ -255,6 +265,28 @@ function initEventListeners() {
|
||||
if (elements.testCpaServiceBtn) {
|
||||
elements.testCpaServiceBtn.addEventListener('click', handleTestCpaService);
|
||||
}
|
||||
|
||||
// Sub2API 服务管理
|
||||
if (elements.addSub2ApiServiceBtn) {
|
||||
elements.addSub2ApiServiceBtn.addEventListener('click', () => openSub2ApiServiceModal());
|
||||
}
|
||||
if (elements.closeSub2ApiServiceModal) {
|
||||
elements.closeSub2ApiServiceModal.addEventListener('click', closeSub2ApiServiceModal);
|
||||
}
|
||||
if (elements.cancelSub2ApiServiceBtn) {
|
||||
elements.cancelSub2ApiServiceBtn.addEventListener('click', closeSub2ApiServiceModal);
|
||||
}
|
||||
if (elements.sub2ApiServiceEditModal) {
|
||||
elements.sub2ApiServiceEditModal.addEventListener('click', (e) => {
|
||||
if (e.target === elements.sub2ApiServiceEditModal) closeSub2ApiServiceModal();
|
||||
});
|
||||
}
|
||||
if (elements.sub2ApiServiceForm) {
|
||||
elements.sub2ApiServiceForm.addEventListener('submit', handleSaveSub2ApiService);
|
||||
}
|
||||
if (elements.testSub2ApiServiceBtn) {
|
||||
elements.testSub2ApiServiceBtn.addEventListener('click', handleTestSub2ApiService);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
@@ -1230,6 +1262,154 @@ async function handleTestCpaService() {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sub2API 服务管理
|
||||
// ============================================================================
|
||||
|
||||
let _sub2apiEditingId = null;
|
||||
|
||||
async function loadSub2ApiServices() {
|
||||
try {
|
||||
const services = await api.get('/sub2api-services');
|
||||
renderSub2ApiServices(services);
|
||||
} catch (e) {
|
||||
if (elements.sub2ApiServicesTable) {
|
||||
elements.sub2ApiServicesTable.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">加载失败</td></tr>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderSub2ApiServices(services) {
|
||||
if (!elements.sub2ApiServicesTable) return;
|
||||
if (!services || services.length === 0) {
|
||||
elements.sub2ApiServicesTable.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">暂无服务,点击"添加服务"按钮添加</td></tr>';
|
||||
return;
|
||||
}
|
||||
elements.sub2ApiServicesTable.innerHTML = services.map(s => `
|
||||
<tr>
|
||||
<td>${escapeHtml(s.name)}</td>
|
||||
<td><code>${escapeHtml(s.api_url)}</code></td>
|
||||
<td><span class="status-badge ${s.enabled ? 'active' : 'disabled'}">${s.enabled ? '已启用' : '已禁用'}</span></td>
|
||||
<td>${s.priority}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-ghost btn-sm" onclick="editSub2ApiService(${s.id})" title="编辑">✏️</button>
|
||||
<button class="btn btn-ghost btn-sm" onclick="deleteSub2ApiService(${s.id}, '${escapeHtml(s.name)}')" title="删除">🗑️</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function openSub2ApiServiceModal(svc = null) {
|
||||
_sub2apiEditingId = svc ? svc.id : null;
|
||||
elements.sub2ApiServiceModalTitle.textContent = svc ? '编辑 Sub2API 服务' : '添加 Sub2API 服务';
|
||||
elements.sub2ApiServiceForm.reset();
|
||||
document.getElementById('sub2api-service-id').value = svc ? svc.id : '';
|
||||
if (svc) {
|
||||
document.getElementById('sub2api-service-name').value = svc.name || '';
|
||||
document.getElementById('sub2api-service-url').value = svc.api_url || '';
|
||||
document.getElementById('sub2api-service-priority').value = svc.priority ?? 0;
|
||||
document.getElementById('sub2api-service-enabled').checked = svc.enabled !== false;
|
||||
document.getElementById('sub2api-service-key').placeholder = svc.has_key ? '已配置,留空保持不变' : '请输入 API Key';
|
||||
}
|
||||
elements.sub2ApiServiceEditModal.classList.add('active');
|
||||
}
|
||||
|
||||
function closeSub2ApiServiceModal() {
|
||||
elements.sub2ApiServiceEditModal.classList.remove('active');
|
||||
elements.sub2ApiServiceForm.reset();
|
||||
_sub2apiEditingId = null;
|
||||
}
|
||||
|
||||
async function editSub2ApiService(id) {
|
||||
try {
|
||||
const svc = await api.get(`/sub2api-services/${id}`);
|
||||
openSub2ApiServiceModal(svc);
|
||||
} catch (e) {
|
||||
toast.error('加载失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSub2ApiService(id, name) {
|
||||
if (!confirm(`确认删除 Sub2API 服务「${name}」?`)) return;
|
||||
try {
|
||||
await api.delete(`/sub2api-services/${id}`);
|
||||
toast.success('服务已删除');
|
||||
loadSub2ApiServices();
|
||||
} catch (e) {
|
||||
toast.error('删除失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveSub2ApiService(e) {
|
||||
e.preventDefault();
|
||||
const id = document.getElementById('sub2api-service-id').value;
|
||||
const data = {
|
||||
name: document.getElementById('sub2api-service-name').value,
|
||||
api_url: document.getElementById('sub2api-service-url').value,
|
||||
api_key: document.getElementById('sub2api-service-key').value || undefined,
|
||||
priority: parseInt(document.getElementById('sub2api-service-priority').value) || 0,
|
||||
enabled: document.getElementById('sub2api-service-enabled').checked,
|
||||
};
|
||||
if (!id && !data.api_key) {
|
||||
toast.error('请填写 API Key');
|
||||
return;
|
||||
}
|
||||
if (!data.api_key) delete data.api_key;
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
await api.patch(`/sub2api-services/${id}`, data);
|
||||
toast.success('服务已更新');
|
||||
} else {
|
||||
await api.post('/sub2api-services', data);
|
||||
toast.success('服务已添加');
|
||||
}
|
||||
closeSub2ApiServiceModal();
|
||||
loadSub2ApiServices();
|
||||
} catch (e) {
|
||||
toast.error('保存失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTestSub2ApiService() {
|
||||
const apiUrl = document.getElementById('sub2api-service-url').value.trim();
|
||||
const apiKey = document.getElementById('sub2api-service-key').value.trim();
|
||||
const id = document.getElementById('sub2api-service-id').value;
|
||||
|
||||
if (!apiUrl) {
|
||||
toast.error('请先填写 API URL');
|
||||
return;
|
||||
}
|
||||
if (!id && !apiKey) {
|
||||
toast.error('请先填写 API Key');
|
||||
return;
|
||||
}
|
||||
|
||||
elements.testSub2ApiServiceBtn.disabled = true;
|
||||
elements.testSub2ApiServiceBtn.textContent = '测试中...';
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (id && !apiKey) {
|
||||
result = await api.post(`/sub2api-services/${id}/test`);
|
||||
} else {
|
||||
result = await api.post('/sub2api-services/test-connection', { api_url: apiUrl, api_key: apiKey });
|
||||
}
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
} else {
|
||||
toast.error(result.message);
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error('测试失败: ' + e.message);
|
||||
} finally {
|
||||
elements.testSub2ApiServiceBtn.disabled = false;
|
||||
elements.testSub2ApiServiceBtn.textContent = '🔌 测试连接';
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const d = document.createElement('div');
|
||||
|
||||
@@ -131,6 +131,9 @@
|
||||
<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>
|
||||
@@ -233,6 +236,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub2API 服务选择模态框 -->
|
||||
<div class="modal" id="sub2api-service-modal">
|
||||
<div class="modal-content" style="max-width: 480px;">
|
||||
<div class="modal-header">
|
||||
<h3>🔗 选择 Sub2API 服务</h3>
|
||||
<button class="modal-close" id="close-sub2api-modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="color: var(--text-muted); margin-bottom: 12px; font-size: 0.9rem;">选择要上传到的 Sub2API 服务,或自动选择第一个启用的服务。</p>
|
||||
<div id="sub2api-service-list" style="display: flex; flex-direction: column; gap: 8px; max-height: 300px; overflow-y: auto;">
|
||||
<div style="text-align: center; color: var(--text-muted);">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="padding: 12px 20px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end;">
|
||||
<button class="btn btn-secondary" id="sub2api-use-auto-btn">自动选择</button>
|
||||
<button class="btn btn-secondary" id="cancel-sub2api-modal-btn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/utils.js"></script>
|
||||
<script src="/static/js/accounts.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" data-tab="proxy">🌐 代理设置</button>
|
||||
<button class="tab-btn" data-tab="webui">🔒 访问控制</button>
|
||||
<button class="tab-btn" data-tab="cpa">☁️ CPA上传</button>
|
||||
<button class="tab-btn" data-tab="team-manager">🚀 Team Manager</button>
|
||||
<button class="tab-btn" data-tab="upload">☁️ 上传</button>
|
||||
<button class="tab-btn" data-tab="outlook">📮 Outlook配置</button>
|
||||
<button class="tab-btn" data-tab="registration">⚙️ 注册配置</button>
|
||||
<button class="tab-btn" data-tab="email-code">📧 验证码配置</button>
|
||||
@@ -202,12 +201,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CPA 上传设置 -->
|
||||
<div class="tab-content" id="cpa-tab">
|
||||
<!-- 上传服务设置(CPA + Sub2API + Team Manager) -->
|
||||
<div class="tab-content" id="upload-tab">
|
||||
<!-- CPA 服务管理 -->
|
||||
<div class="card">
|
||||
<div class="card" style="margin-top: var(--spacing-lg);">
|
||||
<div class="card-header">
|
||||
<h3>CPA 服务管理</h3>
|
||||
<h3>☁️ CPA 服务</h3>
|
||||
<button class="btn btn-primary btn-sm" id="add-cpa-service-btn">+ 添加服务</button>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 0;">
|
||||
@@ -229,6 +228,110 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub2API 服务管理 -->
|
||||
<div class="card" style="margin-top: var(--spacing-lg);">
|
||||
<div class="card-header">
|
||||
<h3>🔗 Sub2API 服务</h3>
|
||||
<button class="btn btn-primary btn-sm" id="add-sub2api-service-btn">+ 添加服务</button>
|
||||
</div>
|
||||
<div class="card-body" style="padding: 0;">
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>API URL</th>
|
||||
<th style="width:80px;">状态</th>
|
||||
<th style="width:60px;">优先级</th>
|
||||
<th style="width:160px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sub2api-services-table">
|
||||
<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Manager 配置 -->
|
||||
<div class="card" style="margin-top: var(--spacing-lg);">
|
||||
<div class="card-header">
|
||||
<h3>🚀 Team Manager</h3>
|
||||
<span class="hint">配置 Team Manager 账号导入功能</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="tm-form">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="tm-enabled" name="enabled">
|
||||
启用 Team Manager 上传
|
||||
</label>
|
||||
<p class="hint">启用后可在账号管理页面将账号导入 Team Manager 平台</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tm-api-url">API URL</label>
|
||||
<input type="text" id="tm-api-url" name="api_url" placeholder="例如: https://tm.example.com">
|
||||
<p class="hint">Team Manager 平台的 API 地址</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tm-api-key">API Key</label>
|
||||
<input type="password" id="tm-api-key" name="api_key" placeholder="留空则保持原值" autocomplete="new-password">
|
||||
<p class="hint">Team Manager 平台的认证 Key</p>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存设置</button>
|
||||
<button type="button" class="btn btn-secondary" id="test-tm-btn">🔌 测试连接</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub2API 服务编辑模态框 -->
|
||||
<div class="modal" id="sub2api-service-edit-modal">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header">
|
||||
<h3 id="sub2api-service-modal-title">添加 Sub2API 服务</h3>
|
||||
<button class="modal-close" id="close-sub2api-service-modal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="sub2api-service-form">
|
||||
<input type="hidden" id="sub2api-service-id">
|
||||
<div class="form-group">
|
||||
<label for="sub2api-service-name">名称 *</label>
|
||||
<input type="text" id="sub2api-service-name" placeholder="例如: 主服务" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sub2api-service-url">API URL *</label>
|
||||
<input type="text" id="sub2api-service-url" placeholder="http://host" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sub2api-service-key">API Key</label>
|
||||
<input type="password" id="sub2api-service-key" placeholder="编辑时留空则保持原值" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="sub2api-service-priority">优先级</label>
|
||||
<input type="number" id="sub2api-service-priority" value="0" min="0">
|
||||
<p class="hint">数字越小优先级越高</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label> </label>
|
||||
<label style="margin-top:10px;display:flex;align-items:center;gap:8px;">
|
||||
<input type="checkbox" id="sub2api-service-enabled" checked> 启用
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存</button>
|
||||
<button type="button" class="btn btn-secondary" id="test-sub2api-service-btn">🔌 测试连接</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancel-sub2api-service-btn">取消</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CPA 服务编辑模态框 -->
|
||||
@@ -276,44 +379,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Manager 设置 -->
|
||||
<div class="tab-content" id="team-manager-tab">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3>Team Manager 配置</h3>
|
||||
<span class="hint">配置 Team Manager 账号导入功能</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="tm-form">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="tm-enabled" name="enabled">
|
||||
启用 Team Manager 上传
|
||||
</label>
|
||||
<p class="hint">启用后可在账号管理页面将账号导入 Team Manager 平台</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tm-api-url">API URL</label>
|
||||
<input type="text" id="tm-api-url" name="api_url" placeholder="例如: https://tm.example.com">
|
||||
<p class="hint">Team Manager 平台的 API 地址</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tm-api-key">API Key</label>
|
||||
<input type="password" id="tm-api-key" name="api_key" placeholder="留空则保持原值" autocomplete="new-password">
|
||||
<p class="hint">Team Manager 平台的认证 Key</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 保存设置</button>
|
||||
<button type="button" class="btn btn-secondary" id="test-tm-btn">🔌 测试连接</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outlook 配置 -->
|
||||
<div class="tab-content" id="outlook-tab">
|
||||
<div class="card">
|
||||
|
||||
Reference in New Issue
Block a user