taolib.testing.email_service.server.app#

FastAPI 应用工厂模块。

Attributes#

Functions#

_build_providers(→ list[tuple])

根据配置构建提供商列表。

lifespan(→ collections.abc.AsyncGenerator[None])

应用生命周期管理。

create_app(→ fastapi.FastAPI)

创建 FastAPI 应用。

Module Contents#

taolib.testing.email_service.server.app._build_providers() list[tuple]#

根据配置构建提供商列表。

async taolib.testing.email_service.server.app.lifespan(app: fastapi.FastAPI) collections.abc.AsyncGenerator[None]#

应用生命周期管理。

taolib.testing.email_service.server.app.create_app() fastapi.FastAPI#

创建 FastAPI 应用。

taolib.testing.email_service.server.app._DASHBOARD_HTML = Multiline-String#
Show Value
"""<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Service Dashboard</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#f0f2f5;color:#1f2937}
.header{background:#1e40af;color:#fff;padding:20px 32px;display:flex;justify-content:space-between;align-items:center}
.header h1{font-size:22px}
.container{max-width:1200px;margin:24px auto;padding:0 16px}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:24px}
.card{background:#fff;border-radius:8px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,.1)}
.card .label{font-size:13px;color:#6b7280;margin-bottom:4px}
.card .value{font-size:28px;font-weight:700}
.card .value.green{color:#059669}.card .value.red{color:#dc2626}
.card .value.blue{color:#2563eb}.card .value.amber{color:#d97706}
.section{background:#fff;border-radius:8px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,.1);margin-bottom:24px}
.section h2{font-size:16px;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid #e5e7eb}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{padding:10px 12px;text-align:left;border-bottom:1px solid #f3f4f6}
th{font-weight:600;color:#6b7280;font-size:12px;text-transform:uppercase}
.badge{display:inline-block;padding:2px 8px;border-radius:12px;font-size:12px;font-weight:500}
.badge.sent{background:#dbeafe;color:#1e40af}.badge.delivered{background:#d1fae5;color:#065f46}
.badge.opened{background:#fef3c7;color:#92400e}.badge.failed{background:#fee2e2;color:#991b1b}
.badge.queued{background:#f3f4f6;color:#4b5563}.badge.bounced{background:#fce7f3;color:#9d174d}
.badge.healthy{background:#d1fae5;color:#065f46}.badge.unhealthy{background:#fee2e2;color:#991b1b}
.refresh{font-size:12px;color:rgba(255,255,255,.7)}
</style>
</head>
<body>
<div class="header"><h1>Email Service Dashboard</h1><span class="refresh" id="lastUpdate"></span></div>
<div class="container">
  <div class="cards" id="statsCards"></div>
  <div class="section"><h2>Provider Status</h2><div id="providerStatus">Loading...</div></div>
  <div class="section"><h2>Queue Status</h2><div id="queueStatus">Loading...</div></div>
  <div class="section"><h2>Recent Emails</h2><table><thead><tr>
    <th>ID</th><th>Subject</th><th>Recipients</th><th>Status</th><th>Created</th>
  </tr></thead><tbody id="emailsTable"></tbody></table></div>
</div>
<script>
const API='/api/v1';
function badge(status){return `<span class="badge ${status}">${status}</span>`}
function shortId(id){return id?id.substring(0,8)+'...':'—'}
function fmtDate(d){return d?new Date(d).toLocaleString('zh-CN'):'—'}
async function fetchJSON(url){try{const r=await fetch(url);return await r.json()}catch{return null}}
async function refresh(){
  document.getElementById('lastUpdate').textContent='Updated: '+new Date().toLocaleString('zh-CN');
  const [health,emails,analytics]=await Promise.all([
    fetchJSON(API+'/health'),fetchJSON(API+'/emails?limit=20'),
    fetchJSON(API+'/tracking/analytics?days=7')
  ]);
  if(analytics){
    document.getElementById('statsCards').innerHTML=`
      <div class="card"><div class="label">Total Sent</div><div class="value blue">${analytics.total_sent||0}</div></div>
      <div class="card"><div class="label">Delivery Rate</div><div class="value green">${(analytics.delivery_rate||0).toFixed(1)}%</div></div>
      <div class="card"><div class="label">Open Rate</div><div class="value amber">${(analytics.open_rate||0).toFixed(1)}%</div></div>
      <div class="card"><div class="label">Click Rate</div><div class="value blue">${(analytics.click_rate||0).toFixed(1)}%</div></div>
      <div class="card"><div class="label">Bounce Rate</div><div class="value red">${(analytics.bounce_rate||0).toFixed(1)}%</div></div>`;
  }
  if(health){
    const ps=(health.providers||[]).map(p=>`<span class="badge ${p.is_healthy?'healthy':'unhealthy'}">${p.provider_name}: ${p.is_healthy?'Healthy':'Down'}</span> `).join('');
    document.getElementById('providerStatus').innerHTML=ps||'No providers configured';
    const q=health.queue||{};
    document.getElementById('queueStatus').innerHTML=`High: <b>${q.high||0}</b> | Normal: <b>${q.normal||0}</b> | Low: <b>${q.low||0}</b>`;
  }
  if(Array.isArray(emails)){
    document.getElementById('emailsTable').innerHTML=emails.map(e=>`<tr>
      <td>${shortId(e.id||e._id)}</td><td>${e.subject||'—'}</td>
      <td>${(e.recipients||[]).map(r=>r.email||r).join(', ')}</td>
      <td>${badge(e.status)}</td><td>${fmtDate(e.created_at)}</td></tr>`).join('');
  }
}
refresh();setInterval(refresh,30000);
</script>
</body></html>"""