fix: restore protocol baseline, resolve 403/400 registration errors, and fully remove deprecated playwright dependency

This commit is contained in:
Mison
2026-03-23 18:49:51 +08:00
parent a7a6391f0d
commit de2c4aa7ab
60 changed files with 4126 additions and 2834 deletions

5
static/favicon.svg Normal file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#111827"/>
<path d="M18 20h28v6H18zm0 10h20v6H18zm0 10h28v6H18z" fill="#f9fafb"/>
<circle cx="46" cy="33" r="6" fill="#22c55e"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

View File

@@ -1055,6 +1055,7 @@ function resetButtons() {
elements.cancelBtn.disabled = true;
currentTask = null;
currentBatch = null;
isBatchMode = false;
// 重置完成标志
taskCompleted = false;
batchCompleted = false;
@@ -1280,13 +1281,12 @@ function connectBatchWebSocket(batchId) {
if (!toastShown) {
toastShown = true;
if (data.status === 'completed') {
const batchLabel = isOutlookBatchMode ? 'Outlook 批量' : '批量';
addLog('success', `[完成] ${batchLabel}任务完成!成功: ${data.success}, 失败: ${data.failed}, 跳过: ${data.skipped || 0}`);
addLog('success', `[完成] Outlook 批量任务完成!成功: ${data.success}, 失败: ${data.failed}, 跳过: ${data.skipped || 0}`);
if (data.success > 0) {
toast.success(`${batchLabel}注册完成,成功 ${data.success}`);
toast.success(`Outlook 批量注册完成,成功 ${data.success}`);
loadRecentAccounts();
} else {
toast.warning(`${batchLabel}注册完成,但没有成功注册任何账号`);
toast.warning('Outlook 批量注册完成,但没有成功注册任何账号');
}
} else if (data.status === 'failed') {
addLog('error', '[错误] 批量任务执行失败');

View File

@@ -72,25 +72,6 @@ const elements = {
editOutlookForm: document.getElementById('edit-outlook-form'),
closeEditOutlookModal: document.getElementById('close-edit-outlook-modal'),
cancelEditOutlook: document.getElementById('cancel-edit-outlook'),
// 收件箱模态框
inboxModal: document.getElementById('inbox-modal'),
closeInboxModal: document.getElementById('close-inbox-modal'),
inboxRefreshBtn: document.getElementById('inbox-refresh-btn'),
inboxOnlyUnseen: document.getElementById('inbox-only-unseen'),
inboxLoading: document.getElementById('inbox-loading'),
inboxTable: document.getElementById('inbox-table'),
inboxTbody: document.getElementById('inbox-tbody'),
inboxEmpty: document.getElementById('inbox-empty'),
inboxModalEmail: document.getElementById('inbox-modal-email'),
// 邮件正文模态框
emailDetailModal: document.getElementById('email-detail-modal'),
closeEmailDetailModal: document.getElementById('close-email-detail-modal'),
emailDetailSubject: document.getElementById('email-detail-subject'),
emailDetailSender: document.getElementById('email-detail-sender'),
emailDetailDate: document.getElementById('email-detail-date'),
emailDetailBody: document.getElementById('email-detail-body'),
};
const CUSTOM_SUBTYPE_LABELS = {
@@ -183,12 +164,6 @@ function initEventListeners() {
document.addEventListener('click', () => {
document.querySelectorAll('.dropdown-menu.active').forEach(m => m.classList.remove('active'));
});
// 收件箱模态框事件
elements.closeInboxModal.addEventListener('click', () => elements.inboxModal.classList.remove('active'));
elements.closeEmailDetailModal.addEventListener('click', () => elements.emailDetailModal.classList.remove('active'));
elements.inboxRefreshBtn.addEventListener('click', () => loadInbox(currentInboxServiceId, true));
elements.inboxOnlyUnseen.addEventListener('change', () => loadInbox(currentInboxServiceId));
}
function toggleEmailMoreMenu(btn) {
@@ -272,7 +247,6 @@ async function loadOutlookServices() {
<td>${format.date(service.last_used)}</td>
<td>
<div style="display:flex;gap:4px;align-items:center;white-space:nowrap;">
<button class="btn btn-secondary btn-sm" onclick="openInboxModal(${service.id}, '${escapeHtml(service.config?.email || service.name)}')">收件箱</button>
<button class="btn btn-secondary btn-sm" onclick="editOutlookService(${service.id})">编辑</button>
<div class="dropdown" style="position:relative;">
<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation();toggleEmailMoreMenu(this)">更多</button>
@@ -813,57 +787,3 @@ async function handleEditOutlook(e) {
toast.error('更新失败: ' + error.message);
}
}
// ============== 收件箱 ==============
let currentInboxServiceId = null;
async function openInboxModal(serviceId, email) {
currentInboxServiceId = serviceId;
elements.inboxModalEmail.textContent = email;
elements.inboxOnlyUnseen.checked = false;
elements.inboxModal.classList.add('active');
await loadInbox(serviceId);
}
async function loadInbox(serviceId) {
if (!serviceId) return;
const onlyUnseen = elements.inboxOnlyUnseen.checked;
elements.inboxLoading.style.display = 'block';
elements.inboxTable.style.display = 'none';
elements.inboxEmpty.style.display = 'none';
elements.inboxEmpty.textContent = '暂无邮件';
try {
const params = new URLSearchParams({ count: 50, only_unseen: onlyUnseen });
const data = await api.get(`/email-services/${serviceId}/inbox?${params}`);
const emails = data.emails || [];
elements.inboxLoading.style.display = 'none';
if (emails.length === 0) {
elements.inboxEmpty.style.display = 'block';
return;
}
elements.inboxTbody.innerHTML = emails.map(m => {
const dataAttr = escapeHtml(JSON.stringify(m));
return `<tr style="cursor:pointer;" onclick="showEmailDetail(JSON.parse(this.dataset.mail))" data-mail="${dataAttr}">
<td style="text-align:center;">${m.is_read ? '' : '<span style="color:var(--primary);font-size:10px;">●</span>'}</td>
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(m.subject)}">${escapeHtml(m.subject) || '(无主题)'}</td>
<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(m.sender)}">${escapeHtml(m.sender)}</td>
<td>${format.date(m.received_at)}</td>
</tr>`;
}).join('');
elements.inboxTable.style.display = 'table';
} catch (e) {
elements.inboxLoading.style.display = 'none';
elements.inboxEmpty.style.display = 'block';
elements.inboxEmpty.textContent = '加载失败:' + (e.message || '未知错误');
console.error('加载收件箱失败:', e);
}
}
function showEmailDetail(mail) {
elements.emailDetailSubject.textContent = mail.subject || '(无主题)';
elements.emailDetailSender.textContent = mail.sender || '';
elements.emailDetailDate.textContent = format.date(mail.received_at);
elements.emailDetailBody.textContent = mail.body || mail.body_preview || '(无正文)';
elements.emailDetailModal.classList.add('active');
}

View File

@@ -31,7 +31,6 @@ const elements = {
proxiesTable: document.getElementById('proxies-table'),
addProxyBtn: document.getElementById('add-proxy-btn'),
testAllProxiesBtn: document.getElementById('test-all-proxies-btn'),
deleteDisabledProxiesBtn: document.getElementById('delete-disabled-proxies-btn'),
addProxyModal: document.getElementById('add-proxy-modal'),
proxyItemForm: document.getElementById('proxy-item-form'),
closeProxyModal: document.getElementById('close-proxy-modal'),
@@ -207,10 +206,6 @@ function initEventListeners() {
elements.testAllProxiesBtn.addEventListener('click', handleTestAllProxies);
}
if (elements.deleteDisabledProxiesBtn) {
elements.deleteDisabledProxiesBtn.addEventListener('click', handleDeleteDisabledProxies);
}
if (elements.closeProxyModal) {
elements.closeProxyModal.addEventListener('click', closeProxyModal);
}
@@ -678,16 +673,16 @@ async function handleOutlookBatchImport() {
lines.forEach((line, index) => {
const parts = line.split('----').map(p => p.trim());
if (parts.length < 4) {
errors.push(`${index + 1} 行格式错误,必须为 邮箱----密码----client_id----refresh_token`);
if (parts.length < 2) {
errors.push(`${index + 1} 行格式错误`);
return;
}
const account = {
email: parts[0],
password: parts[1],
client_id: parts[2],
refresh_token: parts[3],
client_id: parts[2] || null,
refresh_token: parts[3] || null,
enabled: enabled,
priority: priority
};
@@ -697,11 +692,6 @@ async function handleOutlookBatchImport() {
return;
}
if (!account.client_id || !account.refresh_token) {
errors.push(`${index + 1} 行 client_id 或 refresh_token 不能为空`);
return;
}
accounts.push(account);
});
@@ -777,13 +767,11 @@ async function loadProxies() {
try {
const data = await api.get('/settings/proxies');
renderProxies(data.proxies);
updateProxyBulkActions(data.proxies || []);
} catch (error) {
console.error('加载代理列表失败:', error);
updateProxyBulkActions([]);
elements.proxiesTable.innerHTML = `
<tr>
<td colspan="8">
<td colspan="7">
<div class="empty-state">
<div class="empty-state-icon">❌</div>
<div class="empty-state-title">加载失败</div>
@@ -799,7 +787,7 @@ function renderProxies(proxies) {
if (!proxies || proxies.length === 0) {
elements.proxiesTable.innerHTML = `
<tr>
<td colspan="8">
<td colspan="7">
<div class="empty-state">
<div class="empty-state-icon">🌐</div>
<div class="empty-state-title">暂无代理</div>
@@ -843,17 +831,6 @@ function renderProxies(proxies) {
`).join('');
}
function updateProxyBulkActions(proxies) {
if (!elements.deleteDisabledProxiesBtn) return;
const disabledCount = (proxies || []).filter(proxy => !proxy.enabled).length;
elements.deleteDisabledProxiesBtn.disabled = disabledCount === 0;
elements.deleteDisabledProxiesBtn.dataset.count = String(disabledCount);
elements.deleteDisabledProxiesBtn.textContent = disabledCount > 0
? `🧹 删除禁用项 (${disabledCount})`
: '🧹 删除禁用项';
}
function toggleSettingsMoreMenu(btn) {
const menu = btn.nextElementSibling;
const isActive = menu.classList.contains('active');
@@ -949,12 +926,7 @@ async function testProxyItem(id) {
if (result.success) {
toast.success(result.message);
} else {
if (result.auto_disabled) {
toast.warning(result.message);
await loadProxies();
} else {
toast.error(result.message);
}
toast.error(result.message);
}
} catch (error) {
toast.error('测试失败: ' + error.message);
@@ -987,22 +959,6 @@ async function deleteProxyItem(id) {
}
}
async function handleDeleteDisabledProxies() {
const count = Number(elements.deleteDisabledProxiesBtn?.dataset.count || 0);
if (!count) return;
const confirmed = await confirm(`确定要删除全部 ${count} 个已禁用代理吗?此操作不可恢复。`);
if (!confirmed) return;
try {
const result = await api.delete('/settings/proxies/disabled/batch-delete');
toast.success(result.message);
await loadProxies();
} catch (error) {
toast.error('批量删除失败: ' + error.message);
}
}
// 测试所有代理
async function handleTestAllProxies() {
elements.testAllProxiesBtn.disabled = true;
@@ -1010,13 +966,8 @@ async function handleTestAllProxies() {
try {
const result = await api.post('/settings/proxies/test-all');
const summary = `测试完成: 成功 ${result.success}, 失败 ${result.failed}`;
if (result.auto_disabled > 0) {
toast.warning(`${summary},已自动禁用 ${result.auto_disabled}`);
} else {
toast.info(summary);
}
await loadProxies();
toast.info(`测试完成: 成功 ${result.success}, 失败 ${result.failed}`);
loadProxies();
} catch (error) {
toast.error('测试失败: ' + error.message);
} finally {