Files
sap-erp/public/mail-logs.html
T
John 69b4e68baf
SAP-ERP Portal CI/CD / build (push) Failing after 5m20s
first commit
2026-09-23 17:31:02 +05:30

147 lines
9.5 KiB
HTML
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.
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/auth-guard.js?v=4"></script>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Mail Log — SAP B1</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Bebas+Neue&display=swap" rel="stylesheet"/>
<style>
:root{--ink:#080e18;--ink2:#0d1626;--purple:#9f5afd;--blue:#1a6fff;--amber:#f5a623;--green:#0dbb8a;--red:#e53e3e;--teal:#06b6d4;--border:#1c2e48;--border2:#243650;--text:#c8d6e8;--text2:#7a9ab8;--text3:#3d5470;--surface:#0f1d30;--surface2:#142338;--surface3:#0b1520;--radius:10px;--font:'Space Grotesk',sans-serif;--mono:'JetBrains Mono',monospace;--display:'Bebas Neue',sans-serif;--shadow:0 8px 32px rgba(0,0,0,.5)}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--font);background:var(--ink);color:var(--text);min-height:100vh;-webkit-font-smoothing:antialiased}
nav{position:sticky;top:0;z-index:200;background:rgba(8,14,24,.92);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:0 12px;height:50px;display:flex;align-items:center;justify-content:space-between;gap:6px}
.nav-logo{background:var(--blue);color:#fff;font-family:var(--display);font-size:15px;letter-spacing:2px;padding:3px 8px;border-radius:5px}
.nav-links{display:flex;gap:2px;overflow-x:auto;flex:1;justify-content:center}
.nav-link{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:600;color:var(--text2);text-decoration:none;white-space:nowrap}.nav-link.active{color:var(--blue);background:rgba(26,111,255,.1)}
.btn-sm{padding:4px 8px;border-radius:5px;font-size:10px;font-weight:700;border:1px solid var(--border2);color:var(--text3);background:none;cursor:pointer;font-family:var(--font)}.btn-sm:hover{border-color:var(--red);color:var(--red)}
main{padding:12px;max-width:1300px;margin:0 auto}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);margin-bottom:12px;overflow:hidden}
.card-hd{background:var(--surface3);padding:10px 14px;font-size:12px;font-weight:700;display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--border)}
.card-hd .right{margin-left:auto;font-size:11px;color:var(--text3)}
.card-bd{padding:12px 14px}
.fl{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:3px;display:block}
.fi{width:100%;padding:7px 9px;border:1.5px solid var(--border2);border-radius:7px;font-size:12.5px;font-family:var(--font);background:var(--surface2);color:var(--text);outline:none;height:34px}
.fi:focus{border-color:var(--blue)}
select.fi{cursor:pointer}
.filters{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end}
.filters>div{min-width:120px}
.btn{padding:8px 15px;border-radius:8px;border:none;font-family:var(--font);font-size:12px;font-weight:700;cursor:pointer;height:34px}
.b-primary{background:var(--blue);color:#fff}.b-cancel{background:var(--surface2);color:var(--text2);border:1px solid var(--border2)}
.lst{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}
.lst th{text-align:left;padding:8px 10px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:var(--text3);border-bottom:2px solid var(--border2);white-space:nowrap}
.lst td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:top}
.mono{font-family:var(--mono)}
.pill{font-size:9px;font-weight:800;padding:2px 7px;border-radius:20px;text-transform:uppercase;letter-spacing:.3px;white-space:nowrap}
.p-sent{background:rgba(13,187,138,.14);color:#0aa457}
.p-failed{background:rgba(229,62,62,.14);color:#e06666}
.p-skipped{background:rgba(245,166,35,.14);color:#c78a1a}
.err-msg{font-size:10.5px;color:var(--red);margin-top:2px}
.empty-msg{text-align:center;padding:26px;color:var(--text3);font-size:12px}
.pager{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px}
.count{font-size:11px;font-weight:700;color:var(--blue);background:rgba(26,111,255,.1);padding:3px 11px;border-radius:20px}
.sp{width:14px;height:14px;border:2px solid rgba(26,111,255,.25);border-top-color:var(--blue);border-radius:50%;animation:spin .6s linear infinite;display:inline-block}
@keyframes spin{to{transform:rotate(360deg)}}
.toast-c{position:fixed;top:56px;right:10px;z-index:9999;display:flex;flex-direction:column;gap:6px}
.toast{padding:9px 13px;border-radius:8px;font-size:12px;font-weight:600;box-shadow:var(--shadow)}
.t-err{background:var(--red);color:#fff}
</style>
<link rel="stylesheet" href="/theme.css?v=3"/>
</head>
<body>
<div class="toast-c" id="toast-c"></div>
<nav>
<span class="nav-logo">SAP</span>
<div class="nav-links"><a href="/" class="nav-link">Home</a><a href="/mail-logs" class="nav-link active">Mail Log</a></div>
<button class="btn-sm" id="btn-logout">Logout</button>
</nav>
<main id="app"></main>
<script>
'use strict';
let _tok=null;const BE='/api';
try{_tok=sessionStorage.getItem('portal_token');}catch(_e){}
if(!_tok)location.href='/';
document.getElementById('btn-logout').onclick=()=>{sessionStorage.clear();location.href='/';};
const _f={from:'',to:'',status:'',q:''};
let _page=0,_pageSize=50,_total=0,_rows=[],_loading=false;
function esc(v){return(v==null?'':String(v)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function q(s){return document.querySelector(s);}
function toast(m){const tc=q('#toast-c');const e=document.createElement('div');e.className='toast t-err';e.textContent=m;tc.appendChild(e);setTimeout(()=>e.remove(),4500);}
function fmt(iso){if(!iso)return '—';const d=new Date(iso);if(isNaN(d))return '—';const p=n=>String(n).padStart(2,'0');return `${p(d.getDate())}-${['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()]}-${d.getFullYear()} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;}
function pcls(s){return {SENT:'p-sent',FAILED:'p-failed',SKIPPED:'p-skipped'}[s]||'p-skipped';}
async function api(p){
const r=await fetch(BE+p,{headers:{Authorization:'Bearer '+_tok}});
if(r.status===401){location.href='/';throw new Error('Session expired');}
const d=await r.json();
if(!r.ok||d.success===false)throw new Error(d.message||('HTTP '+r.status));
return d;
}
async function load(reset){
if(reset)_page=0;
_loading=true;render();
const qs=new URLSearchParams();
Object.entries(_f).forEach(([k,v])=>{if(v)qs.set(k,v);});
qs.set('top',_pageSize);qs.set('skip',_page*_pageSize);
try{const r=await api('/mail-logs?'+qs.toString());_rows=r.data||[];_total=r.total||0;}
catch(e){toast(e.message);_rows=[];_total=0;}
_loading=false;render();
}
function render(){
const app=document.getElementById('app');
const start=_total?_page*_pageSize+1:0, end=Math.min(_total,(_page+1)*_pageSize);
app.innerHTML=`
<div class="card"><div class="card-hd"><span>✉ Mail Log</span><span class="right">${_loading?'':`${_total.toLocaleString()} email${_total===1?'':'s'}`}</span></div>
<div class="card-bd">
<div class="filters">
<div><label class="fl">From</label><input class="fi" type="date" id="f-from" value="${_f.from}"/></div>
<div><label class="fl">To</label><input class="fi" type="date" id="f-to" value="${_f.to}"/></div>
<div><label class="fl">Status</label><select class="fi" id="f-status"><option value="">All</option><option value="SENT" ${_f.status==='SENT'?'selected':''}>Sent</option><option value="FAILED" ${_f.status==='FAILED'?'selected':''}>Failed</option><option value="SKIPPED" ${_f.status==='SKIPPED'?'selected':''}>Skipped</option></select></div>
<div style="min-width:200px;flex:1"><label class="fl">Search</label><input class="fi" id="f-q" placeholder="recipient / subject…" value="${esc(_f.q)}"/></div>
<button class="btn b-primary" id="f-go">Apply</button>
<button class="btn b-cancel" id="f-clear">Clear</button>
</div>
<div style="overflow-x:auto;margin-top:12px">
${_loading?`<div class="empty-msg"><span class="sp"></span> Loading…</div>`:
!_rows.length?`<div class="empty-msg">No emails match these filters.</div>`:
`<table class="lst"><thead><tr><th>Sent At</th><th>To</th><th>Subject</th><th>Status</th></tr></thead>
<tbody>${_rows.map(r=>rowHtml(r)).join('')}</tbody></table>
<div class="pager">
<button class="btn b-cancel" id="p-prev" ${_page===0?'disabled':''}>‹ Prev</button>
<span class="count">${start.toLocaleString()}–${end.toLocaleString()} of ${_total.toLocaleString()}</span>
<button class="btn b-cancel" id="p-next" ${end>=_total?'disabled':''}>Next ›</button>
</div>`}
</div>
</div></div>`;
wire();
}
function rowHtml(r){
return `<tr>
<td class="mono" style="white-space:nowrap">${esc(fmt(r.at))}</td>
<td>${esc(r.to||'—')}</td>
<td>${esc(r.subject||'—')}</td>
<td><span class="pill ${pcls(r.status)}">${esc(r.status||'—')}</span>${r.error?`<div class="err-msg">${esc(r.error)}</div>`:''}</td>
</tr>`;
}
function wire(){
q('#f-go')&&(q('#f-go').onclick=()=>{
_f.from=q('#f-from').value;_f.to=q('#f-to').value;_f.status=q('#f-status').value;_f.q=q('#f-q').value.trim();
load(true);
});
q('#f-clear')&&(q('#f-clear').onclick=()=>{Object.keys(_f).forEach(k=>_f[k]='');load(true);});
q('#f-q')&&(q('#f-q').onkeydown=e=>{if(e.key==='Enter')q('#f-go').click();});
q('#p-prev')&&(q('#p-prev').onclick=()=>{if(_page>0){_page--;load();}});
q('#p-next')&&(q('#p-next').onclick=()=>{if((_page+1)*_pageSize<_total){_page++;load();}});
}
load();
</script>
<script src="/sidebar.js?v=25"></script>
</body>
</html>