// Admin views: Dashboard, Domains, Keys, Firehose, Abuse, Health, Audit, Settings
// Wired to backend via window.tmApi (lib/tm-browser.js)
// ================= DASHBOARD =================
const AdminDashboard = ({ toast }) => {
const [data, setData] = React.useState(null);
const [err, setErr] = React.useState(null);
const [range, setRange] = React.useState('24h'); // 1h | 24h | 7d | 30d
React.useEffect(() => {
let cancel = false;
setData(null);
tmApi.admin.overview({ range }).then((d) => { if (!cancel) setData(d); }).catch((e) => !cancel && setErr(e));
return () => { cancel = true; };
}, [range]);
if (err) return
| DOMAIN | STATUS | INBOXES | EMAILS 24H | SPAM SCORE | STORAGE | BADGES | ACTIONS |
|---|---|---|---|---|---|---|---|
|
{d.premium &&
|
|
{d.inboxes.toLocaleString()} | {d.emails24h.toLocaleString()} | 0.1 ? '#DC2626' : d.spamScore > 0.05 ? '#F59E0B' : '#10B981' }}>{d.spamScore.toFixed(2)} | {d.storage} |
{d.badges.map((b) =>
|
|
| DOMAIN | STATUS | OWNER KEY | EMAILS | RETENTION | ADDED | ACTIONS |
|---|---|---|---|---|---|---|
| @{d.domain} | {d.owner.codePreview} {d.owner.label ? · {d.owner.label} : null} | {d.emails.toLocaleString()} | {d.retention}h | {d.added} |
|
{value}
SET THESE AT THE REGISTRAR — MAIL WON'T FLOW UNTIL THEN
Open the DNS panel for {domain} at whichever registrar holds it (Namecheap, Cloudflare, Route 53, etc.) and add the records below. The relay will accept mail to anything@{domain} as soon as MX propagates (~5-30 min).
dig {domain} MX +short
dig {domain} TXT +short
| CODE | LABEL | STATUS | CREATED | EXPIRES | INBOXES | EMAILS | LAST SEEN | ACTIONS |
|---|---|---|---|---|---|---|---|---|
|
{k.code}
{k.hasFullCode && (
)}
|
{k.label || —} |
|
{k.created} | {k.expires} | {k.inboxes} | {k.emails} | {k.lastSeen} |
{menuOpenId === k.id && (
e.stopPropagation()}
style={{
position: 'absolute', top: '100%', right: 16, marginTop: 4, zIndex: 60,
background: '#0f0f0f', border: '1px solid #262626', borderRadius: 6,
boxShadow: '0 8px 24px rgba(0,0,0,0.6)', minWidth: 220, overflow: 'hidden',
animation: 'slideDown 120ms ease',
textAlign: 'left',
}}
>
)}
|
Copy this access code now. It's argon2-hashed in the database — once you close this dialog there is no way to retrieve it. If you lose it, you'll need to revoke this key and generate a new one.
{code}
The keys list will only show the redacted preview {code.slice(0,4)}-XXXX-XXXX-{code.slice(-4)} from now on.
| KIND | PATTERN | HITS | REASON | ADDED | ACTIONS |
|---|---|---|---|---|---|
| {b.kind.toUpperCase()} | {b.pattern} | {b.hits} | {b.reason || —} | {b.added} |
| TIMESTAMP | ACTOR | ACTION | TARGET | META |
|---|---|---|---|---|
| {a.ts} | {a.actor} | {a.action} | {a.target} | {a.meta} |
{selected.subject}
{selected.hasCode ? `Your verification code is ${selected.code}.` : 'Full email body would render here, sanitized and sandboxed.'}