Files
codex-register/static/js/settings.js
cnlimiter 0688f4ca7e 3
2026-03-14 17:36:55 +08:00

546 lines
18 KiB
JavaScript

/**
* 设置页面 JavaScript
* 使用 utils.js 中的工具库
*/
// DOM 元素
const elements = {
tabs: document.querySelectorAll('.tab-btn'),
tabContents: document.querySelectorAll('.tab-content'),
proxyForm: document.getElementById('proxy-form'),
registrationForm: document.getElementById('registration-settings-form'),
testProxyBtn: document.getElementById('test-proxy-btn'),
backupBtn: document.getElementById('backup-btn'),
cleanupBtn: document.getElementById('cleanup-btn'),
addEmailServiceBtn: document.getElementById('add-email-service-btn'),
addServiceModal: document.getElementById('add-service-modal'),
addServiceForm: document.getElementById('add-service-form'),
closeServiceModal: document.getElementById('close-service-modal'),
cancelAddService: document.getElementById('cancel-add-service'),
serviceType: document.getElementById('service-type'),
serviceConfigFields: document.getElementById('service-config-fields'),
emailServicesTable: document.getElementById('email-services-table'),
// Outlook 导入
toggleImportBtn: document.getElementById('toggle-import-btn'),
outlookImportBody: document.getElementById('outlook-import-body'),
outlookImportBtn: document.getElementById('outlook-import-btn'),
clearImportBtn: document.getElementById('clear-import-btn'),
outlookImportData: document.getElementById('outlook-import-data'),
importResult: document.getElementById('import-result'),
// 批量操作
selectAllServices: document.getElementById('select-all-services')
};
// 选中的服务 ID
let selectedServiceIds = new Set();
// 初始化
document.addEventListener('DOMContentLoaded', () => {
initTabs();
loadSettings();
loadEmailServices();
loadDatabaseInfo();
initEventListeners();
});
// 初始化标签页
function initTabs() {
elements.tabs.forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
elements.tabs.forEach(b => b.classList.remove('active'));
elements.tabContents.forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById(`${tab}-tab`).classList.add('active');
});
});
}
// 事件监听
function initEventListeners() {
// 代理表单
elements.proxyForm.addEventListener('submit', handleSaveProxy);
// 测试代理
elements.testProxyBtn.addEventListener('click', handleTestProxy);
// 注册配置表单
elements.registrationForm.addEventListener('submit', handleSaveRegistration);
// 备份数据库
elements.backupBtn.addEventListener('click', handleBackup);
// 清理数据
elements.cleanupBtn.addEventListener('click', handleCleanup);
// 添加邮箱服务
elements.addEmailServiceBtn.addEventListener('click', () => {
elements.addServiceModal.classList.add('active');
loadServiceConfigFields(elements.serviceType.value);
});
elements.closeServiceModal.addEventListener('click', () => {
elements.addServiceModal.classList.remove('active');
});
elements.cancelAddService.addEventListener('click', () => {
elements.addServiceModal.classList.remove('active');
});
elements.addServiceModal.addEventListener('click', (e) => {
if (e.target === elements.addServiceModal) {
elements.addServiceModal.classList.remove('active');
}
});
// 服务类型切换
elements.serviceType.addEventListener('change', (e) => {
loadServiceConfigFields(e.target.value);
});
// 添加服务表单
elements.addServiceForm.addEventListener('submit', handleAddService);
// Outlook 批量导入展开/折叠
if (elements.toggleImportBtn) {
elements.toggleImportBtn.addEventListener('click', () => {
const isHidden = elements.outlookImportBody.style.display === 'none';
elements.outlookImportBody.style.display = isHidden ? 'block' : 'none';
elements.toggleImportBtn.textContent = isHidden ? '收起' : '展开';
});
}
// Outlook 批量导入
if (elements.outlookImportBtn) {
elements.outlookImportBtn.addEventListener('click', handleOutlookBatchImport);
}
// 清空导入数据
if (elements.clearImportBtn) {
elements.clearImportBtn.addEventListener('click', () => {
elements.outlookImportData.value = '';
elements.importResult.style.display = 'none';
});
}
// 全选/取消全选
if (elements.selectAllServices) {
elements.selectAllServices.addEventListener('change', (e) => {
const checkboxes = document.querySelectorAll('.service-checkbox');
checkboxes.forEach(cb => cb.checked = e.target.checked);
updateSelectedServices();
});
}
}
// 加载设置
async function loadSettings() {
try {
const data = await api.get('/settings');
// 代理设置
document.getElementById('proxy-enabled').checked = data.proxy?.enabled || false;
document.getElementById('proxy-type').value = data.proxy?.type || 'http';
document.getElementById('proxy-host').value = data.proxy?.host || '127.0.0.1';
document.getElementById('proxy-port').value = data.proxy?.port || 7890;
document.getElementById('proxy-username').value = data.proxy?.username || '';
// 注册配置
document.getElementById('max-retries').value = data.registration?.max_retries || 3;
document.getElementById('timeout').value = data.registration?.timeout || 120;
document.getElementById('password-length').value = data.registration?.default_password_length || 12;
document.getElementById('sleep-min').value = data.registration?.sleep_min || 5;
document.getElementById('sleep-max').value = data.registration?.sleep_max || 30;
} catch (error) {
console.error('加载设置失败:', error);
toast.error('加载设置失败');
}
}
// 加载邮箱服务
async function loadEmailServices() {
try {
const data = await api.get('/email-services');
renderEmailServices(data.services);
} catch (error) {
console.error('加载邮箱服务失败:', error);
elements.emailServicesTable.innerHTML = `
<tr>
<td colspan="7">
<div class="empty-state">
<div class="empty-state-icon">❌</div>
<div class="empty-state-title">加载失败</div>
</div>
</td>
</tr>
`;
}
}
// 渲染邮箱服务
function renderEmailServices(services) {
if (services.length === 0) {
elements.emailServicesTable.innerHTML = `
<tr>
<td colspan="7">
<div class="empty-state">
<div class="empty-state-icon">📭</div>
<div class="empty-state-title">暂无配置</div>
<div class="empty-state-description">点击上方"添加服务"按钮添加邮箱服务</div>
</div>
</td>
</tr>
`;
return;
}
elements.emailServicesTable.innerHTML = services.map(service => `
<tr data-service-id="${service.id}">
<td>
<input type="checkbox" class="service-checkbox" data-id="${service.id}"
onchange="updateSelectedServices()">
</td>
<td>${escapeHtml(service.name)}</td>
<td>${getServiceTypeText(service.service_type)}</td>
<td>
<span class="status-badge ${service.enabled ? 'active' : 'disabled'}">
${service.enabled ? '已启用' : '已禁用'}
</span>
</td>
<td>${service.priority}</td>
<td>${format.date(service.last_used)}</td>
<td>
<div class="action-buttons">
<button class="btn btn-ghost btn-sm" onclick="testService(${service.id})" title="测试">
🔌
</button>
<button class="btn btn-ghost btn-sm" onclick="toggleService(${service.id}, ${!service.enabled})" title="${service.enabled ? '禁用' : '启用'}">
${service.enabled ? '🔒' : '🔓'}
</button>
<button class="btn btn-ghost btn-sm" onclick="deleteService(${service.id})" title="删除">
🗑️
</button>
</div>
</td>
</tr>
`).join('');
}
// 加载数据库信息
async function loadDatabaseInfo() {
try {
const data = await api.get('/settings/database');
document.getElementById('db-size').textContent = `${data.database_size_mb} MB`;
document.getElementById('db-accounts').textContent = format.number(data.accounts_count);
document.getElementById('db-services').textContent = format.number(data.email_services_count);
document.getElementById('db-tasks').textContent = format.number(data.tasks_count);
} catch (error) {
console.error('加载数据库信息失败:', error);
}
}
// 保存代理设置
async function handleSaveProxy(e) {
e.preventDefault();
const data = {
enabled: document.getElementById('proxy-enabled').checked,
type: document.getElementById('proxy-type').value,
host: document.getElementById('proxy-host').value,
port: parseInt(document.getElementById('proxy-port').value),
username: document.getElementById('proxy-username').value || null,
password: document.getElementById('proxy-password').value || null,
};
try {
await api.post('/settings/proxy', data);
toast.success('代理设置已保存');
} catch (error) {
toast.error('保存失败: ' + error.message);
}
}
// 测试代理
async function handleTestProxy() {
elements.testProxyBtn.disabled = true;
elements.testProxyBtn.innerHTML = '<span class="loading-spinner"></span> 测试中...';
try {
// TODO: 实现代理测试 API
await new Promise(resolve => setTimeout(resolve, 1500));
toast.info('代理测试功能待实现');
} finally {
elements.testProxyBtn.disabled = false;
elements.testProxyBtn.textContent = '🔌 测试连接';
}
}
// 保存注册配置
async function handleSaveRegistration(e) {
e.preventDefault();
const data = {
max_retries: parseInt(document.getElementById('max-retries').value),
timeout: parseInt(document.getElementById('timeout').value),
default_password_length: parseInt(document.getElementById('password-length').value),
sleep_min: parseInt(document.getElementById('sleep-min').value),
sleep_max: parseInt(document.getElementById('sleep-max').value),
};
try {
await api.post('/settings/registration', data);
toast.success('注册配置已保存');
} catch (error) {
toast.error('保存失败: ' + error.message);
}
}
// 备份数据库
async function handleBackup() {
elements.backupBtn.disabled = true;
elements.backupBtn.innerHTML = '<span class="loading-spinner"></span> 备份中...';
try {
const data = await api.post('/settings/database/backup');
toast.success(`备份成功: ${data.backup_path}`);
} catch (error) {
toast.error('备份失败: ' + error.message);
} finally {
elements.backupBtn.disabled = false;
elements.backupBtn.textContent = '💾 备份数据库';
}
}
// 清理数据
async function handleCleanup() {
const confirmed = await confirm('确定要清理过期数据吗?此操作不可恢复。');
if (!confirmed) return;
elements.cleanupBtn.disabled = true;
elements.cleanupBtn.innerHTML = '<span class="loading-spinner"></span> 清理中...';
try {
const data = await api.post('/settings/database/cleanup?days=30');
toast.success(data.message);
loadDatabaseInfo();
} catch (error) {
toast.error('清理失败: ' + error.message);
} finally {
elements.cleanupBtn.disabled = false;
elements.cleanupBtn.textContent = '🧹 清理过期数据';
}
}
// 加载服务配置字段
async function loadServiceConfigFields(serviceType) {
try {
const data = await api.get('/email-services/types');
const typeInfo = data.types.find(t => t.value === serviceType);
if (!typeInfo) {
elements.serviceConfigFields.innerHTML = '';
return;
}
elements.serviceConfigFields.innerHTML = typeInfo.config_fields.map(field => `
<div class="form-group">
<label for="config-${field.name}">${field.label}</label>
<input type="${field.name.includes('password') || field.name.includes('token') ? 'password' : 'text'}"
id="config-${field.name}"
name="${field.name}"
value="${field.default || ''}"
placeholder="${field.label}"
${field.required ? 'required' : ''}>
</div>
`).join('');
} catch (error) {
console.error('加载配置字段失败:', error);
}
}
// 添加邮箱服务
async function handleAddService(e) {
e.preventDefault();
const formData = new FormData(elements.addServiceForm);
const config = {};
elements.serviceConfigFields.querySelectorAll('input').forEach(input => {
config[input.name] = input.value;
});
const data = {
service_type: formData.get('service_type'),
name: formData.get('name'),
config: config,
enabled: true,
priority: 0,
};
try {
await api.post('/email-services', data);
toast.success('邮箱服务已添加');
elements.addServiceModal.classList.remove('active');
elements.addServiceForm.reset();
loadEmailServices();
} catch (error) {
toast.error('添加失败: ' + error.message);
}
}
// 测试服务
async function testService(id) {
try {
const data = await api.post(`/email-services/${id}/test`);
if (data.success) {
toast.success('服务连接正常');
} else {
toast.warning('服务连接失败: ' + data.message);
}
} catch (error) {
toast.error('测试失败: ' + error.message);
}
}
// 切换服务状态
async function toggleService(id, enabled) {
try {
const endpoint = enabled ? 'enable' : 'disable';
await api.post(`/email-services/${id}/${endpoint}`);
toast.success(enabled ? '服务已启用' : '服务已禁用');
loadEmailServices();
} catch (error) {
toast.error('操作失败: ' + error.message);
}
}
// 删除服务
async function deleteService(id) {
const confirmed = await confirm('确定要删除此邮箱服务配置吗?');
if (!confirmed) return;
try {
await api.delete(`/email-services/${id}`);
toast.success('服务已删除');
loadEmailServices();
} catch (error) {
toast.error('删除失败: ' + error.message);
}
}
// 更新选中的服务
function updateSelectedServices() {
selectedServiceIds.clear();
document.querySelectorAll('.service-checkbox:checked').forEach(cb => {
selectedServiceIds.add(parseInt(cb.dataset.id));
});
}
// Outlook 批量导入
async function handleOutlookBatchImport() {
const data = elements.outlookImportData.value.trim();
if (!data) {
toast.warning('请输入要导入的数据');
return;
}
const enabled = document.getElementById('outlook-import-enabled').checked;
const priority = parseInt(document.getElementById('outlook-import-priority').value) || 0;
// 解析数据
const lines = data.split('\n').filter(line => line.trim() && !line.trim().startsWith('#'));
const accounts = [];
const errors = [];
lines.forEach((line, index) => {
const parts = line.split('----').map(p => p.trim());
if (parts.length < 2) {
errors.push(`${index + 1} 行格式错误`);
return;
}
const account = {
email: parts[0],
password: parts[1],
client_id: parts[2] || null,
refresh_token: parts[3] || null,
enabled: enabled,
priority: priority
};
if (!account.email.includes('@')) {
errors.push(`${index + 1} 行邮箱格式错误: ${account.email}`);
return;
}
accounts.push(account);
});
if (errors.length > 0) {
elements.importResult.style.display = 'block';
elements.importResult.innerHTML = `
<div class="import-errors">${errors.map(e => `<div>${e}</div>`).join('')}</div>
`;
return;
}
elements.outlookImportBtn.disabled = true;
elements.outlookImportBtn.innerHTML = '<span class="loading-spinner"></span> 导入中...';
let successCount = 0;
let failCount = 0;
try {
for (const account of accounts) {
try {
await api.post('/email-services', {
service_type: 'outlook',
name: account.email,
config: {
email: account.email,
password: account.password,
client_id: account.client_id,
refresh_token: account.refresh_token
},
enabled: account.enabled,
priority: account.priority
});
successCount++;
} catch {
failCount++;
}
}
elements.importResult.style.display = 'block';
elements.importResult.innerHTML = `
<div class="import-stats">
<span>✅ 成功: ${successCount}</span>
<span>❌ 失败: ${failCount}</span>
</div>
`;
toast.success(`导入完成,成功 ${successCount}`);
loadEmailServices();
} catch (error) {
toast.error('导入失败: ' + error.message);
} finally {
elements.outlookImportBtn.disabled = false;
elements.outlookImportBtn.textContent = '📥 开始导入';
}
}
// HTML 转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}