Files
codex-manager/static/js/accounts.js
T
2026-03-14 16:51:57 +08:00

382 lines
12 KiB
JavaScript

/**
* 账号管理页面 JavaScript
*/
// API 基础路径
const API_BASE = '/api';
// 状态
let currentPage = 1;
let pageSize = 20;
let totalAccounts = 0;
let selectedAccounts = new Set();
// DOM 元素
const accountsTable = document.getElementById('accounts-table');
const totalAccountsEl = document.getElementById('total-accounts');
const activeAccountsEl = document.getElementById('active-accounts');
const expiredAccountsEl = document.getElementById('expired-accounts');
const failedAccountsEl = document.getElementById('failed-accounts');
const filterStatus = document.getElementById('filter-status');
const filterService = document.getElementById('filter-service');
const searchInput = document.getElementById('search-input');
const refreshBtn = document.getElementById('refresh-btn');
const batchDeleteBtn = document.getElementById('batch-delete-btn');
const exportBtn = document.getElementById('export-btn');
const exportMenu = document.getElementById('export-menu');
const selectAllCheckbox = document.getElementById('select-all');
const prevPageBtn = document.getElementById('prev-page');
const nextPageBtn = document.getElementById('next-page');
const pageInfo = document.getElementById('page-info');
const detailModal = document.getElementById('detail-modal');
const modalBody = document.getElementById('modal-body');
const closeModalBtn = document.getElementById('close-modal');
// 初始化
document.addEventListener('DOMContentLoaded', () => {
loadStats();
loadAccounts();
initEventListeners();
});
// 事件监听
function initEventListeners() {
// 筛选
filterStatus.addEventListener('change', () => {
currentPage = 1;
loadAccounts();
});
filterService.addEventListener('change', () => {
currentPage = 1;
loadAccounts();
});
// 搜索
let searchTimeout;
searchInput.addEventListener('input', () => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
currentPage = 1;
loadAccounts();
}, 300);
});
// 刷新
refreshBtn.addEventListener('click', () => {
loadStats();
loadAccounts();
});
// 批量删除
batchDeleteBtn.addEventListener('click', handleBatchDelete);
// 全选
selectAllCheckbox.addEventListener('change', (e) => {
const checkboxes = accountsTable.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(cb => {
cb.checked = e.target.checked;
const id = parseInt(cb.dataset.id);
if (e.target.checked) {
selectedAccounts.add(id);
} else {
selectedAccounts.delete(id);
}
});
updateBatchButtons();
});
// 分页
prevPageBtn.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
loadAccounts();
}
});
nextPageBtn.addEventListener('click', () => {
const totalPages = Math.ceil(totalAccounts / pageSize);
if (currentPage < totalPages) {
currentPage++;
loadAccounts();
}
});
// 导出
exportBtn.addEventListener('click', (e) => {
e.stopPropagation();
exportMenu.classList.toggle('active');
});
document.querySelectorAll('#export-menu .dropdown-item').forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const format = e.target.dataset.format;
exportAccounts(format);
exportMenu.classList.remove('active');
});
});
// 关闭模态框
closeModalBtn.addEventListener('click', () => {
detailModal.classList.remove('active');
});
detailModal.addEventListener('click', (e) => {
if (e.target === detailModal) {
detailModal.classList.remove('active');
}
});
// 点击其他地方关闭下拉菜单
document.addEventListener('click', () => {
exportMenu.classList.remove('active');
});
}
// 加载统计信息
async function loadStats() {
try {
const response = await fetch(`${API_BASE}/accounts/stats/summary`);
const data = await response.json();
totalAccountsEl.textContent = data.total || 0;
activeAccountsEl.textContent = data.by_status?.active || 0;
expiredAccountsEl.textContent = data.by_status?.expired || 0;
failedAccountsEl.textContent = data.by_status?.failed || 0;
} catch (error) {
console.error('加载统计信息失败:', error);
}
}
// 加载账号列表
async function loadAccounts() {
const params = new URLSearchParams({
page: currentPage,
page_size: pageSize,
});
if (filterStatus.value) {
params.append('status', filterStatus.value);
}
if (filterService.value) {
params.append('email_service', filterService.value);
}
if (searchInput.value.trim()) {
params.append('search', searchInput.value.trim());
}
try {
const response = await fetch(`${API_BASE}/accounts?${params}`);
const data = await response.json();
totalAccounts = data.total;
renderAccounts(data.accounts);
updatePagination();
} catch (error) {
console.error('加载账号列表失败:', error);
accountsTable.innerHTML = '<tr><td colspan="7" style="text-align: center;">加载失败</td></tr>';
}
}
// 渲染账号列表
function renderAccounts(accounts) {
if (accounts.length === 0) {
accountsTable.innerHTML = '<tr><td colspan="7" style="text-align: center;">暂无数据</td></tr>';
return;
}
accountsTable.innerHTML = accounts.map(account => `
<tr>
<td><input type="checkbox" data-id="${account.id}" ${selectedAccounts.has(account.id) ? 'checked' : ''}></td>
<td>${account.id}</td>
<td>${escapeHtml(account.email)}</td>
<td>${escapeHtml(account.email_service)}</td>
<td><span class="status-badge ${account.status}">${getStatusText(account.status)}</span></td>
<td>${formatDate(account.registered_at)}</td>
<td>
<button class="btn btn-sm btn-secondary" onclick="viewAccount(${account.id})">查看</button>
<button class="btn btn-sm btn-danger" onclick="deleteAccount(${account.id}, '${escapeHtml(account.email)}')">删除</button>
</td>
</tr>
`).join('');
// 绑定复选框事件
accountsTable.querySelectorAll('input[type="checkbox"]').forEach(cb => {
cb.addEventListener('change', (e) => {
const id = parseInt(e.target.dataset.id);
if (e.target.checked) {
selectedAccounts.add(id);
} else {
selectedAccounts.delete(id);
}
updateBatchButtons();
});
});
}
// 更新分页
function updatePagination() {
const totalPages = Math.ceil(totalAccounts / pageSize);
prevPageBtn.disabled = currentPage <= 1;
nextPageBtn.disabled = currentPage >= totalPages;
pageInfo.textContent = `第 ${currentPage} 页 / 共 ${totalPages} 页`;
}
// 更新批量操作按钮
function updateBatchButtons() {
batchDeleteBtn.disabled = selectedAccounts.size === 0;
}
// 查看账号详情
async function viewAccount(id) {
try {
const response = await fetch(`${API_BASE}/accounts/${id}`);
const account = await response.json();
const tokensResponse = await fetch(`${API_BASE}/accounts/${id}/tokens`);
const tokens = await tokensResponse.json();
modalBody.innerHTML = `
<div class="info-grid">
<div class="info-item">
<span class="label">邮箱</span>
<span class="value">${escapeHtml(account.email)}</span>
</div>
<div class="info-item">
<span class="label">邮箱服务</span>
<span class="value">${escapeHtml(account.email_service)}</span>
</div>
<div class="info-item">
<span class="label">状态</span>
<span class="value">${getStatusText(account.status)}</span>
</div>
<div class="info-item">
<span class="label">注册时间</span>
<span class="value">${formatDate(account.registered_at)}</span>
</div>
<div class="info-item">
<span class="label">Account ID</span>
<span class="value">${escapeHtml(account.account_id || '-')}</span>
</div>
<div class="info-item">
<span class="label">Workspace ID</span>
<span class="value">${escapeHtml(account.workspace_id || '-')}</span>
</div>
<div class="info-item">
<span class="label">Access Token</span>
<span class="value" style="font-size: 0.75rem; word-break: break-all;">${escapeHtml(tokens.access_token || '-')}</span>
</div>
<div class="info-item">
<span class="label">Refresh Token</span>
<span class="value" style="font-size: 0.75rem; word-break: break-all;">${escapeHtml(tokens.refresh_token || '-')}</span>
</div>
</div>
`;
detailModal.classList.add('active');
} catch (error) {
alert('加载账号详情失败: ' + error.message);
}
}
// 删除账号
async function deleteAccount(id, email) {
if (!confirm(`确定要删除账号 ${email} 吗?`)) {
return;
}
try {
const response = await fetch(`${API_BASE}/accounts/${id}`, {
method: 'DELETE',
});
if (response.ok) {
loadStats();
loadAccounts();
} else {
const data = await response.json();
alert('删除失败: ' + (data.detail || '未知错误'));
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 批量删除
async function handleBatchDelete() {
if (selectedAccounts.size === 0) return;
if (!confirm(`确定要删除选中的 ${selectedAccounts.size} 个账号吗?`)) {
return;
}
try {
const response = await fetch(`${API_BASE}/accounts/batch-delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ids: Array.from(selectedAccounts),
}),
});
const data = await response.json();
if (response.ok) {
alert(`成功删除 ${data.deleted_count} 个账号`);
selectedAccounts.clear();
loadStats();
loadAccounts();
} else {
alert('删除失败: ' + (data.detail || '未知错误'));
}
} catch (error) {
alert('删除失败: ' + error.message);
}
}
// 导出账号
function exportAccounts(format) {
const params = new URLSearchParams();
if (filterStatus.value) {
params.append('status', filterStatus.value);
}
if (filterService.value) {
params.append('email_service', filterService.value);
}
window.location.href = `${API_BASE}/accounts/export/${format}?${params}`;
}
// 工具函数
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getStatusText(status) {
const statusMap = {
'active': '活跃',
'expired': '过期',
'banned': '封禁',
'failed': '失败',
};
return statusMap[status] || status;
}
function formatDate(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN');
}