titan-bot / admin.html
M-hv1's picture
Update admin.html
827e14f verified
Raw
History Blame Contribute Delete
45.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Titan Nexus β€” Command Center</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Tajawal:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg:#050810; --surface:#0b0f1a; --surface2:#0f1525;
--border:rgba(99,179,237,.12); --border2:rgba(99,179,237,.25);
--accent:#63b3ed; --accent2:#4fd1c5; --gold:#f6c90e;
--danger:#fc5c65; --success:#26de81; --warn:#fd9644;
--text:#e2ecff; --muted:#5a7090;
--mono:'Space Mono',monospace; --sans:'Tajawal',sans-serif;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{background:var(--bg);color:var(--text);font-family:var(--sans);min-height:100vh;overflow-x:hidden}
body::before{content:'';position:fixed;inset:0;background-image:linear-gradient(rgba(99,179,237,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(99,179,237,.03) 1px,transparent 1px);background-size:40px 40px;pointer-events:none;z-index:0}
/* LOCK */
#lockScreen{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:100;background:var(--bg);flex-direction:column}
.lock-card{background:var(--surface);border:1px solid var(--border2);border-radius:20px;padding:44px 40px 36px;width:100%;max-width:400px;box-shadow:0 0 80px rgba(99,179,237,.08),0 40px 80px rgba(0,0,0,.6);position:relative;overflow:hidden}
.lock-card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent),var(--accent2),transparent)}
.lock-logo{font-family:var(--mono);font-size:11px;letter-spacing:4px;color:var(--muted);text-align:center;margin-bottom:32px;text-transform:uppercase}
.lock-icon{font-size:48px;text-align:center;display:block;margin-bottom:8px;filter:drop-shadow(0 0 20px rgba(246,201,14,.4))}
.lock-title{font-family:var(--mono);font-size:18px;color:var(--accent);text-align:center;margin-bottom:4px;letter-spacing:1px}
.lock-sub{font-size:13px;color:var(--muted);text-align:center;margin-bottom:28px}
.lock-input{width:100%;background:var(--bg);border:1px solid var(--border2);border-radius:10px;padding:14px 16px;color:var(--text);font-family:var(--mono);font-size:14px;letter-spacing:2px;margin-bottom:14px;transition:.2s;text-align:center;outline:none}
.lock-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,179,237,.12)}
.lock-btn{width:100%;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#000;font-family:var(--mono);font-weight:700;font-size:13px;letter-spacing:2px;border:none;border-radius:10px;padding:14px;cursor:pointer;transition:.2s;text-transform:uppercase}
.lock-btn:hover{opacity:.9;transform:translateY(-1px)}
.lock-err{color:var(--danger);font-size:12px;font-family:var(--mono);text-align:center;margin-top:12px;min-height:18px}
/* MAIN */
#mainApp{display:none;position:relative;z-index:1}
/* TOPBAR */
.topbar{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:50;gap:12px}
.topbar-brand{display:flex;align-items:center;gap:10px}
.topbar-brand .crown{font-size:22px;filter:drop-shadow(0 0 8px rgba(246,201,14,.5))}
.topbar-brand .name{font-family:var(--mono);font-size:13px;letter-spacing:2px;color:var(--accent);text-transform:uppercase}
.topbar-brand .version{font-family:var(--mono);font-size:10px;color:var(--muted);background:var(--bg);border:1px solid var(--border);padding:2px 8px;border-radius:20px}
.topbar-right{display:flex;align-items:center;gap:12px}
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--success);box-shadow:0 0 8px var(--success);animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.topbar-time{font-family:var(--mono);font-size:12px;color:var(--muted)}
.logout-btn{font-family:var(--mono);font-size:11px;color:var(--muted);background:none;border:1px solid var(--border);border-radius:6px;padding:6px 12px;cursor:pointer;transition:.2s;letter-spacing:1px}
.logout-btn:hover{border-color:var(--danger);color:var(--danger)}
/* TABS */
.tabs{display:flex;padding:0 24px;background:var(--surface);border-bottom:1px solid var(--border);overflow-x:auto}
.tab{font-family:var(--mono);font-size:11px;letter-spacing:1.5px;color:var(--muted);padding:14px 20px;cursor:pointer;border-bottom:2px solid transparent;transition:.2s;white-space:nowrap;text-transform:uppercase;background:none;border-left:none;border-right:none;border-top:none}
.tab:hover{color:var(--text)}
.tab.active{color:var(--accent);border-bottom-color:var(--accent)}
/* TAB CONTENT */
.tab-content{display:none;padding:24px;animation:fadeIn .2s ease}
.tab-content.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
/* STAT CARDS */
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:24px}
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:18px 16px;transition:.2s;position:relative;overflow:hidden}
.stat-card::after{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--accent-c,var(--accent));opacity:.6}
.stat-card:hover{border-color:var(--border2);transform:translateY(-2px)}
.stat-label{font-family:var(--mono);font-size:9px;letter-spacing:2px;color:var(--muted);text-transform:uppercase;margin-bottom:10px}
.stat-value{font-family:var(--mono);font-size:22px;color:var(--text);font-weight:700}
.stat-sub{font-size:11px;color:var(--muted);margin-top:4px}
/* SECTION TITLE */
.section-title{font-family:var(--mono);font-size:10px;letter-spacing:3px;color:var(--muted);text-transform:uppercase;margin-bottom:14px;display:flex;align-items:center;gap:10px}
.section-title::after{content:'';flex:1;height:1px;background:var(--border)}
/* TERMINAL */
.terminal-wrap{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden}
.terminal-bar{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:var(--surface2);border-bottom:1px solid var(--border)}
.terminal-dots{display:flex;gap:6px}
.terminal-dots span{width:10px;height:10px;border-radius:50%}
.d1{background:#fc5c65}.d2{background:#fd9644}.d3{background:#26de81}
.terminal-title{font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:1px}
.terminal-status{font-family:var(--mono);font-size:10px;padding:3px 10px;border-radius:20px;letter-spacing:1px}
.terminal-status.ready{background:rgba(38,222,129,.1);color:var(--success);border:1px solid rgba(38,222,129,.2)}
.terminal-status.running{background:rgba(99,179,237,.1);color:var(--accent);border:1px solid rgba(99,179,237,.2)}
.terminal-status.ok{background:rgba(38,222,129,.1);color:var(--success);border:1px solid rgba(38,222,129,.2)}
.terminal-status.error{background:rgba(252,92,101,.1);color:var(--danger);border:1px solid rgba(252,92,101,.2)}
.terminal-output{font-family:var(--mono);font-size:13px;color:#7ec8e3;padding:20px;min-height:220px;max-height:420px;overflow-y:auto;white-space:pre-wrap;word-break:break-word;line-height:1.7}
.terminal-output::-webkit-scrollbar{width:4px}
.terminal-output::-webkit-scrollbar-thumb{background:var(--border2);border-radius:4px}
.line-success{color:var(--success)}.line-error{color:var(--danger)}.line-warn{color:var(--warn)}.line-info{color:var(--accent)}
.cmd-row{display:flex;gap:10px;align-items:stretch;padding:12px 16px;border-top:1px solid var(--border);background:var(--surface2)}
.cmd-prompt{font-family:var(--mono);font-size:14px;color:var(--accent);display:flex;align-items:center;padding:0 4px;user-select:none}
.cmd-input{flex:1;background:transparent;border:none;color:var(--text);font-family:var(--mono);font-size:14px;outline:none;direction:ltr}
.cmd-input::placeholder{color:var(--muted)}
.cmd-run{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#000;font-family:var(--mono);font-size:11px;font-weight:700;letter-spacing:1.5px;border:none;border-radius:8px;padding:10px 18px;cursor:pointer;transition:.2s;text-transform:uppercase;white-space:nowrap}
.cmd-run:hover{opacity:.85}
.cmd-run:disabled{opacity:.4;cursor:not-allowed}
/* QUICK BUTTONS */
.quick-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin-bottom:20px}
.quick-btn{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 10px;cursor:pointer;transition:.2s;text-align:center;color:var(--text)}
.quick-btn:hover{border-color:var(--accent);background:rgba(99,179,237,.05);transform:translateY(-2px)}
.quick-btn .qicon{font-size:20px;margin-bottom:6px}
.quick-btn .qlabel{font-family:var(--mono);font-size:10px;color:var(--muted);letter-spacing:1px;text-transform:uppercase}
/* FORMS */
.broadcast-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden;margin-bottom:16px}
.broadcast-header{padding:14px 18px;border-bottom:1px solid var(--border);background:var(--surface2);font-family:var(--mono);font-size:11px;letter-spacing:2px;color:var(--muted);text-transform:uppercase}
.broadcast-body{padding:18px}
textarea.bcast-input{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:14px;color:var(--text);font-family:var(--sans);font-size:15px;resize:vertical;min-height:100px;outline:none;transition:.2s;margin-bottom:12px}
textarea.bcast-input:focus{border-color:var(--accent)}
.form-row{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
.form-field{flex:1;min-width:200px}
.form-field label{display:block;font-family:var(--mono);font-size:10px;letter-spacing:2px;color:var(--muted);text-transform:uppercase;margin-bottom:8px}
.form-field input{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:12px 14px;color:var(--text);font-family:var(--mono);font-size:13px;outline:none;transition:.2s;direction:ltr}
.form-field input:focus{border-color:var(--accent)}
/* ACTION BUTTONS */
.action-btn{border:none;border-radius:10px;padding:12px 24px;font-family:var(--mono);font-size:12px;font-weight:700;letter-spacing:1.5px;cursor:pointer;transition:.2s;text-transform:uppercase}
.action-btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent2));color:#000}
.action-btn.primary:hover{opacity:.85;transform:translateY(-1px)}
.action-btn.danger{background:rgba(252,92,101,.12);color:var(--danger);border:1px solid rgba(252,92,101,.3)}
.action-btn.danger:hover{background:rgba(252,92,101,.2);transform:translateY(-1px)}
.action-btn.market{background:rgba(246,201,14,.1);color:var(--gold);border:1px solid rgba(246,201,14,.25)}
.action-btn.market:hover{background:rgba(246,201,14,.18);transform:translateY(-1px)}
/* TOAST */
#toast{position:fixed;bottom:28px;left:50%;transform:translateX(-50%) translateY(80px);background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:14px 24px;font-family:var(--mono);font-size:13px;color:var(--text);z-index:999;transition:transform .3s cubic-bezier(.34,1.56,.64,1);box-shadow:0 20px 60px rgba(0,0,0,.6);white-space:nowrap}
#toast.show{transform:translateX(-50%) translateY(0)}
#toast.t-success{border-color:rgba(38,222,129,.4);color:var(--success)}
#toast.t-error{border-color:rgba(252,92,101,.4);color:var(--danger)}
/* HISTORY */
.history-list{display:flex;flex-direction:column;gap:8px}
.history-item{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 16px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:.15s}
.history-item:hover{border-color:var(--border2)}
.h-time{font-family:var(--mono);font-size:11px;color:var(--muted);min-width:80px}
.h-cmd{font-family:var(--mono);font-size:13px;color:var(--accent);flex:1;direction:ltr}
/* MARKET */
.market-trigger-card{background:var(--surface);border:1px solid rgba(246,201,14,.2);border-radius:16px;padding:24px;margin-bottom:16px;text-align:center}
.m-icon{font-size:40px;margin-bottom:12px}
.m-title{font-family:var(--mono);font-size:14px;color:var(--gold);margin-bottom:6px;letter-spacing:1px}
.m-sub{font-size:13px;color:var(--muted);margin-bottom:20px}
.schedule-list{display:flex;flex-direction:column;gap:8px}
.schedule-item{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:14px 18px;display:flex;align-items:center;justify-content:space-between}
.s-time{font-family:var(--mono);font-size:14px;color:var(--accent2)}
.s-label{font-size:13px;color:var(--text)}
.s-dot{width:8px;height:8px;border-radius:50%;background:var(--success);box-shadow:0 0 6px var(--success)}
@media(max-width:600px){
.topbar{padding:12px 16px}
.tab-content{padding:16px}
.quick-grid{grid-template-columns:repeat(3,1fr)}
.stats-grid{grid-template-columns:repeat(2,1fr)}
.form-row{flex-direction:column}
}
</style>
</head>
<body>
<div id="lockScreen">
<div class="lock-card">
<div class="lock-logo">Titan Nexus &middot; Secure Access</div>
<span class="lock-icon">&#x1F451;</span>
<div class="lock-title">COMMAND CENTER</div>
<div class="lock-sub">Royal Admin Access Required</div>
<input class="lock-input" type="password" id="lockKey" placeholder="Admin Key" autocomplete="off">
<button class="lock-btn" id="authBtn">AUTHENTICATE &rarr;</button>
<div class="lock-err" id="lockErr"></div>
</div>
</div>
<div id="mainApp">
<div class="topbar">
<div class="topbar-brand">
<span class="crown">&#x1F451;</span>
<span class="name">Titan Nexus</span>
<span class="version">v14.2</span>
</div>
<div class="topbar-right">
<div class="live-dot"></div>
<span class="topbar-time" id="topbarTime">--:--</span>
<button class="logout-btn" id="logoutBtn">LOGOUT</button>
</div>
</div>
<div class="tabs">
<button class="tab active" data-tab="overview">OVERVIEW</button>
<button class="tab" data-tab="terminal">TERMINAL</button>
<button class="tab" data-tab="broadcast">BROADCAST</button>
<button class="tab" data-tab="users">USERS</button>
<button class="tab" data-tab="market">MARKET</button>
<button class="tab" data-tab="email">EMAIL</button>
<button class="tab" data-tab="database">DATABASE</button>
<button class="tab" data-tab="history">HISTORY</button>
</div>
<!-- OVERVIEW -->
<div class="tab-content active" id="tab-overview">
<div class="stats-grid">
<div class="stat-card" style="--accent-c:var(--accent)"><div class="stat-label">Uptime</div><div class="stat-value" id="s-uptime">β€”</div><div class="stat-sub">since restart</div></div>
<div class="stat-card" style="--accent-c:var(--success)"><div class="stat-label">Online Users</div><div class="stat-value" id="s-users">β€”</div><div class="stat-sub">registered</div></div>
<div class="stat-card" style="--accent-c:var(--accent2)"><div class="stat-label">Connections</div><div class="stat-value" id="s-conns">β€”</div><div class="stat-sub">total sockets</div></div>
<div class="stat-card" style="--accent-c:var(--warn)"><div class="stat-label">Memory</div><div class="stat-value" id="s-mem">β€”</div><div class="stat-sub" id="s-mem-sub">heap</div></div>
<div class="stat-card" style="--accent-c:var(--gold)"><div class="stat-label">CPU Load</div><div class="stat-value" id="s-cpu">β€”</div><div class="stat-sub">1-min avg</div></div>
<div class="stat-card" style="--accent-c:var(--success)"><div class="stat-label">Sentinel</div><div class="stat-value" style="font-size:14px;color:var(--success)">ACTIVE</div><div class="stat-sub">MK2 armed</div></div>
</div>
<div class="section-title">Quick Actions</div>
<div class="quick-grid">
<div class="quick-btn" id="qStats"><div class="qicon">&#x1F4CA;</div><div class="qlabel">Stats</div></div>
<div class="quick-btn" id="qBroadcast"><div class="qicon">&#x1F4E2;</div><div class="qlabel">Broadcast</div></div>
<div class="quick-btn" id="qClear"><div class="qicon">&#x1F9F9;</div><div class="qlabel">Clear Logs</div></div>
<div class="quick-btn" id="qMarket"><div class="qicon">&#x1F4C8;</div><div class="qlabel">Market</div></div>
<div class="quick-btn" id="qUsers"><div class="qicon">&#x1F464;</div><div class="qlabel">Users</div></div>
<div class="quick-btn" id="qSweep"><div class="qicon">&#x1F9F9;</div><div class="qlabel">DB Sweep</div></div>
<div class="quick-btn" id="qRefresh"><div class="qicon">&#x1F504;</div><div class="qlabel">Refresh</div></div>
</div>
<div class="section-title">System Info</div>
<div class="terminal-wrap"><div class="terminal-output" id="overviewInfo">Loading...</div></div>
</div>
<!-- TERMINAL -->
<div class="tab-content" id="tab-terminal">
<div class="section-title">Command Terminal</div>
<div class="terminal-wrap">
<div class="terminal-bar">
<div class="terminal-dots"><span class="d1"></span><span class="d2"></span><span class="d3"></span></div>
<div class="terminal-title">titan-nexus@server:~$</div>
<div class="terminal-status ready" id="termStatus">READY</div>
</div>
<div class="terminal-output" id="termOutput">Welcome to Titan Nexus Command Center v14.2
Type a command below or use the quick buttons.
──────────────────────────────────────────</div>
<div class="cmd-row">
<span class="cmd-prompt">$</span>
<input class="cmd-input" id="termInput" type="text" placeholder="/stats /ban [uid] [reason] /broadcast [msg]" autocomplete="off">
<button class="cmd-run" id="runBtn">RUN &#x25B6;</button>
</div>
</div>
<div style="margin-top:16px">
<div class="section-title">Quick Commands</div>
<div class="quick-grid">
<div class="quick-btn qcmd" data-cmd="/stats"><div class="qicon">&#x1F4CA;</div><div class="qlabel">/stats</div></div>
<div class="quick-btn qcmd" data-cmd="/clear logs"><div class="qicon">&#x1F9F9;</div><div class="qlabel">/clear logs</div></div>
<div class="quick-btn qcmd" data-cmd="/broadcast "><div class="qicon">&#x1F4E2;</div><div class="qlabel">/broadcast</div></div>
<div class="quick-btn qcmd" data-cmd="/notify "><div class="qicon">&#x1F514;</div><div class="qlabel">/notify</div></div>
<div class="quick-btn qcmd" data-cmd="/ban "><div class="qicon">&#x1F6AB;</div><div class="qlabel">/ban</div></div>
</div>
</div>
</div>
<!-- BROADCAST -->
<div class="tab-content" id="tab-broadcast">
<div class="section-title">Broadcast Message</div>
<div class="broadcast-box">
<div class="broadcast-header">&#x1F4E2; Send to All Users</div>
<div class="broadcast-body">
<textarea class="bcast-input" id="bcastMsg" placeholder="Ψ§ΩƒΨͺΨ¨ Ψ±Ψ³Ψ§Ω„ΨͺΩƒ Ω‡Ω†Ψ§..."></textarea>
<button class="action-btn primary" id="sendBcastBtn">SEND BROADCAST &rarr;</button>
</div>
</div>
<div class="section-title" style="margin-top:24px">Notify Specific User</div>
<div class="broadcast-box">
<div class="broadcast-header">&#x1F514; Direct Notification</div>
<div class="broadcast-body">
<div class="form-row">
<div class="form-field"><label>User UID</label><input type="text" id="notifyUid" placeholder="Firebase UID..."></div>
<div class="form-field"><label>Message</label><input type="text" id="notifyMsg" placeholder="Notification message..."></div>
</div>
<button class="action-btn primary" id="sendNotifyBtn">SEND NOTIFICATION &rarr;</button>
</div>
</div>
</div>
<!-- USERS -->
<div class="tab-content" id="tab-users">
<div class="section-title">User Actions</div>
<div class="broadcast-box">
<div class="broadcast-header">&#x1F6AB; Ban User</div>
<div class="broadcast-body">
<div class="form-row">
<div class="form-field"><label>User UID</label><input type="text" id="banUid" placeholder="Firebase UID to ban..."></div>
<div class="form-field"><label>Reason</label><input type="text" id="banReason" placeholder="Ban reason..."></div>
</div>
<button class="action-btn danger" id="banBtn">BAN USER &#x1F6AB;</button>
</div>
</div>
<div class="section-title" style="margin-top:24px">Royal Family</div>
<div class="broadcast-box">
<div class="broadcast-header" style="color:var(--gold)">&#x1F451; Protected Members</div>
<div class="broadcast-body" style="font-family:var(--mono);font-size:12px;color:var(--muted);line-height:2">
Royal members cannot be banned.<br>Their messages are preserved by Janitor MK2.<br>
<span style="color:var(--gold)">2 members active.</span>
</div>
</div>
</div>
<!-- MARKET -->
<div class="tab-content" id="tab-market">
<div class="market-trigger-card">
<div class="m-icon">&#x1F4C8;</div>
<div class="m-title">MARKET INTELLIGENCE ENGINE</div>
<div class="m-sub">Powered by Groq Compound β€” Live web search for gold, stocks &amp; currencies</div>
<button class="action-btn market" id="marketBtn">FETCH &amp; BROADCAST MARKET REPORT &rarr;</button>
</div>
<div class="section-title">Scheduled Reports</div>
<div class="schedule-list">
<div class="schedule-item"><span class="s-time">08:00</span><span class="s-label">Morning Market Report</span><span class="s-dot"></span></div>
<div class="schedule-item"><span class="s-time">15:00</span><span class="s-label">Afternoon Update</span><span class="s-dot"></span></div>
<div class="schedule-item"><span class="s-time">23:59</span><span class="s-label">Royal Night Report</span><span class="s-dot"></span></div>
</div>
<div class="section-title" style="margin-top:20px">Coverage</div>
<div class="stats-grid">
<div class="stat-card" style="--accent-c:var(--gold)"><div class="stat-label">Gold Markets</div><div class="stat-value" style="font-size:14px">EG SA AE</div><div class="stat-sub">EGP / SAR / AED / USD</div></div>
<div class="stat-card" style="--accent-c:var(--accent)"><div class="stat-label">Indices</div><div class="stat-value" style="font-size:14px">S&amp;P NSDQ</div><div class="stat-sub">+ Dow + EGX30</div></div>
<div class="stat-card" style="--accent-c:var(--warn)"><div class="stat-label">Commodities</div><div class="stat-value" style="font-size:14px">Brent BTC</div><div class="stat-sub">Oil &amp; Bitcoin</div></div>
<div class="stat-card" style="--accent-c:var(--accent2)"><div class="stat-label">FX Rates</div><div class="stat-value" style="font-size:14px">4 pairs</div><div class="stat-sub">vs EGP</div></div>
</div>
</div>
<!-- DATABASE -->
<div class="tab-content" id="tab-database">
<div class="section-title">Database Maintenance</div>
<!-- Sweep Orphans Card -->
<div class="broadcast-box" style="border-color:rgba(252,92,101,.25)">
<div class="broadcast-header" style="color:var(--warn)">&#x1F9F9; Orphaned Data Sweeper</div>
<div class="broadcast-body">
<div style="font-family:var(--mono);font-size:12px;color:var(--muted);line-height:2;margin-bottom:18px">
&#x2022; Paginates all Firebase Auth users to build a valid-UID set<br>
&#x2022; Cross-references <span style="color:var(--accent)">titan_ghosts</span> to find records with no Auth account<br>
&#x2022; Batch-deletes orphans from 5 collections: <span style="color:var(--accent)">titan_ghosts, titan_keys, users, user_reputation, welcome_sent</span><br>
&#x2022; Safe chunking β€” commits every 450 ops to stay under Firestore's 500-op batch limit
</div>
<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
<button class="action-btn danger" id="sweepBtn">&#x1F9F9; RUN SWEEP</button>
<span id="sweepStatus" style="font-family:var(--mono);font-size:12px;color:var(--muted)"></span>
</div>
</div>
</div>
<!-- Sweep Result Panel (hidden until a sweep runs) -->
<div id="sweepResultPanel" style="display:none;margin-top:8px">
<div class="section-title">Last Sweep Report</div>
<div class="terminal-wrap">
<div class="terminal-bar">
<div class="terminal-dots"><span class="d1"></span><span class="d2"></span><span class="d3"></span></div>
<div class="terminal-title">sweeper@titan-nexus:~$</div>
<div class="terminal-status ok" id="sweepTermStatus">DONE</div>
</div>
<div class="terminal-output" id="sweepOutput" style="min-height:140px"></div>
</div>
<!-- Result stat cards -->
<div class="stats-grid" style="margin-top:16px" id="sweepStats">
<div class="stat-card" style="--accent-c:var(--danger)">
<div class="stat-label">Orphans Found</div>
<div class="stat-value" id="sw-orphans">β€”</div>
<div class="stat-sub">missing from Auth</div>
</div>
<div class="stat-card" style="--accent-c:var(--warn)">
<div class="stat-label">Docs Deleted</div>
<div class="stat-value" id="sw-docs">β€”</div>
<div class="stat-sub">across 5 collections</div>
</div>
<div class="stat-card" style="--accent-c:var(--accent)">
<div class="stat-label">Batches</div>
<div class="stat-value" id="sw-batches">β€”</div>
<div class="stat-sub">committed</div>
</div>
<div class="stat-card" style="--accent-c:var(--success)">
<div class="stat-label">Status</div>
<div class="stat-value" style="font-size:14px" id="sw-status">β€”</div>
<div class="stat-sub" id="sw-time">β€”</div>
</div>
</div>
</div>
</div>
<!-- HISTORY -->
<div class="tab-content" id="tab-history">
<div class="section-title">Command History <span style="color:var(--muted);font-size:10px">(this session)</span></div>
<div class="history-list" id="historyList">
<div style="color:var(--muted);font-family:var(--mono);font-size:13px;padding:20px 0;text-align:center">No commands run yet.</div>
</div>
</div>
<!-- EMAIL -->
<div class="tab-content" id="tab-email">
<div class="section-title">Send Email to Users</div>
<div class="broadcast-box">
<div class="broadcast-header">&#x1F4E7; Professional Email Broadcast</div>
<div class="broadcast-body">
<div class="form-row">
<div class="form-field">
<label>Target (empty = ALL users)</label>
<input type="email" id="emailTarget" placeholder="specific@email.com or leave empty for all">
</div>
<div class="form-field">
<label>Accent Color</label>
<input type="text" id="emailAccent" placeholder="#63b3ed" value="#63b3ed" style="max-width:140px">
</div>
</div>
<div class="form-field" style="margin-bottom:12px">
<label>Subject</label>
<input type="text" id="emailSubject" placeholder="Email subject...">
</div>
<div class="form-field" style="margin-bottom:12px">
<label>Logo URL (optional)</label>
<input type="url" id="emailLogo" placeholder="https://example.com/logo.png">
</div>
<div class="form-field" style="margin-bottom:12px">
<label>Message Body</label>
<textarea class="bcast-input" id="emailBody" placeholder="Write your email here..." style="min-height:160px"></textarea>
</div>
<div class="form-row">
<div class="form-field">
<label>Button Text (optional)</label>
<input type="text" id="emailBtnText" placeholder="Open App">
</div>
<div class="form-field">
<label>Button URL (optional)</label>
<input type="url" id="emailBtnUrl" placeholder="https://...">
</div>
</div>
<div class="form-field" style="margin-bottom:16px">
<label>PDF URL (shown as download button)</label>
<input type="url" id="emailPdfUrl" placeholder="https://...">
</div>
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
<button class="action-btn primary" id="sendEmailBtn">&#x1F4E7; SEND EMAIL</button>
<span id="emailStatus" style="font-family:var(--mono);font-size:12px;color:var(--muted)"></span>
</div>
</div>
</div>
<div class="section-title" style="margin-top:16px">Features</div>
<div class="broadcast-box">
<div class="broadcast-body" style="font-family:var(--mono);font-size:12px;color:var(--muted);line-height:2">
&#x2022; Each user gets a private individual email<br>
&#x2022; Professional dark HTML template with gradient<br>
&#x2022; Optional logo, CTA button, PDF download link<br>
&#x2022; Customizable accent color per campaign<br>
&#x2022; Requires MAILER_URL + MAILER_TOKEN in HF Secrets
</div>
</div>
</div>
</div>
<div id="toast"></div>
<script>
var ADMIN_KEY = '';
var history_log = [];
var toastTimer = null;
// ── AUTH ──
document.getElementById('authBtn').addEventListener('click', function() { doUnlock(); });
document.getElementById('lockKey').addEventListener('keydown', function(e) { if (e.key === 'Enter') doUnlock(); });
function doUnlock() {
var k = document.getElementById('lockKey').value.trim();
if (!k) { document.getElementById('lockErr').textContent = 'Enter admin key'; return; }
document.getElementById('lockErr').textContent = 'Authenticating...';
document.getElementById('authBtn').disabled = true;
fetch('/api/admin/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: k, command: '/stats' })
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
ADMIN_KEY = k;
document.getElementById('lockScreen').style.display = 'none';
document.getElementById('mainApp').style.display = 'block';
startClock();
loadStats();
if (d.stats) showOverviewInfo(d.stats);
} else {
document.getElementById('lockErr').textContent = 'Invalid key β€” access denied';
document.getElementById('authBtn').disabled = false;
}
})
.catch(function(e) {
document.getElementById('lockErr').textContent = 'Server unreachable: ' + e.message;
document.getElementById('authBtn').disabled = false;
});
}
// ── LOGOUT ──
document.getElementById('logoutBtn').addEventListener('click', function() {
ADMIN_KEY = '';
document.getElementById('lockScreen').style.display = 'flex';
document.getElementById('mainApp').style.display = 'none';
document.getElementById('lockKey').value = '';
document.getElementById('authBtn').disabled = false;
});
// ── CLOCK ──
function startClock() {
function tick() {
var now = new Date();
document.getElementById('topbarTime').textContent =
now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
}
tick();
setInterval(tick, 1000);
}
// ── TABS ──
document.querySelectorAll('.tab').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
document.querySelectorAll('.tab-content').forEach(function(t) { t.classList.remove('active'); });
btn.classList.add('active');
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
function goTab(name) {
document.querySelectorAll('.tab').forEach(function(t) {
if (t.dataset.tab === name) t.click();
});
}
// ── STATS ──
function loadStats() {
fetch('/stats')
.then(function(r) { return r.json(); })
.then(function(d) {
document.getElementById('s-uptime').textContent = d.uptime || 'β€”';
document.getElementById('s-users').textContent = d.connections ? d.connections.registered : 'β€”';
document.getElementById('s-conns').textContent = d.connections ? d.connections.total : 'β€”';
document.getElementById('s-mem').textContent = d.memory ? d.memory.percentage : 'β€”';
document.getElementById('s-mem-sub').textContent = d.memory ? d.memory.heapUsed : '';
document.getElementById('s-cpu').textContent = d.cpuLoad ? d.cpuLoad['1min'] : 'β€”';
})
.catch(function() {});
}
setInterval(function() { if (ADMIN_KEY) loadStats(); }, 30000);
function showOverviewInfo(s) {
var lines = [
'Platform : ' + s.platform,
'Node.js : ' + s.nodeVersion,
'Uptime : ' + s.uptime,
'Memory : ' + s.memory.heapUsed + ' / ' + s.memory.heapTotal + ' (' + s.memory.percentage + ')',
'CPU Load : ' + s.cpuLoad['1min'] + ' / ' + s.cpuLoad['5min'] + ' / ' + s.cpuLoad['15min']
];
document.getElementById('overviewInfo').textContent = lines.join('\n');
}
// ── QUICK ACTIONS (overview) ──
document.getElementById('qStats').addEventListener('click', function() { goTab('terminal'); setCmd('/stats'); });
document.getElementById('qBroadcast').addEventListener('click', function() { goTab('broadcast'); });
document.getElementById('qClear').addEventListener('click', function() { goTab('terminal'); setCmd('/clear logs'); });
document.getElementById('qMarket').addEventListener('click', function() { goTab('market'); });
document.getElementById('qUsers').addEventListener('click', function() { goTab('users'); });
document.getElementById('qSweep').addEventListener('click', function() { goTab('database'); });
document.getElementById('qRefresh').addEventListener('click', function() { loadStats(); showToast('Refreshed!', 'success'); });
// ── TERMINAL ──
function setCmd(c) {
goTab('terminal');
var inp = document.getElementById('termInput');
inp.value = c;
inp.focus();
}
function appendTerm(text, cls) {
var el = document.getElementById('termOutput');
var line = document.createElement('div');
if (cls) line.className = cls;
line.textContent = text;
el.appendChild(line);
el.scrollTop = el.scrollHeight;
}
function runCommand() {
var cmd = document.getElementById('termInput').value.trim();
if (!cmd) return;
var btn = document.getElementById('runBtn');
var statusEl = document.getElementById('termStatus');
btn.disabled = true;
statusEl.textContent = 'RUNNING';
statusEl.className = 'terminal-status running';
appendTerm('$ ' + cmd, 'line-info');
fetch('/api/admin/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: ADMIN_KEY, command: cmd })
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
var out = d.report || (d.stats ? JSON.stringify(d.stats, null, 2) : JSON.stringify(d, null, 2));
appendTerm(out, 'line-success');
statusEl.textContent = 'OK';
statusEl.className = 'terminal-status ok';
addHistory(cmd, true);
showToast('Done!', 'success');
} else {
appendTerm('Error: ' + (d.error || 'Unknown'), 'line-error');
statusEl.textContent = 'ERROR';
statusEl.className = 'terminal-status error';
addHistory(cmd, false);
showToast(d.error || 'Failed', 'error');
}
btn.disabled = false;
document.getElementById('termInput').value = '';
})
.catch(function(e) {
appendTerm('Connection error: ' + e.message, 'line-error');
statusEl.textContent = 'ERROR';
statusEl.className = 'terminal-status error';
btn.disabled = false;
showToast('Unreachable', 'error');
});
}
document.getElementById('runBtn').addEventListener('click', runCommand);
document.getElementById('termInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') runCommand(); });
// quick cmd buttons in terminal tab
document.querySelectorAll('.qcmd').forEach(function(el) {
el.addEventListener('click', function() { setCmd(el.dataset.cmd); });
});
// ── BROADCAST ──
document.getElementById('sendBcastBtn').addEventListener('click', function() {
var msg = document.getElementById('bcastMsg').value.trim();
if (!msg) { showToast('Write a message first', 'error'); return; }
execCmd('/broadcast ' + msg, function(ok) {
if (ok) { document.getElementById('bcastMsg').value = ''; showToast('Broadcast sent!', 'success'); }
});
});
document.getElementById('sendNotifyBtn').addEventListener('click', function() {
var uid = document.getElementById('notifyUid').value.trim();
var msg = document.getElementById('notifyMsg').value.trim();
if (!uid || !msg) { showToast('Fill all fields', 'error'); return; }
execCmd('/notify ' + uid + ' ' + msg, function(ok) {
if (ok) showToast('Notification sent!', 'success');
});
});
// ── BAN ──
document.getElementById('banBtn').addEventListener('click', function() {
var uid = document.getElementById('banUid').value.trim();
var reason = document.getElementById('banReason').value.trim();
if (!uid || !reason) { showToast('Fill all fields', 'error'); return; }
if (!confirm('Ban user ' + uid + '?')) return;
execCmd('/ban ' + uid + ' ' + reason, function(ok) {
if (ok) showToast('User banned.', 'success');
});
});
// ── MARKET ──
document.getElementById('marketBtn').addEventListener('click', function() {
showToast('Fetching market data... (30-45s)', 'success');
fetch('/admin/market-now', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: ADMIN_KEY })
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) showToast('Market report initiated!', 'success');
else showToast(d.error || 'Failed', 'error');
})
.catch(function() { showToast('Server error', 'error'); });
});
// ── HELPERS ──
function execCmd(cmd, cb) {
fetch('/api/admin/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: ADMIN_KEY, command: cmd })
})
.then(function(r) { return r.json(); })
.then(function(d) {
addHistory(cmd, d.success);
if (!d.success) showToast(d.error || 'Failed', 'error');
if (cb) cb(d.success);
})
.catch(function() { showToast('Unreachable', 'error'); if (cb) cb(false); });
}
function addHistory(cmd, ok) {
history_log.unshift({ cmd: cmd, ok: ok, time: new Date().toLocaleTimeString('en-US', { hour12: false }) });
if (history_log.length > 50) history_log.pop();
renderHistory();
}
function renderHistory() {
var el = document.getElementById('historyList');
if (!history_log.length) return;
var html = '';
for (var i = 0; i < history_log.length; i++) {
var h = history_log[i];
html += '<div class="history-item">';
html += '<span class="h-time">' + h.time + '</span>';
html += '<span class="h-cmd">' + h.cmd + '</span>';
html += '<span>' + (h.ok ? '&#x2705;' : '&#x274C;') + '</span>';
html += '</div>';
}
el.innerHTML = html;
}
function showToast(msg, type) {
var el = document.getElementById('toast');
el.textContent = (type === 'success' ? 'βœ“ ' : 'βœ— ') + msg;
el.className = 'show t-' + type;
clearTimeout(toastTimer);
toastTimer = setTimeout(function() { el.className = ''; }, 3000);
}
// ── EMAIL ──
document.getElementById('sendEmailBtn').addEventListener('click', function() {
var subject = document.getElementById('emailSubject').value.trim();
var body = document.getElementById('emailBody').value.trim();
var target = document.getElementById('emailTarget').value.trim();
var logo = document.getElementById('emailLogo').value.trim();
var btnText = document.getElementById('emailBtnText').value.trim();
var btnUrl = document.getElementById('emailBtnUrl').value.trim();
var pdfUrl = document.getElementById('emailPdfUrl').value.trim();
var accent = document.getElementById('emailAccent').value.trim();
var statusEl = document.getElementById('emailStatus');
if (!subject || !body) { showToast('Fill subject and message', 'error'); return; }
if (!confirm(target ? 'Send to ' + target + '?' : 'Send to ALL ' + recipients_count + ' users?')) return;
statusEl.textContent = 'Sending...';
document.getElementById('sendEmailBtn').disabled = true;
var payload = { adminKey: ADMIN_KEY, subject: subject, body: body };
if (target) payload.targetEmail = target;
if (logo) payload.logoUrl = logo;
if (btnText) payload.buttonText = btnText;
if (btnUrl) payload.buttonUrl = btnUrl;
if (pdfUrl) payload.pdfUrl = pdfUrl;
if (accent) payload.accentColor = accent;
fetch('/admin/send-email', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(payload)
})
.then(function(r) { return r.json(); })
.then(function(d) {
document.getElementById('sendEmailBtn').disabled = false;
if (d.success) {
statusEl.textContent = 'Sent: ' + d.sent + ' | Failed: ' + d.failed + ' | Total: ' + d.total;
showToast('Email sent!', 'success');
} else {
statusEl.textContent = 'Error: ' + (d.error || 'Unknown');
showToast(d.error || 'Failed', 'error');
}
})
.catch(function() {
document.getElementById('sendEmailBtn').disabled = false;
statusEl.textContent = 'Connection error';
showToast('Server unreachable', 'error');
});
});
// ── SWEEP ORPHANS ──
document.getElementById('sweepBtn').addEventListener('click', function() {
if (!confirm('Run orphaned data sweep? This will permanently delete records for users no longer in Firebase Auth.')) return;
var btn = document.getElementById('sweepBtn');
var statusEl = document.getElementById('sweepStatus');
var panel = document.getElementById('sweepResultPanel');
var termOut = document.getElementById('sweepOutput');
var termSt = document.getElementById('sweepTermStatus');
btn.disabled = true;
statusEl.textContent = 'Sweeping… this may take a moment.';
statusEl.style.color = 'var(--warn)';
termSt.textContent = 'RUNNING';
termSt.className = 'terminal-status running';
panel.style.display = 'block';
termOut.textContent = 'Paginating Firebase Auth users...\nFetching titan_ghosts collection...';
fetch('/api/admin/command', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminKey: ADMIN_KEY, command: '/sweep-orphans' })
})
.then(function(r) { return r.json(); })
.then(function(d) {
btn.disabled = false;
if (d.success) {
var det = d.details || {};
var orphans = det.orphansFound !== undefined ? det.orphansFound : '?';
var docs = det.docsDeleted !== undefined ? det.docsDeleted : '?';
var batches = det.batchesCommitted !== undefined ? det.batchesCommitted : '?';
var colls = det.collectionsSwept ? det.collectionsSwept.join(', ') : 'β€”';
var uids = det.orphanedUids && det.orphanedUids.length ? det.orphanedUids.join('\n ') : 'none';
var now = new Date().toLocaleTimeString('en-US', { hour12: false });
var lines = [
'βœ… ' + (d.message || 'Sweep complete.'),
'─────────────────────────────────',
'Orphaned UIDs : ' + orphans,
'Docs deleted : ' + docs,
'Batches : ' + batches,
'Collections : ' + colls,
'─────────────────────────────────'
];
if (orphans > 0) {
lines.push('Orphaned UIDs (masked):');
lines.push(' ' + uids);
}
lines.push('Completed at : ' + now);
termOut.textContent = lines.join('\n');
termSt.textContent = 'DONE';
termSt.className = 'terminal-status ok';
document.getElementById('sw-orphans').textContent = orphans;
document.getElementById('sw-docs').textContent = docs;
document.getElementById('sw-batches').textContent = batches;
document.getElementById('sw-status').textContent = orphans === 0 ? 'CLEAN βœ…' : 'SWEPT βœ…';
document.getElementById('sw-status').style.color = orphans === 0 ? 'var(--success)' : 'var(--warn)';
document.getElementById('sw-time').textContent = 'at ' + now;
statusEl.textContent = orphans === 0 ? 'βœ“ Database is clean.' : 'βœ“ Swept ' + orphans + ' orphan(s).';
statusEl.style.color = 'var(--success)';
addHistory('/sweep-orphans', true);
showToast(orphans === 0 ? 'DB is clean!' : 'Swept ' + orphans + ' orphan(s)!', 'success');
} else {
termOut.textContent = '❌ Error: ' + (d.error || 'Unknown');
termSt.textContent = 'ERROR';
termSt.className = 'terminal-status error';
statusEl.textContent = 'Failed: ' + (d.error || 'Unknown');
statusEl.style.color = 'var(--danger)';
addHistory('/sweep-orphans', false);
showToast(d.error || 'Sweep failed', 'error');
}
})
.catch(function(e) {
btn.disabled = false;
termOut.textContent = '❌ Connection error: ' + e.message;
termSt.textContent = 'ERROR';
termSt.className = 'terminal-status error';
statusEl.textContent = 'Server unreachable';
statusEl.style.color = 'var(--danger)';
showToast('Server unreachable', 'error');
});
});
var recipients_count = '?';
</script>
</body>
</html>