mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-07-10 23:11:21 +08:00
feat(mail): 多邮箱服务支持:将「自定义域名(REST API)」展开为两种子类型说明——MoeMail 和 TempMail
This commit is contained in:
@@ -10,7 +10,9 @@
|
|||||||
- **多邮箱服务支持**
|
- **多邮箱服务支持**
|
||||||
- Tempmail.lol(临时邮箱,无需配置)
|
- Tempmail.lol(临时邮箱,无需配置)
|
||||||
- Outlook(IMAP + XOAUTH2,支持批量导入)
|
- Outlook(IMAP + XOAUTH2,支持批量导入)
|
||||||
- 自定义域名(REST API)
|
- 自定义域名(两种子类型)
|
||||||
|
- **MoeMail**:标准 REST API,配置 API 地址 + API 密钥
|
||||||
|
- **TempMail**:自部署 Cloudflare Worker 临时邮箱,配置 Worker 地址 + Admin 密码
|
||||||
|
|
||||||
- **注册模式**
|
- **注册模式**
|
||||||
- 单次注册
|
- 单次注册
|
||||||
@@ -278,7 +280,8 @@ docker-compose build --no-cache
|
|||||||
- CPA 上传始终直连,不经过代理
|
- CPA 上传始终直连,不经过代理
|
||||||
- Team Manager 上传始终直连,不经过代理
|
- Team Manager 上传始终直连,不经过代理
|
||||||
- 支付链接生成使用账号 access_token 鉴权,走全局代理配置
|
- 支付链接生成使用账号 access_token 鉴权,走全局代理配置
|
||||||
- 无痕浏览器依次尝试 Chrome、Edge,未找到时返回失败提示
|
- 无痕浏览器优先使用 playwright(注入 cookie 直达支付页);未安装时降级为系统 Chrome/Edge 无痕模式
|
||||||
|
- 安装完整支付功能:`pip install playwright && playwright install chromium`(可选)
|
||||||
- 订阅状态自动检测调用 `chatgpt.com/backend-api/me`,走全局代理
|
- 订阅状态自动检测调用 `chatgpt.com/backend-api/me`,走全局代理
|
||||||
- 批量注册并发数上限为 50,线程池大小已相应调整
|
- 批量注册并发数上限为 50,线程池大小已相应调整
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
let outlookServices = [];
|
let outlookServices = [];
|
||||||
let customServices = [];
|
let customServices = []; // 合并 custom_domain + temp_mail
|
||||||
let selectedOutlook = new Set();
|
let selectedOutlook = new Set();
|
||||||
let selectedCustom = new Set();
|
let selectedCustom = new Set();
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ const elements = {
|
|||||||
selectAllOutlook: document.getElementById('select-all-outlook'),
|
selectAllOutlook: document.getElementById('select-all-outlook'),
|
||||||
batchDeleteOutlookBtn: document.getElementById('batch-delete-outlook-btn'),
|
batchDeleteOutlookBtn: document.getElementById('batch-delete-outlook-btn'),
|
||||||
|
|
||||||
// 自定义域名
|
// 自定义域名(合并)
|
||||||
customTable: document.getElementById('custom-services-table'),
|
customTable: document.getElementById('custom-services-table'),
|
||||||
addCustomBtn: document.getElementById('add-custom-btn'),
|
addCustomBtn: document.getElementById('add-custom-btn'),
|
||||||
selectAllCustom: document.getElementById('select-all-custom'),
|
selectAllCustom: document.getElementById('select-all-custom'),
|
||||||
@@ -47,30 +47,25 @@ const elements = {
|
|||||||
addCustomForm: document.getElementById('add-custom-form'),
|
addCustomForm: document.getElementById('add-custom-form'),
|
||||||
closeCustomModal: document.getElementById('close-custom-modal'),
|
closeCustomModal: document.getElementById('close-custom-modal'),
|
||||||
cancelAddCustom: document.getElementById('cancel-add-custom'),
|
cancelAddCustom: document.getElementById('cancel-add-custom'),
|
||||||
|
customSubType: document.getElementById('custom-sub-type'),
|
||||||
|
addMoemailFields: document.getElementById('add-moemail-fields'),
|
||||||
|
addTempmailFields: document.getElementById('add-tempmail-fields'),
|
||||||
|
|
||||||
// 编辑自定义域名模态框
|
// 编辑自定义域名模态框
|
||||||
editCustomModal: document.getElementById('edit-custom-modal'),
|
editCustomModal: document.getElementById('edit-custom-modal'),
|
||||||
editCustomForm: document.getElementById('edit-custom-form'),
|
editCustomForm: document.getElementById('edit-custom-form'),
|
||||||
closeEditCustomModal: document.getElementById('close-edit-custom-modal'),
|
closeEditCustomModal: document.getElementById('close-edit-custom-modal'),
|
||||||
cancelEditCustom: document.getElementById('cancel-edit-custom'),
|
cancelEditCustom: document.getElementById('cancel-edit-custom'),
|
||||||
|
editMoemailFields: document.getElementById('edit-moemail-fields'),
|
||||||
|
editTempmailFields: document.getElementById('edit-tempmail-fields'),
|
||||||
|
editCustomTypeBadge: document.getElementById('edit-custom-type-badge'),
|
||||||
|
editCustomSubTypeHidden: document.getElementById('edit-custom-sub-type-hidden'),
|
||||||
|
|
||||||
// 编辑 Outlook 模态框
|
// 编辑 Outlook 模态框
|
||||||
editOutlookModal: document.getElementById('edit-outlook-modal'),
|
editOutlookModal: document.getElementById('edit-outlook-modal'),
|
||||||
editOutlookForm: document.getElementById('edit-outlook-form'),
|
editOutlookForm: document.getElementById('edit-outlook-form'),
|
||||||
closeEditOutlookModal: document.getElementById('close-edit-outlook-modal'),
|
closeEditOutlookModal: document.getElementById('close-edit-outlook-modal'),
|
||||||
cancelEditOutlook: document.getElementById('cancel-edit-outlook'),
|
cancelEditOutlook: document.getElementById('cancel-edit-outlook'),
|
||||||
|
|
||||||
// Temp-Mail 服务
|
|
||||||
tempMailTable: document.getElementById('tempmail-services-table'),
|
|
||||||
addTempMailBtn: document.getElementById('add-tempmail-btn'),
|
|
||||||
addTempMailModal: document.getElementById('add-tempmail-modal'),
|
|
||||||
addTempMailForm: document.getElementById('add-tempmail-form'),
|
|
||||||
closeAddTempMailModal: document.getElementById('close-add-tempmail-modal'),
|
|
||||||
cancelAddTempMail: document.getElementById('cancel-add-tempmail'),
|
|
||||||
editTempMailModal: document.getElementById('edit-tempmail-modal'),
|
|
||||||
editTempMailForm: document.getElementById('edit-tempmail-form'),
|
|
||||||
closeEditTempMailModal: document.getElementById('close-edit-tempmail-modal'),
|
|
||||||
cancelEditTempMail: document.getElementById('cancel-edit-tempmail')
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
@@ -78,7 +73,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
loadStats();
|
loadStats();
|
||||||
loadOutlookServices();
|
loadOutlookServices();
|
||||||
loadCustomServices();
|
loadCustomServices();
|
||||||
loadTempMailServices();
|
|
||||||
loadTempmailConfig();
|
loadTempmailConfig();
|
||||||
initEventListeners();
|
initEventListeners();
|
||||||
});
|
});
|
||||||
@@ -105,11 +99,8 @@ function initEventListeners() {
|
|||||||
checkboxes.forEach(cb => {
|
checkboxes.forEach(cb => {
|
||||||
cb.checked = e.target.checked;
|
cb.checked = e.target.checked;
|
||||||
const id = parseInt(cb.dataset.id);
|
const id = parseInt(cb.dataset.id);
|
||||||
if (e.target.checked) {
|
if (e.target.checked) selectedOutlook.add(id);
|
||||||
selectedOutlook.add(id);
|
else selectedOutlook.delete(id);
|
||||||
} else {
|
|
||||||
selectedOutlook.delete(id);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
updateBatchButtons();
|
updateBatchButtons();
|
||||||
});
|
});
|
||||||
@@ -117,80 +108,64 @@ function initEventListeners() {
|
|||||||
// Outlook 批量删除
|
// Outlook 批量删除
|
||||||
elements.batchDeleteOutlookBtn.addEventListener('click', handleBatchDeleteOutlook);
|
elements.batchDeleteOutlookBtn.addEventListener('click', handleBatchDeleteOutlook);
|
||||||
|
|
||||||
// 添加自定义域名
|
|
||||||
elements.addCustomBtn.addEventListener('click', () => {
|
|
||||||
elements.addCustomModal.classList.add('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.closeCustomModal.addEventListener('click', () => {
|
|
||||||
elements.addCustomModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.cancelAddCustom.addEventListener('click', () => {
|
|
||||||
elements.addCustomModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.addCustomForm.addEventListener('submit', handleAddCustom);
|
|
||||||
|
|
||||||
// 编辑自定义域名模态框
|
|
||||||
elements.closeEditCustomModal.addEventListener('click', () => {
|
|
||||||
elements.editCustomModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.cancelEditCustom.addEventListener('click', () => {
|
|
||||||
elements.editCustomModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.editCustomForm.addEventListener('submit', handleEditCustom);
|
|
||||||
|
|
||||||
// 编辑 Outlook 模态框
|
|
||||||
elements.closeEditOutlookModal.addEventListener('click', () => {
|
|
||||||
elements.editOutlookModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.cancelEditOutlook.addEventListener('click', () => {
|
|
||||||
elements.editOutlookModal.classList.remove('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
elements.editOutlookForm.addEventListener('submit', handleEditOutlook);
|
|
||||||
|
|
||||||
// 自定义域名全选
|
// 自定义域名全选
|
||||||
elements.selectAllCustom.addEventListener('change', (e) => {
|
elements.selectAllCustom.addEventListener('change', (e) => {
|
||||||
const checkboxes = elements.customTable.querySelectorAll('input[type="checkbox"][data-id]');
|
const checkboxes = elements.customTable.querySelectorAll('input[type="checkbox"][data-id]');
|
||||||
checkboxes.forEach(cb => {
|
checkboxes.forEach(cb => {
|
||||||
cb.checked = e.target.checked;
|
cb.checked = e.target.checked;
|
||||||
const id = parseInt(cb.dataset.id);
|
const id = parseInt(cb.dataset.id);
|
||||||
if (e.target.checked) {
|
if (e.target.checked) selectedCustom.add(id);
|
||||||
selectedCustom.add(id);
|
else selectedCustom.delete(id);
|
||||||
} else {
|
|
||||||
selectedCustom.delete(id);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加自定义域名
|
||||||
|
elements.addCustomBtn.addEventListener('click', () => {
|
||||||
|
elements.addCustomForm.reset();
|
||||||
|
switchAddSubType('moemail');
|
||||||
|
elements.addCustomModal.classList.add('active');
|
||||||
|
});
|
||||||
|
elements.closeCustomModal.addEventListener('click', () => elements.addCustomModal.classList.remove('active'));
|
||||||
|
elements.cancelAddCustom.addEventListener('click', () => elements.addCustomModal.classList.remove('active'));
|
||||||
|
elements.addCustomForm.addEventListener('submit', handleAddCustom);
|
||||||
|
|
||||||
|
// 类型切换(添加表单)
|
||||||
|
elements.customSubType.addEventListener('change', (e) => switchAddSubType(e.target.value));
|
||||||
|
|
||||||
|
// 编辑自定义域名
|
||||||
|
elements.closeEditCustomModal.addEventListener('click', () => elements.editCustomModal.classList.remove('active'));
|
||||||
|
elements.cancelEditCustom.addEventListener('click', () => elements.editCustomModal.classList.remove('active'));
|
||||||
|
elements.editCustomForm.addEventListener('submit', handleEditCustom);
|
||||||
|
|
||||||
|
// 编辑 Outlook
|
||||||
|
elements.closeEditOutlookModal.addEventListener('click', () => elements.editOutlookModal.classList.remove('active'));
|
||||||
|
elements.cancelEditOutlook.addEventListener('click', () => elements.editOutlookModal.classList.remove('active'));
|
||||||
|
elements.editOutlookForm.addEventListener('submit', handleEditOutlook);
|
||||||
|
|
||||||
// 临时邮箱配置
|
// 临时邮箱配置
|
||||||
elements.tempmailForm.addEventListener('submit', handleSaveTempmail);
|
elements.tempmailForm.addEventListener('submit', handleSaveTempmail);
|
||||||
elements.testTempmailBtn.addEventListener('click', handleTestTempmail);
|
elements.testTempmailBtn.addEventListener('click', handleTestTempmail);
|
||||||
|
}
|
||||||
|
|
||||||
// Temp-Mail 服务
|
// 切换添加表单子类型
|
||||||
elements.addTempMailBtn.addEventListener('click', () => {
|
function switchAddSubType(subType) {
|
||||||
elements.addTempMailModal.classList.add('active');
|
elements.customSubType.value = subType;
|
||||||
});
|
if (subType === 'moemail') {
|
||||||
elements.closeAddTempMailModal.addEventListener('click', () => {
|
elements.addMoemailFields.style.display = '';
|
||||||
elements.addTempMailModal.classList.remove('active');
|
elements.addTempmailFields.style.display = 'none';
|
||||||
});
|
} else {
|
||||||
elements.cancelAddTempMail.addEventListener('click', () => {
|
elements.addMoemailFields.style.display = 'none';
|
||||||
elements.addTempMailModal.classList.remove('active');
|
elements.addTempmailFields.style.display = '';
|
||||||
});
|
}
|
||||||
elements.addTempMailForm.addEventListener('submit', handleAddTempMail);
|
}
|
||||||
|
|
||||||
elements.closeEditTempMailModal.addEventListener('click', () => {
|
// 切换编辑表单子类型显示
|
||||||
elements.editTempMailModal.classList.remove('active');
|
function switchEditSubType(subType) {
|
||||||
});
|
elements.editCustomSubTypeHidden.value = subType;
|
||||||
elements.cancelEditTempMail.addEventListener('click', () => {
|
const isMoe = subType === 'moemail';
|
||||||
elements.editTempMailModal.classList.remove('active');
|
elements.editMoemailFields.style.display = isMoe ? '' : 'none';
|
||||||
});
|
elements.editTempmailFields.style.display = isMoe ? 'none' : '';
|
||||||
elements.editTempMailForm.addEventListener('submit', handleEditTempMail);
|
elements.editCustomTypeBadge.textContent = isMoe ? '🔗 MoeMail(自定义域名 API)' : '📮 TempMail(自部署 Cloudflare Worker)';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载统计信息
|
// 加载统计信息
|
||||||
@@ -198,7 +173,7 @@ async function loadStats() {
|
|||||||
try {
|
try {
|
||||||
const data = await api.get('/email-services/stats');
|
const data = await api.get('/email-services/stats');
|
||||||
elements.outlookCount.textContent = data.outlook_count || 0;
|
elements.outlookCount.textContent = data.outlook_count || 0;
|
||||||
elements.customCount.textContent = data.custom_count || 0;
|
elements.customCount.textContent = (data.custom_count || 0) + (data.temp_mail_count || 0);
|
||||||
elements.tempmailStatus.textContent = data.tempmail_available ? '可用' : '不可用';
|
elements.tempmailStatus.textContent = data.tempmail_available ? '可用' : '不可用';
|
||||||
elements.totalEnabled.textContent = data.enabled_count || 0;
|
elements.totalEnabled.textContent = data.enabled_count || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -229,10 +204,7 @@ async function loadOutlookServices() {
|
|||||||
|
|
||||||
elements.outlookTable.innerHTML = outlookServices.map(service => `
|
elements.outlookTable.innerHTML = outlookServices.map(service => `
|
||||||
<tr data-id="${service.id}">
|
<tr data-id="${service.id}">
|
||||||
<td>
|
<td><input type="checkbox" data-id="${service.id}" ${selectedOutlook.has(service.id) ? 'checked' : ''}></td>
|
||||||
<input type="checkbox" data-id="${service.id}"
|
|
||||||
${selectedOutlook.has(service.id) ? 'checked' : ''}>
|
|
||||||
</td>
|
|
||||||
<td>${escapeHtml(service.config?.email || service.name)}</td>
|
<td>${escapeHtml(service.config?.email || service.name)}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge ${service.config?.has_oauth ? 'active' : 'pending'}">
|
<span class="status-badge ${service.config?.has_oauth ? 'active' : 'pending'}">
|
||||||
@@ -248,65 +220,50 @@ async function loadOutlookServices() {
|
|||||||
<td>${format.date(service.last_used)}</td>
|
<td>${format.date(service.last_used)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="editOutlookService(${service.id})" title="编辑">
|
<button class="btn btn-ghost btn-sm" onclick="editOutlookService(${service.id})" title="编辑">✏️</button>
|
||||||
✏️
|
<button class="btn btn-ghost btn-sm" onclick="toggleService(${service.id}, ${!service.enabled})" title="${service.enabled ? '禁用' : '启用'}">${service.enabled ? '🔇' : '🔊'}</button>
|
||||||
</button>
|
<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 ? '禁用' : '启用'}">
|
<button class="btn btn-ghost btn-sm" onclick="deleteService(${service.id}, '${escapeHtml(service.name)}')" title="删除">🗑️</button>
|
||||||
${service.enabled ? '🔇' : '🔊'}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="testService(${service.id})" title="测试">
|
|
||||||
🔌
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="deleteService(${service.id}, '${escapeHtml(service.name)}')" title="删除">
|
|
||||||
🗑️
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
// 绑定复选框事件
|
|
||||||
elements.outlookTable.querySelectorAll('input[type="checkbox"][data-id]').forEach(cb => {
|
elements.outlookTable.querySelectorAll('input[type="checkbox"][data-id]').forEach(cb => {
|
||||||
cb.addEventListener('change', (e) => {
|
cb.addEventListener('change', (e) => {
|
||||||
const id = parseInt(e.target.dataset.id);
|
const id = parseInt(e.target.dataset.id);
|
||||||
if (e.target.checked) {
|
if (e.target.checked) selectedOutlook.add(id);
|
||||||
selectedOutlook.add(id);
|
else selectedOutlook.delete(id);
|
||||||
} else {
|
|
||||||
selectedOutlook.delete(id);
|
|
||||||
}
|
|
||||||
updateBatchButtons();
|
updateBatchButtons();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载 Outlook 服务失败:', error);
|
console.error('加载 Outlook 服务失败:', error);
|
||||||
elements.outlookTable.innerHTML = `
|
elements.outlookTable.innerHTML = `<tr><td colspan="7"><div class="empty-state"><div class="empty-state-icon">❌</div><div class="empty-state-title">加载失败</div></div></td></tr>`;
|
||||||
<tr>
|
|
||||||
<td colspan="7">
|
|
||||||
<div class="empty-state">
|
|
||||||
<div class="empty-state-icon">❌</div>
|
|
||||||
<div class="empty-state-title">加载失败</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载自定义域名服务
|
// 加载自定义域名服务(custom_domain + temp_mail 合并)
|
||||||
async function loadCustomServices() {
|
async function loadCustomServices() {
|
||||||
try {
|
try {
|
||||||
const data = await api.get('/email-services?service_type=custom_domain');
|
const [r1, r2] = await Promise.all([
|
||||||
customServices = data.services || [];
|
api.get('/email-services?service_type=custom_domain'),
|
||||||
|
api.get('/email-services?service_type=temp_mail')
|
||||||
|
]);
|
||||||
|
customServices = [
|
||||||
|
...(r1.services || []).map(s => ({ ...s, _subType: 'moemail' })),
|
||||||
|
...(r2.services || []).map(s => ({ ...s, _subType: 'tempmail' }))
|
||||||
|
];
|
||||||
|
|
||||||
if (customServices.length === 0) {
|
if (customServices.length === 0) {
|
||||||
elements.customTable.innerHTML = `
|
elements.customTable.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7">
|
<td colspan="8">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="empty-state-icon">📭</div>
|
<div class="empty-state-icon">📭</div>
|
||||||
<div class="empty-state-title">暂无自定义域名服务</div>
|
<div class="empty-state-title">暂无自定义域名服务</div>
|
||||||
<div class="empty-state-description">点击"添加服务"按钮创建新服务</div>
|
<div class="empty-state-description">点击「添加服务」按钮创建新服务</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -314,49 +271,35 @@ async function loadCustomServices() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.customTable.innerHTML = customServices.map(service => `
|
elements.customTable.innerHTML = customServices.map(service => {
|
||||||
|
const isMoe = service._subType === 'moemail';
|
||||||
|
const typeLabel = isMoe ? '<span class="status-badge info">MoeMail</span>' : '<span class="status-badge warning">TempMail</span>';
|
||||||
|
const addr = isMoe ? (service.config?.base_url || '-') : (service.config?.base_url || '-');
|
||||||
|
return `
|
||||||
<tr data-id="${service.id}">
|
<tr data-id="${service.id}">
|
||||||
<td>
|
<td><input type="checkbox" data-id="${service.id}" ${selectedCustom.has(service.id) ? 'checked' : ''}></td>
|
||||||
<input type="checkbox" data-id="${service.id}"
|
|
||||||
${selectedCustom.has(service.id) ? 'checked' : ''}>
|
|
||||||
</td>
|
|
||||||
<td>${escapeHtml(service.name)}</td>
|
<td>${escapeHtml(service.name)}</td>
|
||||||
<td style="font-size: 0.75rem;">${escapeHtml(service.config?.base_url || '-')}</td>
|
<td>${typeLabel}</td>
|
||||||
<td>
|
<td style="font-size: 0.75rem;">${escapeHtml(addr)}</td>
|
||||||
<span class="status-badge ${service.enabled ? 'active' : 'disabled'}">
|
<td><span class="status-badge ${service.enabled ? 'active' : 'disabled'}">${service.enabled ? '启用' : '禁用'}</span></td>
|
||||||
${service.enabled ? '启用' : '禁用'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>${service.priority}</td>
|
<td>${service.priority}</td>
|
||||||
<td>${format.date(service.last_used)}</td>
|
<td>${format.date(service.last_used)}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="btn btn-ghost btn-sm" onclick="editCustomService(${service.id})" title="编辑">
|
<button class="btn btn-ghost btn-sm" onclick="editCustomService(${service.id}, '${service._subType}')" title="编辑">✏️</button>
|
||||||
✏️
|
<button class="btn btn-ghost btn-sm" onclick="toggleService(${service.id}, ${!service.enabled})" title="${service.enabled ? '禁用' : '启用'}">${service.enabled ? '🔇' : '🔊'}</button>
|
||||||
</button>
|
<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 ? '禁用' : '启用'}">
|
<button class="btn btn-ghost btn-sm" onclick="deleteService(${service.id}, '${escapeHtml(service.name)}')" title="删除">🗑️</button>
|
||||||
${service.enabled ? '🔇' : '🔊'}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="testService(${service.id})" title="测试">
|
|
||||||
🔌
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="deleteService(${service.id}, '${escapeHtml(service.name)}')" title="删除">
|
|
||||||
🗑️
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>`;
|
||||||
`).join('');
|
}).join('');
|
||||||
|
|
||||||
// 绑定复选框事件
|
|
||||||
elements.customTable.querySelectorAll('input[type="checkbox"][data-id]').forEach(cb => {
|
elements.customTable.querySelectorAll('input[type="checkbox"][data-id]').forEach(cb => {
|
||||||
cb.addEventListener('change', (e) => {
|
cb.addEventListener('change', (e) => {
|
||||||
const id = parseInt(e.target.dataset.id);
|
const id = parseInt(e.target.dataset.id);
|
||||||
if (e.target.checked) {
|
if (e.target.checked) selectedCustom.add(id);
|
||||||
selectedCustom.add(id);
|
else selectedCustom.delete(id);
|
||||||
} else {
|
|
||||||
selectedCustom.delete(id);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -381,10 +324,7 @@ async function loadTempmailConfig() {
|
|||||||
// Outlook 导入
|
// Outlook 导入
|
||||||
async function handleOutlookImport() {
|
async function handleOutlookImport() {
|
||||||
const data = elements.outlookImportData.value.trim();
|
const data = elements.outlookImportData.value.trim();
|
||||||
if (!data) {
|
if (!data) { toast.error('请输入导入数据'); return; }
|
||||||
toast.error('请输入导入数据');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.outlookImportBtn.disabled = true;
|
elements.outlookImportBtn.disabled = true;
|
||||||
elements.outlookImportBtn.textContent = '导入中...';
|
elements.outlookImportBtn.textContent = '导入中...';
|
||||||
@@ -402,14 +342,7 @@ async function handleOutlookImport() {
|
|||||||
<span>✅ 成功导入: <strong>${result.success || 0}</strong></span>
|
<span>✅ 成功导入: <strong>${result.success || 0}</strong></span>
|
||||||
<span>❌ 失败: <strong>${result.failed || 0}</strong></span>
|
<span>❌ 失败: <strong>${result.failed || 0}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
${result.errors?.length ? `
|
${result.errors?.length ? `<div class="import-errors" style="margin-top: var(--spacing-sm);"><strong>错误详情:</strong><ul>${result.errors.map(e => `<li>${escapeHtml(e)}</li>`).join('')}</ul></div>` : ''}
|
||||||
<div class="import-errors" style="margin-top: var(--spacing-sm);">
|
|
||||||
<strong>错误详情:</strong>
|
|
||||||
<ul>
|
|
||||||
${result.errors.map(e => `<li>${escapeHtml(e)}</li>`).join('')}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (result.success > 0) {
|
if (result.success > 0) {
|
||||||
@@ -418,7 +351,6 @@ async function handleOutlookImport() {
|
|||||||
loadStats();
|
loadStats();
|
||||||
elements.outlookImportData.value = '';
|
elements.outlookImportData.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('导入失败: ' + error.message);
|
toast.error('导入失败: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -427,19 +359,34 @@ async function handleOutlookImport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加自定义域名服务
|
// 添加自定义域名服务(根据子类型区分)
|
||||||
async function handleAddCustom(e) {
|
async function handleAddCustom(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const formData = new FormData(e.target);
|
const formData = new FormData(e.target);
|
||||||
const data = {
|
const subType = formData.get('sub_type');
|
||||||
service_type: 'custom_domain',
|
|
||||||
name: formData.get('name'),
|
let serviceType, config;
|
||||||
config: {
|
if (subType === 'moemail') {
|
||||||
|
serviceType = 'custom_domain';
|
||||||
|
config = {
|
||||||
base_url: formData.get('api_url'),
|
base_url: formData.get('api_url'),
|
||||||
api_key: formData.get('api_key'),
|
api_key: formData.get('api_key'),
|
||||||
default_domain: formData.get('domain')
|
default_domain: formData.get('domain')
|
||||||
},
|
};
|
||||||
|
} else {
|
||||||
|
serviceType = 'temp_mail';
|
||||||
|
config = {
|
||||||
|
base_url: formData.get('tm_base_url'),
|
||||||
|
admin_password: formData.get('tm_admin_password'),
|
||||||
|
domain: formData.get('tm_domain'),
|
||||||
|
enable_prefix: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
service_type: serviceType,
|
||||||
|
name: formData.get('name'),
|
||||||
|
config,
|
||||||
enabled: formData.get('enabled') === 'on',
|
enabled: formData.get('enabled') === 'on',
|
||||||
priority: parseInt(formData.get('priority')) || 0
|
priority: parseInt(formData.get('priority')) || 0
|
||||||
};
|
};
|
||||||
@@ -473,11 +420,8 @@ async function toggleService(id, enabled) {
|
|||||||
async function testService(id) {
|
async function testService(id) {
|
||||||
try {
|
try {
|
||||||
const result = await api.post(`/email-services/${id}/test`);
|
const result = await api.post(`/email-services/${id}/test`);
|
||||||
if (result.success) {
|
if (result.success) toast.success('测试成功');
|
||||||
toast.success('测试成功');
|
else toast.error('测试失败: ' + (result.error || '未知错误'));
|
||||||
} else {
|
|
||||||
toast.error('测试失败: ' + (result.error || '未知错误'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('测试失败: ' + error.message);
|
toast.error('测试失败: ' + error.message);
|
||||||
}
|
}
|
||||||
@@ -487,7 +431,6 @@ async function testService(id) {
|
|||||||
async function deleteService(id, name) {
|
async function deleteService(id, name) {
|
||||||
const confirmed = await confirm(`确定要删除 "${name}" 吗?`);
|
const confirmed = await confirm(`确定要删除 "${name}" 吗?`);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.delete(`/email-services/${id}`);
|
await api.delete(`/email-services/${id}`);
|
||||||
toast.success('已删除');
|
toast.success('已删除');
|
||||||
@@ -504,10 +447,8 @@ async function deleteService(id, name) {
|
|||||||
// 批量删除 Outlook
|
// 批量删除 Outlook
|
||||||
async function handleBatchDeleteOutlook() {
|
async function handleBatchDeleteOutlook() {
|
||||||
if (selectedOutlook.size === 0) return;
|
if (selectedOutlook.size === 0) return;
|
||||||
|
|
||||||
const confirmed = await confirm(`确定要删除选中的 ${selectedOutlook.size} 个账户吗?`);
|
const confirmed = await confirm(`确定要删除选中的 ${selectedOutlook.size} 个账户吗?`);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await api.request('/email-services/outlook/batch', {
|
const result = await api.request('/email-services/outlook/batch', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -525,7 +466,6 @@ async function handleBatchDeleteOutlook() {
|
|||||||
// 保存临时邮箱配置
|
// 保存临时邮箱配置
|
||||||
async function handleSaveTempmail(e) {
|
async function handleSaveTempmail(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post('/settings/tempmail', {
|
await api.post('/settings/tempmail', {
|
||||||
api_url: elements.tempmailApi.value,
|
api_url: elements.tempmailApi.value,
|
||||||
@@ -541,17 +481,12 @@ async function handleSaveTempmail(e) {
|
|||||||
async function handleTestTempmail() {
|
async function handleTestTempmail() {
|
||||||
elements.testTempmailBtn.disabled = true;
|
elements.testTempmailBtn.disabled = true;
|
||||||
elements.testTempmailBtn.textContent = '测试中...';
|
elements.testTempmailBtn.textContent = '测试中...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await api.post('/email-services/test-tempmail', {
|
const result = await api.post('/email-services/test-tempmail', {
|
||||||
api_url: elements.tempmailApi.value
|
api_url: elements.tempmailApi.value
|
||||||
});
|
});
|
||||||
|
if (result.success) toast.success('临时邮箱连接正常');
|
||||||
if (result.success) {
|
else toast.error('连接失败: ' + (result.error || '未知错误'));
|
||||||
toast.success('临时邮箱连接正常');
|
|
||||||
} else {
|
|
||||||
toast.error('连接失败: ' + (result.error || '未知错误'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('测试失败: ' + error.message);
|
toast.error('测试失败: ' + error.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -577,27 +512,32 @@ function escapeHtml(text) {
|
|||||||
|
|
||||||
// ============== 编辑功能 ==============
|
// ============== 编辑功能 ==============
|
||||||
|
|
||||||
// 编辑自定义域名服务
|
// 编辑自定义域名服务(支持 moemail / tempmail)
|
||||||
async function editCustomService(id) {
|
async function editCustomService(id, subType) {
|
||||||
try {
|
try {
|
||||||
// 获取完整的服务详情
|
|
||||||
const service = await api.get(`/email-services/${id}/full`);
|
const service = await api.get(`/email-services/${id}/full`);
|
||||||
|
const resolvedSubType = subType || (service.service_type === 'temp_mail' ? 'tempmail' : 'moemail');
|
||||||
|
|
||||||
// 填充表单
|
|
||||||
document.getElementById('edit-custom-id').value = service.id;
|
document.getElementById('edit-custom-id').value = service.id;
|
||||||
document.getElementById('edit-custom-name').value = service.name || '';
|
document.getElementById('edit-custom-name').value = service.name || '';
|
||||||
document.getElementById('edit-custom-api-url').value = service.config?.base_url || '';
|
|
||||||
document.getElementById('edit-custom-api-key').value = service.config?.api_key || '';
|
|
||||||
document.getElementById('edit-custom-domain').value = service.config?.domain || '';
|
|
||||||
document.getElementById('edit-custom-priority').value = service.priority || 0;
|
document.getElementById('edit-custom-priority').value = service.priority || 0;
|
||||||
document.getElementById('edit-custom-enabled').checked = service.enabled;
|
document.getElementById('edit-custom-enabled').checked = service.enabled;
|
||||||
|
|
||||||
// 清空密码提示
|
switchEditSubType(resolvedSubType);
|
||||||
document.getElementById('edit-custom-api-key').placeholder = service.config?.has_api_key ? '已设置,留空保持不变' : 'API Key';
|
|
||||||
|
if (resolvedSubType === 'moemail') {
|
||||||
|
document.getElementById('edit-custom-api-url').value = service.config?.base_url || '';
|
||||||
|
document.getElementById('edit-custom-api-key').value = '';
|
||||||
|
document.getElementById('edit-custom-api-key').placeholder = service.config?.has_api_key ? '已设置,留空保持不变' : 'API Key';
|
||||||
|
document.getElementById('edit-custom-domain').value = service.config?.default_domain || service.config?.domain || '';
|
||||||
|
} else {
|
||||||
|
document.getElementById('edit-tm-base-url').value = service.config?.base_url || '';
|
||||||
|
document.getElementById('edit-tm-admin-password').value = '';
|
||||||
|
document.getElementById('edit-tm-admin-password').placeholder = service.config?.admin_password ? '已设置,留空保持不变' : '请输入 Admin 密码';
|
||||||
|
document.getElementById('edit-tm-domain').value = service.config?.domain || '';
|
||||||
|
}
|
||||||
|
|
||||||
// 显示模态框
|
|
||||||
elements.editCustomModal.classList.add('active');
|
elements.editCustomModal.classList.add('active');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('获取服务信息失败: ' + error.message);
|
toast.error('获取服务信息失败: ' + error.message);
|
||||||
}
|
}
|
||||||
@@ -606,31 +546,35 @@ async function editCustomService(id) {
|
|||||||
// 保存编辑自定义域名服务
|
// 保存编辑自定义域名服务
|
||||||
async function handleEditCustom(e) {
|
async function handleEditCustom(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const id = document.getElementById('edit-custom-id').value;
|
const id = document.getElementById('edit-custom-id').value;
|
||||||
const formData = new FormData(e.target);
|
const formData = new FormData(e.target);
|
||||||
|
const subType = formData.get('sub_type');
|
||||||
|
|
||||||
|
let config;
|
||||||
|
if (subType === 'moemail') {
|
||||||
|
config = {
|
||||||
|
base_url: formData.get('api_url'),
|
||||||
|
default_domain: formData.get('domain')
|
||||||
|
};
|
||||||
|
const apiKey = formData.get('api_key');
|
||||||
|
if (apiKey && apiKey.trim()) config.api_key = apiKey.trim();
|
||||||
|
} else {
|
||||||
|
config = {
|
||||||
|
base_url: formData.get('tm_base_url'),
|
||||||
|
domain: formData.get('tm_domain'),
|
||||||
|
enable_prefix: true
|
||||||
|
};
|
||||||
|
const pwd = formData.get('tm_admin_password');
|
||||||
|
if (pwd && pwd.trim()) config.admin_password = pwd.trim();
|
||||||
|
}
|
||||||
|
|
||||||
// 构建更新数据
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
name: formData.get('name'),
|
name: formData.get('name'),
|
||||||
priority: parseInt(formData.get('priority')) || 0,
|
priority: parseInt(formData.get('priority')) || 0,
|
||||||
enabled: formData.get('enabled') === 'on'
|
enabled: formData.get('enabled') === 'on',
|
||||||
|
config
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建配置
|
|
||||||
const config = {
|
|
||||||
base_url: formData.get('api_url'),
|
|
||||||
default_domain: formData.get('domain')
|
|
||||||
};
|
|
||||||
|
|
||||||
// 只有在填写了 API Key 时才更新
|
|
||||||
const apiKey = formData.get('api_key');
|
|
||||||
if (apiKey && apiKey.trim()) {
|
|
||||||
config.api_key = apiKey.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateData.config = config;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.patch(`/email-services/${id}`, updateData);
|
await api.patch(`/email-services/${id}`, updateData);
|
||||||
toast.success('服务更新成功');
|
toast.success('服务更新成功');
|
||||||
@@ -645,10 +589,7 @@ async function handleEditCustom(e) {
|
|||||||
// 编辑 Outlook 服务
|
// 编辑 Outlook 服务
|
||||||
async function editOutlookService(id) {
|
async function editOutlookService(id) {
|
||||||
try {
|
try {
|
||||||
// 获取完整的服务详情
|
|
||||||
const service = await api.get(`/email-services/${id}/full`);
|
const service = await api.get(`/email-services/${id}/full`);
|
||||||
|
|
||||||
// 填充表单
|
|
||||||
document.getElementById('edit-outlook-id').value = service.id;
|
document.getElementById('edit-outlook-id').value = service.id;
|
||||||
document.getElementById('edit-outlook-email').value = service.config?.email || service.name || '';
|
document.getElementById('edit-outlook-email').value = service.config?.email || service.name || '';
|
||||||
document.getElementById('edit-outlook-password').value = '';
|
document.getElementById('edit-outlook-password').value = '';
|
||||||
@@ -658,10 +599,7 @@ async function editOutlookService(id) {
|
|||||||
document.getElementById('edit-outlook-refresh-token').placeholder = service.config?.refresh_token ? '已设置,留空保持不变' : 'OAuth Refresh Token';
|
document.getElementById('edit-outlook-refresh-token').placeholder = service.config?.refresh_token ? '已设置,留空保持不变' : 'OAuth Refresh Token';
|
||||||
document.getElementById('edit-outlook-priority').value = service.priority || 0;
|
document.getElementById('edit-outlook-priority').value = service.priority || 0;
|
||||||
document.getElementById('edit-outlook-enabled').checked = service.enabled;
|
document.getElementById('edit-outlook-enabled').checked = service.enabled;
|
||||||
|
|
||||||
// 显示模态框
|
|
||||||
elements.editOutlookModal.classList.add('active');
|
elements.editOutlookModal.classList.add('active');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('获取服务信息失败: ' + error.message);
|
toast.error('获取服务信息失败: ' + error.message);
|
||||||
}
|
}
|
||||||
@@ -670,11 +608,9 @@ async function editOutlookService(id) {
|
|||||||
// 保存编辑 Outlook 服务
|
// 保存编辑 Outlook 服务
|
||||||
async function handleEditOutlook(e) {
|
async function handleEditOutlook(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const id = document.getElementById('edit-outlook-id').value;
|
const id = document.getElementById('edit-outlook-id').value;
|
||||||
const formData = new FormData(e.target);
|
const formData = new FormData(e.target);
|
||||||
|
|
||||||
// 获取当前服务信息以保留未修改的敏感字段
|
|
||||||
let currentService;
|
let currentService;
|
||||||
try {
|
try {
|
||||||
currentService = await api.get(`/email-services/${id}/full`);
|
currentService = await api.get(`/email-services/${id}/full`);
|
||||||
@@ -683,23 +619,18 @@ async function handleEditOutlook(e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建更新数据
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
name: formData.get('email'), // 使用邮箱作为名称
|
name: formData.get('email'),
|
||||||
priority: parseInt(formData.get('priority')) || 0,
|
priority: parseInt(formData.get('priority')) || 0,
|
||||||
enabled: formData.get('enabled') === 'on'
|
enabled: formData.get('enabled') === 'on',
|
||||||
|
config: {
|
||||||
|
email: formData.get('email'),
|
||||||
|
password: formData.get('password')?.trim() || currentService.config?.password || '',
|
||||||
|
client_id: formData.get('client_id')?.trim() || currentService.config?.client_id || '',
|
||||||
|
refresh_token: formData.get('refresh_token')?.trim() || currentService.config?.refresh_token || ''
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建配置,保留未修改的敏感字段
|
|
||||||
const config = {
|
|
||||||
email: formData.get('email'),
|
|
||||||
password: formData.get('password')?.trim() || currentService.config?.password || '',
|
|
||||||
client_id: formData.get('client_id')?.trim() || currentService.config?.client_id || '',
|
|
||||||
refresh_token: formData.get('refresh_token')?.trim() || currentService.config?.refresh_token || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
updateData.config = config;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.patch(`/email-services/${id}`, updateData);
|
await api.patch(`/email-services/${id}`, updateData);
|
||||||
toast.success('账户更新成功');
|
toast.success('账户更新成功');
|
||||||
@@ -710,134 +641,3 @@ async function handleEditOutlook(e) {
|
|||||||
toast.error('更新失败: ' + error.message);
|
toast.error('更新失败: ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============== Temp-Mail 服务功能 ==============
|
|
||||||
|
|
||||||
// 加载 Temp-Mail 服务列表
|
|
||||||
async function loadTempMailServices() {
|
|
||||||
try {
|
|
||||||
const data = await api.get('/email-services?service_type=temp_mail');
|
|
||||||
const services = data.services || [];
|
|
||||||
|
|
||||||
if (services.length === 0) {
|
|
||||||
elements.tempMailTable.innerHTML = `
|
|
||||||
<tr><td colspan="6">
|
|
||||||
<div class="empty-state" style="padding: var(--spacing-md);">
|
|
||||||
<div class="empty-state-icon">📮</div>
|
|
||||||
<div class="empty-state-title">暂无 Temp-Mail 服务</div>
|
|
||||||
<div class="empty-state-desc">点击「添加服务」配置自部署 Cloudflare Worker 临时邮箱</div>
|
|
||||||
</div>
|
|
||||||
</td></tr>
|
|
||||||
`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
elements.tempMailTable.innerHTML = services.map(service => {
|
|
||||||
const config = service.config || {};
|
|
||||||
return `
|
|
||||||
<tr>
|
|
||||||
<td><strong>${escapeHtml(service.name)}</strong></td>
|
|
||||||
<td style="font-size: 0.8rem; color: var(--text-muted);">${escapeHtml(config.base_url || '-')}</td>
|
|
||||||
<td>${escapeHtml(config.domain || '-')}</td>
|
|
||||||
<td>
|
|
||||||
<span class="status-badge ${service.enabled ? 'completed' : 'disabled'}">
|
|
||||||
${service.enabled ? '已启用' : '已禁用'}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>${service.priority || 0}</td>
|
|
||||||
<td>
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="editTempMailService(${service.id})" title="编辑">✏️</button>
|
|
||||||
<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('');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载 Temp-Mail 服务失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加 Temp-Mail 服务
|
|
||||||
async function handleAddTempMail(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(e.target);
|
|
||||||
const data = {
|
|
||||||
service_type: 'temp_mail',
|
|
||||||
name: formData.get('name'),
|
|
||||||
config: {
|
|
||||||
base_url: formData.get('base_url'),
|
|
||||||
admin_password: formData.get('admin_password'),
|
|
||||||
domain: formData.get('domain'),
|
|
||||||
enable_prefix: true
|
|
||||||
},
|
|
||||||
enabled: formData.get('enabled') === 'on',
|
|
||||||
priority: parseInt(formData.get('priority')) || 0
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
await api.post('/email-services', data);
|
|
||||||
toast.success('服务添加成功');
|
|
||||||
elements.addTempMailModal.classList.remove('active');
|
|
||||||
e.target.reset();
|
|
||||||
loadTempMailServices();
|
|
||||||
loadStats();
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('添加失败: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑 Temp-Mail 服务
|
|
||||||
async function editTempMailService(id) {
|
|
||||||
try {
|
|
||||||
const service = await api.get(`/email-services/${id}/full`);
|
|
||||||
document.getElementById('edit-tm-id').value = service.id;
|
|
||||||
document.getElementById('edit-tm-name').value = service.name || '';
|
|
||||||
document.getElementById('edit-tm-base-url').value = service.config?.base_url || '';
|
|
||||||
document.getElementById('edit-tm-admin-password').value = '';
|
|
||||||
document.getElementById('edit-tm-admin-password').placeholder = service.config?.admin_password ? '已设置,留空保持不变' : '请输入 Admin 密码';
|
|
||||||
document.getElementById('edit-tm-domain').value = service.config?.domain || '';
|
|
||||||
document.getElementById('edit-tm-priority').value = service.priority || 0;
|
|
||||||
document.getElementById('edit-tm-enabled').checked = service.enabled;
|
|
||||||
elements.editTempMailModal.classList.add('active');
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('获取服务信息失败: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存编辑 Temp-Mail 服务
|
|
||||||
async function handleEditTempMail(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const id = document.getElementById('edit-tm-id').value;
|
|
||||||
const formData = new FormData(e.target);
|
|
||||||
const config = {
|
|
||||||
base_url: formData.get('base_url'),
|
|
||||||
domain: formData.get('domain'),
|
|
||||||
enable_prefix: true
|
|
||||||
};
|
|
||||||
// 只有填写了密码才更新
|
|
||||||
const pwd = formData.get('admin_password');
|
|
||||||
if (pwd && pwd.trim()) {
|
|
||||||
config.admin_password = pwd.trim();
|
|
||||||
}
|
|
||||||
const updateData = {
|
|
||||||
name: formData.get('name'),
|
|
||||||
priority: parseInt(formData.get('priority')) || 0,
|
|
||||||
enabled: formData.get('enabled') === 'on',
|
|
||||||
config
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
await api.patch(`/email-services/${id}`, updateData);
|
|
||||||
toast.success('服务更新成功');
|
|
||||||
elements.editTempMailModal.classList.remove('active');
|
|
||||||
loadTempMailServices();
|
|
||||||
loadStats();
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('更新失败: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义域名管理 -->
|
<!-- 自定义域名管理(含 MoeMail / TempMail) -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3>🔗 自定义域名服务</h3>
|
<h3>🔗 自定义域名服务</h3>
|
||||||
@@ -105,7 +105,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th style="width: 40px;"><input type="checkbox" id="select-all-custom"></th>
|
<th style="width: 40px;"><input type="checkbox" id="select-all-custom"></th>
|
||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
<th style="width: 200px;">API 地址</th>
|
<th style="width: 90px;">类型</th>
|
||||||
|
<th style="width: 200px;">地址</th>
|
||||||
<th style="width: 100px;">状态</th>
|
<th style="width: 100px;">状态</th>
|
||||||
<th style="width: 80px;">优先级</th>
|
<th style="width: 80px;">优先级</th>
|
||||||
<th style="width: 160px;">最后使用</th>
|
<th style="width: 160px;">最后使用</th>
|
||||||
@@ -114,41 +115,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="custom-services-table">
|
<tbody id="custom-services-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7">
|
<td colspan="8">
|
||||||
<div class="empty-state">
|
|
||||||
<div class="skeleton skeleton-text"></div>
|
|
||||||
<div class="skeleton skeleton-text" style="width: 80%;"></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Temp-Mail 服务管理 -->
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>📮 Temp-Mail 服务(自部署)</h3>
|
|
||||||
<button class="btn btn-primary btn-sm" id="add-tempmail-btn">➕ 添加服务</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body" style="padding: 0;">
|
|
||||||
<div class="table-container">
|
|
||||||
<table class="data-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>名称</th>
|
|
||||||
<th style="width: 200px;">Worker 地址</th>
|
|
||||||
<th style="width: 120px;">邮箱域名</th>
|
|
||||||
<th style="width: 100px;">状态</th>
|
|
||||||
<th style="width: 80px;">优先级</th>
|
|
||||||
<th style="width: 160px;">操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tempmail-services-table">
|
|
||||||
<tr>
|
|
||||||
<td colspan="6">
|
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="skeleton skeleton-text"></div>
|
<div class="skeleton skeleton-text"></div>
|
||||||
<div class="skeleton skeleton-text" style="width: 80%;"></div>
|
<div class="skeleton skeleton-text" style="width: 80%;"></div>
|
||||||
@@ -223,7 +190,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加自定义域名模态框 -->
|
<!-- 添加自定义域名服务模态框(含类型选择) -->
|
||||||
<div class="modal" id="add-custom-modal">
|
<div class="modal" id="add-custom-modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -237,16 +204,41 @@
|
|||||||
<input type="text" id="custom-name" name="name" required placeholder="例如:我的域名邮箱">
|
<input type="text" id="custom-name" name="name" required placeholder="例如:我的域名邮箱">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="custom-api-url">API 地址</label>
|
<label for="custom-sub-type">服务类型</label>
|
||||||
<input type="text" id="custom-api-url" name="api_url" required placeholder="https://api.example.com">
|
<select id="custom-sub-type" name="sub_type">
|
||||||
|
<option value="moemail">MoeMail(自定义域名 API)</option>
|
||||||
|
<option value="tempmail">TempMail(自部署 Cloudflare Worker)</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- MoeMail 字段 -->
|
||||||
<label for="custom-api-key">API 密钥 (可选)</label>
|
<div id="add-moemail-fields">
|
||||||
<input type="text" id="custom-api-key" name="api_key" placeholder="API Key">
|
<div class="form-group">
|
||||||
|
<label for="custom-api-url">API 地址</label>
|
||||||
|
<input type="text" id="custom-api-url" name="api_url" placeholder="https://api.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="custom-api-key">API 密钥 (可选)</label>
|
||||||
|
<input type="text" id="custom-api-key" name="api_key" placeholder="API Key">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="custom-domain">邮箱域名</label>
|
||||||
|
<input type="text" id="custom-domain" name="domain" placeholder="example.com">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- TempMail 字段 -->
|
||||||
<label for="custom-domain">邮箱域名</label>
|
<div id="add-tempmail-fields" style="display:none;">
|
||||||
<input type="text" id="custom-domain" name="domain" placeholder="example.com">
|
<div class="form-group">
|
||||||
|
<label for="custom-tm-base-url">Worker 地址</label>
|
||||||
|
<input type="text" id="custom-tm-base-url" name="tm_base_url" placeholder="https://mail.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="custom-tm-admin-password">Admin 密码</label>
|
||||||
|
<input type="password" id="custom-tm-admin-password" name="tm_admin_password" placeholder="x-admin-auth 密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="custom-tm-domain">邮箱域名</label>
|
||||||
|
<input type="text" id="custom-tm-domain" name="tm_domain" placeholder="example.com">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -269,7 +261,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 编辑自定义域名模态框 -->
|
|
||||||
|
<!-- 编辑自定义域名服务模态框(含类型选择) -->
|
||||||
<div class="modal" id="edit-custom-modal">
|
<div class="modal" id="edit-custom-modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -279,22 +272,46 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="edit-custom-form">
|
<form id="edit-custom-form">
|
||||||
<input type="hidden" id="edit-custom-id" name="id">
|
<input type="hidden" id="edit-custom-id" name="id">
|
||||||
|
<input type="hidden" id="edit-custom-sub-type-hidden" name="sub_type">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-custom-name">服务名称</label>
|
<label for="edit-custom-name">服务名称</label>
|
||||||
<input type="text" id="edit-custom-name" name="name" required placeholder="例如:我的域名邮箱">
|
<input type="text" id="edit-custom-name" name="name" required placeholder="例如:我的域名邮箱">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-custom-api-url">API 地址</label>
|
<label>服务类型</label>
|
||||||
<input type="text" id="edit-custom-api-url" name="api_url" required placeholder="https://api.example.com">
|
<div id="edit-custom-type-badge" style="padding: 6px 0; color: var(--text-muted); font-size: 0.875rem;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- MoeMail 字段 -->
|
||||||
<label for="edit-custom-api-key">API 密钥</label>
|
<div id="edit-moemail-fields">
|
||||||
<input type="text" id="edit-custom-api-key" name="api_key" placeholder="API Key">
|
<div class="form-group">
|
||||||
<small style="color: var(--text-muted);">留空则保持原值不变</small>
|
<label for="edit-custom-api-url">API 地址</label>
|
||||||
|
<input type="text" id="edit-custom-api-url" name="api_url" placeholder="https://api.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-custom-api-key">API 密钥</label>
|
||||||
|
<input type="text" id="edit-custom-api-key" name="api_key" placeholder="API Key">
|
||||||
|
<small style="color: var(--text-muted);">留空则保持原值不变</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-custom-domain">邮箱域名</label>
|
||||||
|
<input type="text" id="edit-custom-domain" name="domain" placeholder="example.com">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<!-- TempMail 字段 -->
|
||||||
<label for="edit-custom-domain">邮箱域名</label>
|
<div id="edit-tempmail-fields" style="display:none;">
|
||||||
<input type="text" id="edit-custom-domain" name="domain" placeholder="example.com">
|
<div class="form-group">
|
||||||
|
<label for="edit-tm-base-url">Worker 地址</label>
|
||||||
|
<input type="text" id="edit-tm-base-url" name="tm_base_url" placeholder="https://mail.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-tm-admin-password">Admin 密码</label>
|
||||||
|
<input type="password" id="edit-tm-admin-password" name="tm_admin_password" placeholder="留空则不修改">
|
||||||
|
<small style="color: var(--text-muted);">留空则保持原值不变</small>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-tm-domain">邮箱域名</label>
|
||||||
|
<input type="text" id="edit-tm-domain" name="tm_domain" placeholder="example.com">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -366,100 +383,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加 Temp-Mail 服务模态框 -->
|
|
||||||
<div class="modal" id="add-tempmail-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>➕ 添加 Temp-Mail 服务</h3>
|
|
||||||
<button class="modal-close" id="close-add-tempmail-modal">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="add-tempmail-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tm-name">服务名称</label>
|
|
||||||
<input type="text" id="tm-name" name="name" required placeholder="例如:我的临时邮箱">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tm-base-url">Worker 地址</label>
|
|
||||||
<input type="text" id="tm-base-url" name="base_url" required placeholder="https://mail.example.com">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tm-admin-password">Admin 密码</label>
|
|
||||||
<input type="password" id="tm-admin-password" name="admin_password" required placeholder="x-admin-auth 密码">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tm-domain">邮箱域名</label>
|
|
||||||
<input type="text" id="tm-domain" name="domain" required placeholder="example.com">
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tm-priority">优先级</label>
|
|
||||||
<input type="number" id="tm-priority" name="priority" value="0" min="0">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="tm-enabled" name="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="cancel-add-tempmail">取消</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 编辑 Temp-Mail 服务模态框 -->
|
<script src="/static/js/utils.js"></script>
|
||||||
<div class="modal" id="edit-tempmail-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>✏️ 编辑 Temp-Mail 服务</h3>
|
|
||||||
<button class="modal-close" id="close-edit-tempmail-modal">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="edit-tempmail-form">
|
|
||||||
<input type="hidden" id="edit-tm-id">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-tm-name">服务名称</label>
|
|
||||||
<input type="text" id="edit-tm-name" name="name" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-tm-base-url">Worker 地址</label>
|
|
||||||
<input type="text" id="edit-tm-base-url" name="base_url" required placeholder="https://mail.example.com">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-tm-admin-password">Admin 密码(留空保持不变)</label>
|
|
||||||
<input type="password" id="edit-tm-admin-password" name="admin_password" placeholder="留空则不修改">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-tm-domain">邮箱域名</label>
|
|
||||||
<input type="text" id="edit-tm-domain" name="domain" required placeholder="example.com">
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-tm-priority">优先级</label>
|
|
||||||
<input type="number" id="edit-tm-priority" name="priority" value="0" min="0">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="edit-tm-enabled" name="enabled">
|
|
||||||
启用服务
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-actions">
|
|
||||||
<button type="submit" class="btn btn-primary">保存</button>
|
|
||||||
<button type="button" class="btn btn-secondary" id="cancel-edit-tempmail">取消</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/static/js/utils.js"></script>
|
|
||||||
<script src="/static/js/email_services.js"></script>
|
<script src="/static/js/email_services.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user