dpl/resources/js/admin/mail-flow.js

81 lines
9.5 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Standalone settings panel, using the existing authenticated admin API.
const host = document.createElement('div');
host.id = 'mail-flow-tools';
const style = document.createElement('style');
style.textContent = `#mail-flow-tools .entry{position:fixed;right:24px;bottom:72px;z-index:40;padding:9px 14px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:#0f172a;cursor:pointer}#mail-flow-dialog{width:min(760px,calc(100vw - 40px));max-height:85vh;overflow:auto;border:1px solid #cbd5e1;border-radius:12px;padding:24px;background:#fff;color:#0f172a}#mail-flow-dialog::backdrop{background:#0007}#mail-flow-dialog fieldset{border:1px solid #e2e8f0;border-radius:8px;margin:14px 0;padding:12px}#mail-flow-dialog label{display:block;margin:10px 0;font-size:14px}#mail-flow-dialog select,#mail-flow-dialog input:not([type=checkbox]),#mail-flow-dialog textarea{display:block;width:100%;padding:7px;border:1px solid #cbd5e1;border-radius:5px;box-sizing:border-box}#mail-flow-dialog button{padding:8px 14px;border:1px solid #cbd5e1;border-radius:6px;cursor:pointer}#mail-flow-dialog pre{white-space:pre-wrap;font-size:12px}#mail-flow-dialog p{font-size:13px;line-height:1.6}`;
host.append(style);
const entry = document.createElement('button'); entry.className='entry'; entry.textContent='邮件渠道与自动回复'; entry.type='button'; entry.hidden=true; host.append(entry);
const dialog=document.createElement('dialog'); dialog.id='mail-flow-dialog'; host.append(dialog); document.body.append(host);
const visibility=()=>{entry.hidden=!location.hash.startsWith('#/config') || !document.querySelector('#root h2'); if(entry.hidden && dialog.open) dialog.close();};
window.addEventListener('hashchange',visibility);
new MutationObserver(visibility).observe(document.getElementById('root'),{childList:true,subtree:true}); visibility();
async function api(action, payload) {
let token; try { token=JSON.parse(localStorage.getItem('XBOARD_ACCESS_TOKEN')||'null'); } catch { throw new Error('请重新登录后台'); }
if(!token?.value || (token.expire!=null && token.expire<=Date.now())) throw new Error('请重新登录后台');
const base=new URL(window.settings?.base_url||'/',location.origin);
if(base.origin!==location.origin) throw new Error('后台接口必须同源');
const url=base.pathname.replace(/\/$/,'')+'/api/v2/'+encodeURIComponent(window.settings.secure_path)+'/config/'+action;
const response=await fetch(url,{method:payload?'POST':'GET',credentials:'same-origin',headers:{Authorization:token.value,'Content-Type':'application/json','Content-Language':'zh-CN'},...(payload?{body:JSON.stringify(payload)}:{})});
const data=await response.json();
if(!response.ok || data.status==='fail' || data.data===false) throw new Error(data.message||'请求失败,请检查输入');
return data.data;
}
const groups=[
['发送渠道', [['enabled','启用邮件记录与分流','checkbox'],['resend_enabled','验证码、登录链接和工单通知优先 Resend','checkbox'],['resend_key','Resend API 密钥','password'],['resend_from','Resend 发件地址','email'],['resend_webhook_secret','Resend 回调签名密钥','password'],['reply_to','统一回复地址(常规邮箱)','email'],['daily_limit','日额度上限','number'],['monthly_limit','月额度上限','number']]],
['常规邮箱收件与自动回复', [['inbound_enabled','启用收件','checkbox'],['imap_host','IMAP 主机','text'],['imap_port','IMAP TLS 端口','number'],['imap_username','IMAP 用户名','text'],['imap_password','IMAP 密码','password'],['imap_folder','收件文件夹','text'],['auto_reply_enabled','自动确认收件(按发件地址限频)','checkbox'],['auto_reply_subject','自动回复标题','text'],['auto_reply_text','自动回复内容','textarea']]],
['每天八点汇报', [['report_enabled','启用 master 汇报','checkbox'],['master_url','Master 基础地址(HTTPS)','url'],['master_bot_code','Master 机器人代号','text'],['master_chat_code','Master 接收会话代号','text']]],
];
entry.addEventListener('click',async()=>{
dialog.textContent='正在读取…'; dialog.showModal(); entry.disabled=true;
try {
const data=await api('mailFlowStatus'); dialog.replaceChildren();
const heading=document.createElement('h2'); heading.textContent='邮件渠道与自动回复'; dialog.append(heading);
const note=document.createElement('p'); note.textContent='群发、流量和到期提醒仍用原常规邮箱。Resend 额度不足时回退;收件固定用常规邮箱。密钥留空保留。自动回复仅处理启用之后的新邮件,并限制每个发件地址北京时间每天一次,无需绑定账户。'; dialog.append(note);
const readiness=document.createElement('p'); readiness.textContent=(data.database_ready?'数据库迁移已就绪。':'数据库尚未迁移,暂不可启用。')+(data.imap_available?' IMAP 扩展可用。':' 收件需要安装 PHP IMAP 扩展。'); dialog.append(readiness);
const form=document.createElement('form'); dialog.append(form); const inputs={};
const tierLabel=document.createElement('label'); tierLabel.textContent='自动邮件档位';
const tier=document.createElement('select'); tier.name='automatic_mail_level';
for(const [value,label] of [[1,'一档:保持原有提醒'],[2,'二档:仅套餐到期时提醒'],[3,'三档:关闭自动批量提醒']]) {
const option=document.createElement('option'); option.value=String(value); option.textContent=label; tier.append(option);
}
if(data.automatic_mail_level==null) throw new Error('自动邮件档位暂不可读取,请稍后重试');
tier.value=String(data.automatic_mail_level); tierLabel.append(tier); form.append(tierLabel);
const tierNote=document.createElement('p'); tierNote.textContent='人工群发、验证码、登录、工单回复和收件自动回复均不受档位影响。二档保留原提醒开关与用户到期提醒偏好,从启用二档之后的新到期开始,每分钟检查,不补发此前历史记录;改档取消禁发任务,恢复档位不补发已取消邮件。'; form.append(tierNote);
for(const [title, fields] of groups){
const fieldset=document.createElement('fieldset'); const legend=document.createElement('legend'); legend.textContent=title; fieldset.append(legend);
for(const [key,label,type] of fields){
const wrap=document.createElement('label'); wrap.textContent=label;
const input=document.createElement(type==='textarea'?'textarea':'input');
if(type!=='textarea') input.type=type;
if(type==='checkbox') input.checked=!!data.settings[key];
else if(type==='password') {input.autocomplete='new-password'; input.placeholder=data.settings[key+'_configured']?'已配置,留空保留':'尚未配置';}
else input.value=data.settings[key]??'';
if(type==='number'){input.step='1';input.min='1';if(key==='daily_limit') input.max='100';if(key==='monthly_limit') input.max='3000';if(key==='imap_port') input.max='65535';}
inputs[key]=input; wrap.append(input); fieldset.append(wrap);
} form.append(fieldset);
}
const message=document.createElement('p'); message.setAttribute('role','status'); form.append(message);
const save=document.createElement('button'); save.type='submit'; save.textContent='保存'; form.append(save);
const close=document.createElement('button'); close.type='button'; close.textContent='关闭';close.addEventListener('click',()=>dialog.close());form.append(close);
const history=document.createElement('pre');history.textContent='最近邮件:\n'+data.messages.map(m=>`${m.created_at} · ${m.purpose} · ${m.channel||'待选择'} · ${m.status}${m.fallback_reason?' · '+m.fallback_reason:''}`).join('\n');dialog.append(history);
for(const incoming of data.messages.filter(m=>m.direction==='in')) {
const view=document.createElement('button'); view.type='button'; view.textContent='查看来信:'+incoming.email;
const content=document.createElement('pre'); dialog.append(view,content);
view.addEventListener('click',async()=>{view.disabled=true;try{const mail=await api('mailFlowIncoming?id='+encodeURIComponent(incoming.id));content.textContent=mail.subject+'\n\n'+mail.text;}catch(error){content.textContent=error.message;}finally{view.disabled=false;}});
}
const reports=document.createElement('pre'); reports.textContent='最近汇报:\n'+data.reports.map(r=>r.report_date+' · '+r.status).join('\n');dialog.append(reports);
form.addEventListener('submit',async event=>{
event.preventDefault();if(!form.reportValidity()) return;
if((inputs.enabled.checked || Number(tier.value)===2) && !data.database_ready){message.textContent='请先执行数据库迁移。';return;}
if(inputs.inbound_enabled.checked && !data.imap_available){message.textContent='请先安装 PHP IMAP 扩展。';return;}
const values={};for(const [key,input] of Object.entries(inputs)){
if(input.type==='checkbox') values[key]=input.checked;
else if(input.type==='password' && !input.value) continue;
else values[key]=input.type==='number'?Number(input.value):input.value;
}
save.disabled=true;try{await api('save',{mail_flow:values,automatic_mail_level:Number(tier.value)});message.textContent='配置已保存。实际收发结果请查看邮件状态。';}catch(error){message.textContent=error.message;}finally{save.disabled=false;}
});
} catch(error) {dialog.textContent=error.message;const close=document.createElement('button');close.textContent='关闭';close.addEventListener('click',()=>dialog.close());dialog.append(close);}
finally {entry.disabled=false;}
});