phishguard-api / admin.html
prashanth135's picture
Upload 38 files
bebe233 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PhishGuard Admin</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { margin:0; padding:0; box-sizing:border-box; }
:root {
--bg: #0F0F14; --bg2: #1A1A24; --card: #22222E; --border: rgba(255,255,255,0.06);
--text: #EAEAF0; --text2: #8888A0; --accent: #534AB7;
--safe: #22C55E; --danger: #EF4444; --warn: #F59E0B;
}
body { font-family:'Inter',sans-serif; background:var(--bg); color:var(--text); min-height:100vh; }
/* Login */
.login-wrap { display:flex; align-items:center; justify-content:center; min-height:100vh; }
.login-box { background:var(--card); padding:36px; border-radius:16px; border:1px solid var(--border); width:340px; }
.login-box h2 { font-size:20px; margin-bottom:20px; text-align:center; }
.login-box input { width:100%; padding:10px 14px; background:var(--bg2); border:1px solid var(--border);
border-radius:8px; color:var(--text); font-size:14px; font-family:inherit; margin-bottom:12px; outline:none; }
.login-box input:focus { border-color:var(--accent); }
.login-box button { width:100%; padding:10px; background:linear-gradient(135deg,var(--accent),#6C5ECE);
border:none; border-radius:8px; color:#fff; font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; }
.login-box button:hover { opacity:0.9; }
.login-error { color:var(--danger); font-size:12px; text-align:center; margin-top:8px; display:none; }
/* Dashboard */
.dashboard { display:none; max-width:1000px; margin:0 auto; padding:24px; }
.dash-header { display:flex; align-items:center; gap:12px; margin-bottom:24px; }
.dash-header h1 { font-size:22px; flex:1; }
.dash-header h1 span { color:var(--accent); }
.logout-btn { padding:6px 16px; background:var(--card); border:1px solid var(--border);
border-radius:8px; color:var(--text2); font-size:12px; cursor:pointer; font-family:inherit; }
/* Stats Cards */
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:12px; margin-bottom:24px; }
.stat-card { background:var(--card); border:1px solid var(--border); border-radius:12px; padding:16px; }
.stat-label { font-size:11px; color:var(--text2); text-transform:uppercase; letter-spacing:0.5px; }
.stat-value { font-size:28px; font-weight:700; margin-top:4px; }
.stat-sub { font-size:11px; color:var(--text2); margin-top:2px; }
/* Table */
.section-title { font-size:16px; font-weight:600; margin-bottom:12px; }
.table-wrap { background:var(--card); border:1px solid var(--border); border-radius:12px; overflow:hidden; margin-bottom:24px; }
table { width:100%; border-collapse:collapse; font-size:12px; }
th { background:var(--bg2); padding:10px 14px; text-align:left; font-weight:600; color:var(--text2);
text-transform:uppercase; letter-spacing:0.5px; font-size:10px; }
td { padding:10px 14px; border-top:1px solid var(--border); }
tr:hover td { background:rgba(255,255,255,0.02); }
.badge { display:inline-block; padding:2px 8px; border-radius:4px; font-size:10px; font-weight:600; }
.badge-phish { background:rgba(239,68,68,0.12); color:var(--danger); }
.badge-safe { background:rgba(34,197,94,0.12); color:var(--safe); }
.url-cell { max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:'SF Mono',monospace; font-size:11px; color:var(--text2); }
/* Retrain Button */
.retrain-bar { display:flex; align-items:center; gap:12px; margin-bottom:24px; }
.retrain-btn { padding:10px 24px; background:linear-gradient(135deg,var(--accent),#6C5ECE);
border:none; border-radius:8px; color:#fff; font-size:13px; font-weight:600; cursor:pointer; font-family:inherit; }
.retrain-btn:hover { opacity:0.9; }
.retrain-btn:disabled { opacity:0.4; cursor:not-allowed; }
.retrain-status { font-size:12px; color:var(--text2); }
/* History */
.history-card { background:var(--card); border:1px solid var(--border); border-radius:12px; padding:16px; margin-bottom:8px;
display:flex; align-items:center; gap:16px; }
.hist-version { font-size:22px; font-weight:700; color:var(--accent); min-width:48px; text-align:center; }
.hist-detail { flex:1; }
.hist-detail div:first-child { font-size:13px; font-weight:500; }
.hist-detail div:last-child { font-size:11px; color:var(--text2); margin-top:2px; }
.hist-accuracy { font-size:14px; font-weight:600; }
</style>
</head>
<body>
<!-- Login Screen -->
<div class="login-wrap" id="loginScreen">
<div class="login-box">
<h2>πŸ›‘οΈ PhishGuard Admin</h2>
<input type="password" id="passInput" placeholder="Admin password" autocomplete="off">
<button onclick="attemptLogin()">Login</button>
<div class="login-error" id="loginError">Invalid password</div>
</div>
</div>
<!-- Dashboard (hidden until login) -->
<div class="dashboard" id="dashboard">
<div class="dash-header">
<h1>Phish<span>Guard</span> Admin</h1>
<button class="logout-btn" onclick="logout()">Logout</button>
</div>
<!-- Stats -->
<div class="stats">
<div class="stat-card">
<div class="stat-label">Total Feedback</div>
<div class="stat-value" id="sTotalFeedback">β€”</div>
</div>
<div class="stat-card">
<div class="stat-label">Phishing Reports</div>
<div class="stat-value" id="sPhishing" style="color:var(--danger)">β€”</div>
</div>
<div class="stat-card">
<div class="stat-label">Safe Reports</div>
<div class="stat-value" id="sSafe" style="color:var(--safe)">β€”</div>
</div>
<div class="stat-card">
<div class="stat-label">Model Version</div>
<div class="stat-value" id="sVersion" style="color:var(--accent)">β€”</div>
<div class="stat-sub" id="sLastRetrain">Never retrained</div>
</div>
<div class="stat-card">
<div class="stat-label">Unprocessed</div>
<div class="stat-value" id="sUnprocessed" style="color:var(--warn)">β€”</div>
<div class="stat-sub">of 50 needed</div>
</div>
</div>
<!-- Manual Retrain -->
<div class="retrain-bar">
<button class="retrain-btn" id="retrainBtn" onclick="triggerRetrain()">πŸ”„ Trigger Retraining</button>
<div class="retrain-status" id="retrainStatus"></div>
</div>
<!-- Recent Feedback -->
<div class="section-title">Recent Feedback</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>URL</th><th>Label</th><th>Source</th><th>Prediction</th><th>Time</th></tr>
</thead>
<tbody id="feedbackTable"><tr><td colspan="5" style="text-align:center;color:var(--text2)">Loading...</td></tr></tbody>
</table>
</div>
<!-- Retrain History -->
<div class="section-title">Retrain History</div>
<div id="historyList"><div style="color:var(--text2);font-size:12px">Loading...</div></div>
</div>
<script>
const BASE = window.location.origin;
let authToken = "";
// Login
function attemptLogin() {
const pass = document.getElementById("passInput").value;
fetch(`${BASE}/admin/login`, {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({password: pass})
})
.then(r => r.json())
.then(data => {
if (data.success) {
authToken = data.token;
document.getElementById("loginScreen").style.display = "none";
document.getElementById("dashboard").style.display = "block";
loadDashboard();
} else {
document.getElementById("loginError").style.display = "block";
}
})
.catch(() => { document.getElementById("loginError").style.display = "block"; });
}
document.getElementById("passInput").addEventListener("keyup", e => { if(e.key==="Enter") attemptLogin(); });
function logout() {
authToken = "";
document.getElementById("loginScreen").style.display = "flex";
document.getElementById("dashboard").style.display = "none";
}
// Dashboard data
function loadDashboard() {
// Stats
fetch(`${BASE}/admin/data?token=${authToken}`).then(r=>r.json()).then(data => {
if (data.error) { logout(); return; }
const s = data.stats;
document.getElementById("sTotalFeedback").textContent = s.total_feedback;
document.getElementById("sPhishing").textContent = s.phishing_corrections;
document.getElementById("sSafe").textContent = s.safe_corrections;
document.getElementById("sVersion").textContent = "v" + s.model_version;
document.getElementById("sUnprocessed").textContent = s.unprocessed_count;
document.getElementById("sLastRetrain").textContent = s.last_retrain
? "Last: " + new Date(s.last_retrain).toLocaleString() : "Never retrained";
// Feedback table
const rows = data.recent.map(e => `
<tr>
<td class="url-cell" title="${esc(e.url)}">${esc(e.url)}</td>
<td><span class="badge ${e.label==='phishing'?'badge-phish':'badge-safe'}">${esc(e.label)}</span></td>
<td>${esc(e.source||'β€”')}</td>
<td>${e.original_prediction!=null ? (e.original_prediction*100).toFixed(0)+'%' : 'β€”'}</td>
<td style="font-size:11px;color:var(--text2)">${e.timestamp ? new Date(e.timestamp).toLocaleString() : 'β€”'}</td>
</tr>
`).join("");
document.getElementById("feedbackTable").innerHTML = rows || '<tr><td colspan="5" style="text-align:center;color:var(--text2)">No feedback yet</td></tr>';
// History
const hist = (s.retrain_history || []).reverse();
if (hist.length === 0) {
document.getElementById("historyList").innerHTML = '<div style="color:var(--text2);font-size:12px">No retraining history</div>';
} else {
document.getElementById("historyList").innerHTML = hist.map(h => `
<div class="history-card">
<div class="hist-version">v${h.version}</div>
<div class="hist-detail">
<div>Trained on ${h.samples} samples</div>
<div>${new Date(h.timestamp).toLocaleString()}</div>
</div>
<div class="hist-accuracy" style="color:${h.accuracy>=0.8?'var(--safe)':h.accuracy>=0.6?'var(--warn)':'var(--danger)'}">
${(h.accuracy*100).toFixed(1)}%
</div>
</div>
`).join("");
}
});
}
// Retrain
function triggerRetrain() {
const btn = document.getElementById("retrainBtn");
btn.disabled = true;
btn.textContent = "⏳ Retraining...";
document.getElementById("retrainStatus").textContent = "Training in progress...";
fetch(`${BASE}/admin/retrain?token=${authToken}`, {method:"POST"})
.then(r=>r.json())
.then(data => {
document.getElementById("retrainStatus").textContent = data.message || "Done";
btn.disabled = false;
btn.textContent = "πŸ”„ Trigger Retraining";
setTimeout(loadDashboard, 2000);
})
.catch(e => {
document.getElementById("retrainStatus").textContent = "Error: " + e.message;
btn.disabled = false;
btn.textContent = "πŸ”„ Trigger Retraining";
});
}
function esc(s) { const d=document.createElement('div'); d.textContent=String(s||''); return d.innerHTML; }
// Auto-refresh every 30s
setInterval(() => { if(authToken) loadDashboard(); }, 30000);
</script>
</body>
</html>