background-remover / admin.html
freebg's picture
Upload 2 files
a0a9fa4 verified
Raw
History Blame Contribute Delete
29.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>FreeBG Admin</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#07070f;--s:#0f0f1a;--card:#14141f;--card2:#1a1a28;
--b:#252535;--b2:#2e2e45;
--g:#00e5a0;--g2:rgba(0,229,160,.12);
--bl:#4d8eff;--bl2:rgba(77,142,255,.12);
--red:#ff4d6d;--red2:rgba(255,77,109,.12);
--gold:#ffc850;--gold2:rgba(255,200,80,.12);
--t:#eeeef5;--m:#5e5e78;--m2:#888899;
--r:10px;
}
body{background:var(--bg);color:var(--t);font-family:system-ui,sans-serif;min-height:100vh}
/* LOGIN */
#login-screen{
position:fixed;inset:0;background:var(--bg);
display:flex;align-items:center;justify-content:center;z-index:999;
}
.login-box{
background:var(--card);border:1px solid var(--b);border-radius:14px;
padding:40px;width:340px;text-align:center;
}
.login-box h2{font-size:22px;font-weight:800;margin-bottom:6px}
.login-box p{color:var(--m2);font-size:13px;margin-bottom:28px}
.login-box input{
width:100%;background:var(--card2);border:1px solid var(--b2);
color:var(--t);padding:12px 14px;border-radius:8px;
font-size:15px;outline:none;margin-bottom:14px;
}
.login-box input:focus{border-color:var(--g)}
.btn-login{
width:100%;padding:13px;background:var(--g);color:#000;
border:none;border-radius:9px;font-size:15px;font-weight:800;cursor:pointer;
}
.btn-login:hover{opacity:.88}
.login-err{color:var(--red);font-size:13px;margin-top:10px}
/* HEADER */
header{
background:var(--card);border-bottom:1px solid var(--b);
padding:0 28px;height:58px;display:flex;align-items:center;gap:14px;
position:sticky;top:0;z-index:100;
}
.logo{font-size:18px;font-weight:800;color:var(--g)}
.logo span{color:var(--t)}
.nav-tabs{display:flex;gap:2px;margin-left:24px}
.tab-btn{
padding:7px 16px;border:none;background:none;color:var(--m2);
font-size:13px;font-weight:600;cursor:pointer;border-radius:7px;transition:.2s;
}
.tab-btn:hover{color:var(--t);background:rgba(255,255,255,.05)}
.tab-btn.active{color:var(--g);background:var(--g2)}
.header-right{margin-left:auto;display:flex;gap:8px;align-items:center}
.hf-link{
font-size:12px;color:var(--m2);text-decoration:none;
padding:6px 12px;border:1px solid var(--b2);border-radius:7px;transition:.2s;
}
.hf-link:hover{border-color:var(--g);color:var(--g)}
.btn-logout{
font-size:12px;color:var(--m2);background:none;border:none;cursor:pointer;padding:4px 8px;
}
.btn-logout:hover{color:var(--red)}
/* MAIN */
main{max-width:1100px;margin:0 auto;padding:28px 20px}
/* STATS */
.stats{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:24px}
@media(max-width:700px){.stats{grid-template-columns:repeat(3,1fr)}}
.stat-card{
background:var(--card);border:1px solid var(--b);border-radius:var(--r);
padding:16px 14px;text-align:center;
}
.stat-n{font-size:26px;font-weight:800}
.stat-l{font-size:11px;color:var(--m2);margin-top:3px}
/* PANELS */
.panel{background:var(--card);border:1px solid var(--b);border-radius:var(--r);margin-bottom:20px;overflow:hidden}
.panel-head{
padding:14px 18px;border-bottom:1px solid var(--b);
display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;
}
.panel-title{font-size:14px;font-weight:700}
.panel-body{padding:18px}
/* FORM */
.form-row{display:grid;grid-template-columns:1fr 1fr auto;gap:10px;align-items:end}
@media(max-width:600px){.form-row{grid-template-columns:1fr}}
label{display:block;font-size:11px;font-weight:700;color:var(--m2);
text-transform:uppercase;letter-spacing:.5px;margin-bottom:5px}
input,select{
width:100%;background:var(--card2);border:1px solid var(--b2);
color:var(--t);padding:9px 12px;border-radius:8px;
font-size:14px;outline:none;transition:.2s;
}
input:focus,select:focus{border-color:var(--g)}
input::placeholder{color:var(--m)}
/* BUTTONS */
.btn{
padding:9px 16px;border-radius:8px;font-size:13px;font-weight:700;
cursor:pointer;border:none;transition:.2s;white-space:nowrap;
display:inline-flex;align-items:center;gap:5px;
}
.btn-g{background:var(--g);color:#000}
.btn-g:hover{opacity:.88}
.btn-r{background:var(--red2);color:var(--red);border:1px solid rgba(255,77,109,.3)}
.btn-r:hover{background:rgba(255,77,109,.25)}
.btn-b{background:var(--bl2);color:var(--bl);border:1px solid rgba(77,142,255,.3)}
.btn-b:hover{background:rgba(77,142,255,.25)}
.btn-ghost{background:var(--card2);color:var(--t);border:1px solid var(--b2)}
.btn-ghost:hover{border-color:var(--m2)}
/* TABLE */
.tbl-wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:13px}
th{
text-align:left;padding:9px 12px;
font-size:11px;font-weight:700;color:var(--m2);
text-transform:uppercase;letter-spacing:.5px;
border-bottom:1px solid var(--b);
}
td{padding:10px 12px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:middle}
tr:hover td{background:rgba(255,255,255,.02)}
tr:last-child td{border-bottom:none}
.key-mono{font-family:monospace;font-size:12px;color:var(--m2);cursor:pointer}
.key-mono:hover{color:var(--g)}
.badge{
display:inline-block;font-size:10px;font-weight:800;
padding:2px 8px;border-radius:5px;text-transform:uppercase;
}
.b-free{background:rgba(107,107,133,.2);color:var(--m2)}
.b-starter{background:var(--bl2);color:var(--bl)}
.b-pro{background:var(--g2);color:var(--g)}
.b-master{background:var(--gold2);color:var(--gold)}
.actions{display:flex;gap:5px}
/* EXPORT BOX */
.export-box{
background:var(--card2);border:1px solid var(--b);border-radius:8px;
padding:14px;font-family:monospace;font-size:12px;color:var(--m2);
word-break:break-all;white-space:pre-wrap;
max-height:200px;overflow-y:auto;user-select:all;margin-top:12px;
}
/* INSTRUCTIONS */
.steps{display:flex;flex-direction:column;gap:10px}
.step{display:flex;gap:12px;align-items:flex-start}
.step-n{
width:22px;height:22px;border-radius:50%;background:var(--g);
color:#000;font-size:11px;font-weight:800;
display:flex;align-items:center;justify-content:center;flex-shrink:0;
}
.step-t{font-size:13px;color:var(--m2);line-height:1.5}
.step-t b{color:var(--t)}
.step-t code{
background:var(--card2);border:1px solid var(--b2);
padding:1px 6px;border-radius:4px;font-family:monospace;font-size:12px;color:var(--g);
}
/* USAGE BAR */
.usage-bar{height:6px;background:var(--b);border-radius:3px;overflow:hidden;margin-top:4px}
.usage-fill{height:100%;background:var(--g);border-radius:3px;transition:.3s}
/* TOAST */
.toast{
position:fixed;bottom:20px;right:20px;z-index:999;
padding:10px 16px;border-radius:9px;font-size:13px;font-weight:600;border:1px solid;
transform:translateY(20px);opacity:0;transition:.3s;pointer-events:none;
}
.toast.show{transform:translateY(0);opacity:1}
.toast.ok{background:#0a1a12;border-color:rgba(0,229,160,.4);color:var(--g)}
.toast.err{background:#1a0a0e;border-color:rgba(255,77,109,.4);color:var(--red)}
/* TABS content */
.tab-content{display:none}
.tab-content.active{display:block}
/* EDIT INLINE */
.edit-row td{background:var(--card2)!important;padding:6px 12px}
.edit-row input,.edit-row select{padding:6px 10px;font-size:13px;margin:0}
</style>
</head>
<body>
<!-- LOGIN SCREEN -->
<div id="login-screen">
<div class="login-box">
<h2>πŸ” FreeBG Admin</h2>
<p>Enter admin password to continue</p>
<input type="password" id="pwd-input" placeholder="Admin password"
onkeydown="if(event.key==='Enter')doLogin()">
<button class="btn-login" onclick="doLogin()">Login</button>
<div class="login-err" id="login-err"></div>
</div>
</div>
<!-- HEADER -->
<header>
<div class="logo">Free<span>BG</span> <span style="font-size:11px;color:var(--m2);font-weight:400;margin-left:4px">Admin</span></div>
<div class="nav-tabs">
<button class="tab-btn active" onclick="showTab('customers')">πŸ‘₯ Customers</button>
<button class="tab-btn" onclick="showTab('export')">πŸ“‹ Export</button>
<button class="tab-btn" onclick="showTab('stats')">πŸ“Š Stats</button>
<button class="tab-btn" onclick="showTab('guide')">πŸ“– Guide</button>
</div>
<div class="header-right">
<a class="hf-link" href="https://huggingface.co/spaces/freebg/background-remover/settings"
target="_blank">πŸ€— HF Secrets β†’</a>
<button class="btn-logout" onclick="doLogout()">Logout</button>
</div>
</header>
<main>
<!-- STATS ROW -->
<div class="stats" id="stats-row">
<div class="stat-card"><div class="stat-n" id="s-total" style="color:var(--g)">0</div><div class="stat-l">Total Keys</div></div>
<div class="stat-card"><div class="stat-n" id="s-free" style="color:var(--m2)">0</div><div class="stat-l">Free</div></div>
<div class="stat-card"><div class="stat-n" id="s-start" style="color:var(--bl)">0</div><div class="stat-l">Starter</div></div>
<div class="stat-card"><div class="stat-n" id="s-pro" style="color:var(--g)">0</div><div class="stat-l">Pro</div></div>
<div class="stat-card"><div class="stat-n" id="s-calls" style="color:var(--gold)">0</div><div class="stat-l">Calls Today</div></div>
</div>
<!-- TAB: CUSTOMERS -->
<div class="tab-content active" id="tab-customers">
<!-- Add Customer -->
<div class="panel">
<div class="panel-head"><div class="panel-title">βž• Add New Customer</div></div>
<div class="panel-body">
<div class="form-row">
<div>
<label>Email</label>
<input type="email" id="n-email" placeholder="customer@gmail.com">
</div>
<div>
<label>Plan</label>
<select id="n-plan">
<option value="free">Free β€” 10/day</option>
<option value="starter" selected>Starter β€” 100/day ($9/mo)</option>
<option value="pro">Pro β€” 500/day ($29/mo)</option>
<option value="master">Master β€” Unlimited</option>
</select>
</div>
<div>
<label>&nbsp;</label>
<button class="btn btn-g" onclick="addCustomer()">✨ Generate Key</button>
</div>
</div>
</div>
</div>
<!-- Search + Table -->
<div class="panel">
<div class="panel-head">
<div class="panel-title">All Customers (<span id="count">0</span>)</div>
<div style="display:flex;gap:8px;align-items:center">
<input type="text" id="search" placeholder="Search email or key..."
style="width:220px;margin:0;padding:7px 11px;font-size:13px"
oninput="renderTable()">
<button class="btn btn-r" onclick="if(confirm('Delete ALL keys?'))clearAll()">πŸ—‘οΈ Clear All</button>
</div>
</div>
<div class="tbl-wrap">
<table>
<thead>
<tr>
<th>API Key</th>
<th>Owner</th>
<th>Plan</th>
<th>Calls Today</th>
<th>Limit</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div id="empty" style="text-align:center;padding:40px;color:var(--m);display:none">
No customers yet. Add your first one above!
</div>
</div>
</div>
</div>
<!-- TAB: EXPORT -->
<div class="tab-content" id="tab-export">
<div class="panel">
<div class="panel-head">
<div class="panel-title">πŸ“‹ Export for HuggingFace Secret</div>
<div style="display:flex;gap:8px">
<button class="btn btn-g" onclick="copyExport()">πŸ“‹ Copy JSON</button>
<button class="btn btn-b" onclick="window.open('https://huggingface.co/spaces/freebg/background-remover/settings','_blank')">
πŸ€— Open HF Settings
</button>
</div>
</div>
<div class="panel-body">
<div class="steps">
<div class="step"><div class="step-n">1</div>
<div class="step-t">Neeche JSON copy karo β†’ <b>"Copy JSON"</b> button se</div></div>
<div class="step"><div class="step-n">2</div>
<div class="step-t"><b>"Open HF Settings"</b> click karo</div></div>
<div class="step"><div class="step-n">3</div>
<div class="step-t">Secrets section mein <code>API_KEYS_JSON</code> β†’ <b>Replace</b> β†’ Paste β†’ Save</div></div>
<div class="step"><div class="step-n">4</div>
<div class="step-t">Space automatically restart hoga β€” done! βœ…</div></div>
</div>
<div class="export-box" id="export-box">{}</div>
<div style="margin-top:10px;display:flex;gap:10px">
<button class="btn btn-ghost" onclick="importKeys()">πŸ“‚ Import existing JSON</button>
</div>
</div>
</div>
</div>
<!-- TAB: STATS -->
<div class="tab-content" id="tab-stats">
<div class="panel">
<div class="panel-head"><div class="panel-title">πŸ“Š Usage & Revenue</div></div>
<div class="panel-body">
<div id="stats-detail" style="display:grid;grid-template-columns:1fr 1fr;gap:16px"></div>
</div>
</div>
</div>
<!-- TAB: GUIDE -->
<div class="tab-content" id="tab-guide">
<div class="panel">
<div class="panel-head"><div class="panel-title">πŸ“– How to Use This Admin</div></div>
<div class="panel-body">
<div class="steps" style="gap:16px">
<div class="step"><div class="step-n">1</div>
<div class="step-t"><b>Customer add karo:</b> Email + Plan β†’ Generate Key β†’ Key popup mein dikhegi β†’ Customer ko bhejo</div></div>
<div class="step"><div class="step-n">2</div>
<div class="step-t"><b>HF Space update karo:</b> Export tab β†’ Copy JSON β†’ HF Settings β†’ API_KEYS_JSON β†’ Replace</div></div>
<div class="step"><div class="step-n">3</div>
<div class="step-t"><b>Plan upgrade:</b> Table mein ✏️ Edit β†’ Plan change karo β†’ Save β†’ Phir Export karo</div></div>
<div class="step"><div class="step-n">4</div>
<div class="step-t"><b>Key revoke:</b> Table mein πŸ—‘οΈ Delete β†’ Phir Export karo</div></div>
<div class="step"><div class="step-n">5</div>
<div class="step-t"><b>Data safe:</b> Keys browser mein save hain. <code>Export β†’ Import</code> se backup rakho</div></div>
</div>
<div style="margin-top:20px;padding:14px;background:var(--card2);border-radius:8px;font-size:13px">
<div style="font-weight:700;margin-bottom:8px;color:var(--gold)">⚠️ Important</div>
<div style="color:var(--m2);line-height:1.7">
Har baar customer add/edit/delete karne ke baad <b>Export tab β†’ HF Space update karna zaroori hai</b>.<br>
Otherwise changes sirf browser mein rahenge, Space pe apply nahi honge.<br>
Admin password change karne ke liye HTML mein <code>ADMIN_PASSWORD</code> update karo.
</div>
</div>
<div style="margin-top:14px;padding:14px;background:var(--card2);border-radius:8px;font-size:13px">
<div style="font-weight:700;margin-bottom:8px">πŸ”— Useful Links</div>
<div style="display:flex;flex-direction:column;gap:6px">
<a href="https://huggingface.co/spaces/freebg/background-remover" target="_blank"
style="color:var(--bl)">β†’ HuggingFace Space (App)</a>
<a href="https://huggingface.co/spaces/freebg/background-remover/settings" target="_blank"
style="color:var(--bl)">β†’ HuggingFace Space Settings (Secrets)</a>
<a href="https://huggingface.co/spaces/freebg/background-remover/blob/main/app.py" target="_blank"
style="color:var(--bl)">β†’ app.py source</a>
<a href="/health" target="_blank" style="color:var(--g)">β†’ /health endpoint</a>
<a href="/api/models" target="_blank" style="color:var(--g)">β†’ /api/models endpoint</a>
</div>
</div>
</div>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
// ── CONFIG ────────────────────────────────────────────────────────────────────
const ADMIN_PASSWORD = "freebg-admin-2026"; // ← Change this!
const STORAGE_KEY = "freebg_keys_v2";
const SESSION_KEY = "freebg_admin_auth";
const PLANS = {
free: { daily:10, models:["fast"], price:"$0" },
starter: { daily:100, models:["fast","quality"], price:"$9/mo" },
pro: { daily:500, models:["fast","quality","best"], price:"$29/mo" },
master: { daily:999999,models:["fast","quality","best"], price:"Custom" },
};
// ── AUTH ──────────────────────────────────────────────────────────────────────
function doLogin() {
const val = document.getElementById("pwd-input").value;
if (val === ADMIN_PASSWORD) {
sessionStorage.setItem(SESSION_KEY, "1");
document.getElementById("login-screen").style.display = "none";
init();
} else {
document.getElementById("login-err").textContent = "❌ Wrong password";
setTimeout(() => document.getElementById("login-err").textContent = "", 2000);
}
}
function doLogout() {
sessionStorage.removeItem(SESSION_KEY);
location.reload();
}
if (!sessionStorage.getItem(SESSION_KEY)) {
document.getElementById("login-screen").style.display = "flex";
} else {
document.getElementById("login-screen").style.display = "none";
}
// ── STORAGE ───────────────────────────────────────────────────────────────────
function load() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY)||"{}"); } catch { return {}; } }
function save(k) { localStorage.setItem(STORAGE_KEY, JSON.stringify(k)); refresh(); }
// ── KEY GEN ───────────────────────────────────────────────────────────────────
function genKey(plan) {
const c = "abcdefghijklmnopqrstuvwxyz0123456789";
const r = n => Array.from({length:n},()=>c[Math.floor(Math.random()*c.length)]).join("");
return `freebg-${plan.slice(0,2)}-${r(8)}-${r(8)}`;
}
// ── ADD CUSTOMER ──────────────────────────────────────────────────────────────
function addCustomer() {
const email = document.getElementById("n-email").value.trim();
const plan = document.getElementById("n-plan").value;
if (!email || !email.includes("@")) return toast("Valid email required","err");
const keys = load();
const key = genKey(plan);
keys[key] = {
plan, owner:email, calls_today:0, reset_at:0,
limit: PLANS[plan].daily, models: PLANS[plan].models,
created_at: new Date().toISOString(),
};
save(keys);
document.getElementById("n-email").value = "";
showNewKey(key, email, plan);
toast("Key created! Export β†’ HF Space update karo","ok");
}
function showNewKey(key, email, plan) {
const d = document.createElement("div");
d.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;z-index:500;padding:20px";
d.innerHTML = `
<div style="background:#14141f;border:1px solid #2e2e45;border-radius:14px;padding:32px;max-width:500px;width:100%;text-align:center">
<div style="font-size:36px;margin-bottom:12px">πŸŽ‰</div>
<h3 style="color:#00e5a0;font-size:20px;margin-bottom:8px">Key Created!</h3>
<p style="color:#888;font-size:13px;margin-bottom:18px">
<b style="color:#eee">${email}</b> Β· <b style="color:#eee">${plan}</b> plan Β· ${PLANS[plan].daily} calls/day
</p>
<div style="background:#07070f;border:1px solid #252535;border-radius:8px;padding:14px;font-family:monospace;font-size:14px;color:#00e5a0;word-break:break-all;margin-bottom:8px;cursor:pointer"
onclick="navigator.clipboard.writeText('${key}').then(()=>toast('Copied!','ok'))">${key}</div>
<p style="color:#5e5e78;font-size:12px;margin-bottom:18px">πŸ‘† Click to copy Β· Customer ko yeh key bhejo</p>
<div style="background:#1a1a28;border:1px solid #252535;border-radius:8px;padding:12px;font-size:12px;color:#888;margin-bottom:18px;text-align:left">
⚠️ <b style="color:#eee">Zaroori:</b> Export tab β†’ HF Space update karo taake key activate ho
</div>
<div style="display:flex;gap:10px">
<button onclick="navigator.clipboard.writeText('${key}').then(()=>{this.textContent='βœ… Copied!'})"
style="flex:1;padding:11px;background:#00e5a0;color:#000;border:none;border-radius:8px;font-weight:800;cursor:pointer">
πŸ“‹ Copy Key
</button>
<button onclick="this.closest('div[style]').remove()"
style="padding:11px 20px;background:#1a1a28;color:#eee;border:1px solid #2e2e45;border-radius:8px;cursor:pointer">
Close
</button>
</div>
</div>`;
document.body.appendChild(d);
}
// ── TABLE ─────────────────────────────────────────────────────────────────────
function renderTable() {
const keys = load();
const q = document.getElementById("search").value.toLowerCase();
const tbody = document.getElementById("tbody");
const empty = document.getElementById("empty");
const count = document.getElementById("count");
const entries = Object.entries(keys).filter(([k,v]) =>
!q || k.includes(q) || (v.owner||"").toLowerCase().includes(q)
);
count.textContent = Object.keys(keys).length;
if (!entries.length) {
tbody.innerHTML = ""; empty.style.display = "block"; return;
}
empty.style.display = "none";
tbody.innerHTML = entries.map(([key, d]) => {
const pct = Math.min(100, Math.round((d.calls_today||0)/(d.limit||1)*100));
return `<tr>
<td class="key-mono" onclick="navigator.clipboard.writeText('${key}').then(()=>toast('Key copied!','ok'))"
title="Click to copy">${key.slice(0,28)}…</td>
<td>${d.owner||"β€”"}</td>
<td><span class="badge b-${d.plan}">${d.plan}</span></td>
<td>
<div style="font-size:12px">${d.calls_today||0} / ${d.limit||0}</div>
<div class="usage-bar"><div class="usage-fill" style="width:${pct}%"></div></div>
</td>
<td style="font-size:12px;color:var(--m2)">${(d.limit||0).toLocaleString()}/day</td>
<td style="font-size:12px;color:var(--m2)">${d.created_at?d.created_at.slice(0,10):"β€”"}</td>
<td>
<div class="actions">
<button class="btn btn-b" style="padding:5px 9px;font-size:11px" onclick="editRow('${key}')">✏️</button>
<button class="btn btn-r" style="padding:5px 9px;font-size:11px" onclick="delKey('${key}','${d.owner}')">πŸ—‘οΈ</button>
</div>
</td>
</tr>`;
}).join("");
}
function delKey(key, owner) {
if (!confirm(`Delete key for ${owner}?\n${key}`)) return;
const keys = load(); delete keys[key]; save(keys);
toast("Key deleted. Export β†’ HF update karo","ok");
}
// ── EDIT ──────────────────────────────────────────────────────────────────────
function editRow(key) {
const keys = load(); const d = keys[key]; if (!d) return;
const rows = document.querySelectorAll("#tbody tr");
// Remove any existing edit row
document.querySelectorAll(".edit-row").forEach(r=>r.remove());
// Find the row for this key
let targetRow = null;
rows.forEach(r => { if(r.cells[0]?.textContent.includes(key.slice(0,10))) targetRow = r; });
if (!targetRow) return;
const editRow = document.createElement("tr");
editRow.className = "edit-row";
editRow.innerHTML = `
<td colspan="7" style="padding:12px">
<div style="display:grid;grid-template-columns:1fr 1fr 120px auto auto;gap:8px;align-items:end">
<div><label>Email</label><input id="e-email" value="${d.owner||""}"></div>
<div><label>Plan</label>
<select id="e-plan">
${["free","starter","pro","master"].map(p=>`<option value="${p}"${p===d.plan?" selected":""}>${p} (${PLANS[p].daily}/day)</option>`).join("")}
</select>
</div>
<div><label>Custom limit</label><input type="number" id="e-limit" value="${d.limit||""}" placeholder=""></div>
<div><label>&nbsp;</label><button class="btn btn-g" onclick="saveEdit('${key}')">πŸ’Ύ Save</button></div>
<div><label>&nbsp;</label><button class="btn btn-ghost" onclick="document.querySelectorAll('.edit-row').forEach(r=>r.remove())">βœ•</button></div>
</div>
</td>`;
targetRow.after(editRow);
}
function saveEdit(key) {
const keys = load();
const plan = document.getElementById("e-plan").value;
const limit = parseInt(document.getElementById("e-limit").value) || PLANS[plan].daily;
keys[key].owner = document.getElementById("e-email").value.trim();
keys[key].plan = plan;
keys[key].limit = limit;
keys[key].models = PLANS[plan].models;
save(keys);
document.querySelectorAll(".edit-row").forEach(r=>r.remove());
toast("Updated! Export β†’ HF update karo","ok");
}
// ── EXPORT ────────────────────────────────────────────────────────────────────
function updateExportBox() {
const keys = load();
document.getElementById("export-box").textContent = JSON.stringify(keys, null, 2);
}
function copyExport() {
const text = document.getElementById("export-box").textContent;
navigator.clipboard.writeText(text).then(() =>
toast("JSON copied! HF Space Secrets mein paste karo","ok"));
}
function importKeys() {
const json = prompt("Paste existing api_keys.json content:");
if (!json) return;
try { save(JSON.parse(json)); toast("Imported!","ok"); }
catch { toast("Invalid JSON","err"); }
}
function clearAll() { save({}); toast("All keys deleted","ok"); }
// ── STATS ─────────────────────────────────────────────────────────────────────
function updateStats() {
const keys = load();
const vals = Object.values(keys);
document.getElementById("s-total").textContent = vals.length;
document.getElementById("s-free").textContent = vals.filter(v=>v.plan==="free").length;
document.getElementById("s-start").textContent = vals.filter(v=>v.plan==="starter").length;
document.getElementById("s-pro").textContent = vals.filter(v=>["pro","master"].includes(v.plan)).length;
document.getElementById("s-calls").textContent = vals.reduce((a,v)=>a+(v.calls_today||0),0);
// Detail stats
const det = document.getElementById("stats-detail");
const rev = vals.filter(v=>v.plan==="starter").length*9 + vals.filter(v=>v.plan==="pro").length*29;
det.innerHTML = `
<div style="background:var(--card2);border-radius:8px;padding:16px">
<div style="font-size:12px;color:var(--m2);margin-bottom:4px">EST. MONTHLY REVENUE</div>
<div style="font-size:32px;font-weight:800;color:var(--g)">$${rev}</div>
<div style="font-size:12px;color:var(--m2);margin-top:4px">${vals.filter(v=>v.plan==="starter").length} starter + ${vals.filter(v=>v.plan==="pro").length} pro</div>
</div>
<div style="background:var(--card2);border-radius:8px;padding:16px">
<div style="font-size:12px;color:var(--m2);margin-bottom:4px">TOTAL API CALLS TODAY</div>
<div style="font-size:32px;font-weight:800;color:var(--gold)">${vals.reduce((a,v)=>a+(v.calls_today||0),0)}</div>
<div style="font-size:12px;color:var(--m2);margin-top:4px">across all customers</div>
</div>`;
}
// ── TABS ──────────────────────────────────────────────────────────────────────
function showTab(id) {
document.querySelectorAll(".tab-content").forEach(t=>t.classList.remove("active"));
document.querySelectorAll(".tab-btn").forEach(b=>b.classList.remove("active"));
document.getElementById("tab-"+id).classList.add("active");
event.target.classList.add("active");
if (id==="export") updateExportBox();
if (id==="stats") updateStats();
}
// ── TOAST ─────────────────────────────────────────────────────────────────────
function toast(msg, type) {
const t = document.getElementById("toast");
t.textContent = msg; t.className = `toast ${type} show`;
setTimeout(()=>t.classList.remove("show"),3000);
}
// ── INIT ──────────────────────────────────────────────────────────────────────
function refresh() { renderTable(); updateStats(); updateExportBox(); }
function init() { refresh(); }
if (sessionStorage.getItem(SESSION_KEY)) init();
</script>
</body>
</html>