Files
ccsite-manager/templates/master.html

258 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Master Control — CCSite Fleet</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
:root{--bg:#080812;--card:#12121f;--accent:#6c63ff;--accent2:#00d4aa;--text:#e0e0e0;--muted:#777;--danger:#ef4444;--success:#22c55e;--warning:#f59e0b;--radius:10px}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
header{background:var(--card);border-bottom:1px solid rgba(108,99,255,0.2);padding:0.8rem 2rem;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:100}
header h1{font-size:1.1rem;font-weight:800;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.tabs{display:flex;gap:0;border-bottom:1px solid rgba(255,255,255,0.06);background:var(--card);padding:0 2rem}
.tab{padding:0.7rem 1.3rem;cursor:pointer;font-size:0.8rem;font-weight:600;color:var(--muted);border-bottom:2px solid transparent;transition:all 0.15s;text-transform:uppercase;letter-spacing:0.5px}
.tab:hover{color:var(--text)}
.tab.active{color:var(--accent);border-bottom-color:var(--accent)}
.container{max-width:1500px;margin:0 auto;padding:1.5rem}
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:0.75rem;margin-bottom:1.5rem}
.stat-card{background:var(--card);padding:1rem;border-radius:var(--radius);border:1px solid rgba(255,255,255,0.03);text-align:center}
.stat-card .sv{font-size:1.4rem;font-weight:800;color:var(--accent2)}
.stat-card .sl{font-size:0.6rem;text-transform:uppercase;letter-spacing:1px;color:var(--muted);margin-top:0.15rem}
.panel{display:none}.panel.active{display:block}
table{width:100%;border-collapse:collapse;font-size:0.78rem}
th,td{padding:0.45rem 0.6rem;text-align:left;border-bottom:1px solid rgba(255,255,255,0.04)}
th{color:var(--muted);font-size:0.65rem;text-transform:uppercase;letter-spacing:0.5px;position:sticky;top:0;background:var(--bg)}
tr:hover{background:rgba(108,99,255,0.03)}
.badge{padding:0.15rem 0.45rem;border-radius:10px;font-size:0.6rem;font-weight:700}
.badge-ok{background:rgba(34,197,94,0.15);color:var(--success)}
.badge-pending{background:rgba(245,158,11,0.15);color:var(--warning)}
.badge-danger{background:rgba(239,68,68,0.15);color:var(--danger)}
input,select{padding:0.45rem 0.7rem;border-radius:6px;border:1px solid rgba(255,255,255,0.08);background:var(--card);color:var(--text);font-size:0.78rem;outline:none}
input:focus,select:focus{border-color:var(--accent)}
.search-bar{display:flex;gap:0.5rem;margin-bottom:1rem;flex-wrap:wrap}
.btn{padding:0.4rem 0.9rem;border-radius:6px;border:none;cursor:pointer;font-weight:600;font-size:0.75rem;background:var(--accent);color:#fff}
.btn:hover{filter:brightness(1.15)}
.table-wrap{max-height:60vh;overflow-y:auto;border-radius:var(--radius);border:1px solid rgba(255,255,255,0.04)}
</style>
</head>
<body>
<header>
<h1>🖥️ Master Control Panel</h1>
<div style="font-size:0.7rem;color:var(--muted);"><a href="/" style="color:var(--accent);text-decoration:none;">← Dashboard</a> | <span id="clock">--</span></div>
</header>
<div class="tabs">
<div class="tab active" onclick="switchTab('overview')">📊 Overview</div>
<div class="tab" onclick="switchTab('orders')">📦 Orders</div>
<div class="tab" onclick="switchTab('users')">👥 Users</div>
<div class="tab" onclick="switchTab('products')">🏷️ Products</div>
<div class="tab" onclick="switchTab('transactions')">💳 Transactions</div>
</div>
<div class="container">
<div id="panel-overview" class="panel active">
<div class="stat-row" id="overview-stats"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;">
<div style="background:var(--card);padding:1rem;border-radius:var(--radius);">
<h3 style="font-size:0.75rem;color:var(--muted);margin-bottom:0.8rem;">📦 Recent Orders</h3>
<div id="recent-orders"></div>
</div>
<div style="background:var(--card);padding:1rem;border-radius:var(--radius);">
<h3 style="font-size:0.75rem;color:var(--muted);margin-bottom:0.8rem;">👥 Recent Signups</h3>
<div id="recent-users"></div>
</div>
</div>
</div>
<div id="panel-orders" class="panel">
<div class="search-bar">
<input type="text" id="order-search" placeholder="Search orders..." oninput="renderOrders()">
<select id="order-site" onchange="renderOrders()"><option value="">All Sites</option></select>
<select id="order-status" onchange="renderOrders()"><option value="">All Status</option><option value="pending">Pending</option><option value="completed">Completed</option></select>
</div>
<div class="table-wrap"><table id="orders-table"><thead><tr><th>Site</th><th>Order</th><th>Product</th><th>Email</th><th>Amount</th><th>Status</th><th>Date</th></tr></thead><tbody></tbody></table></div>
</div>
<div id="panel-users" class="panel">
<div class="search-bar">
<input type="text" id="user-search" placeholder="Search users..." oninput="renderUsers()">
<select id="user-site" onchange="renderUsers()"><option value="">All Sites</option></select>
</div>
<div class="table-wrap"><table id="users-table"><thead><tr><th>Site</th><th>User</th><th>Email</th><th>Balance</th><th>Orders</th><th>Joined</th></tr></thead><tbody></tbody></table></div>
</div>
<div id="panel-products" class="panel">
<div class="search-bar">
<input type="text" id="product-search" placeholder="Search products..." oninput="renderProducts()">
<select id="product-site" onchange="renderProducts()"><option value="">All Sites</option></select>
</div>
<div class="table-wrap"><table id="products-table"><thead><tr><th>Site</th><th>Product</th><th>Category</th><th>Price</th><th>Stock</th></tr></thead><tbody></tbody></table></div>
</div>
<div id="panel-transactions" class="panel">
<div class="search-bar">
<input type="text" id="txn-search" placeholder="Search transactions..." oninput="renderTransactions()">
<select id="txn-site" onchange="renderTransactions()"><option value="">All Sites</option></select>
</div>
<div class="table-wrap"><table id="txns-table"><thead><tr><th>Site</th><th>User</th><th>Type</th><th>Method</th><th>Amount</th><th>Date</th></tr></thead><tbody></tbody></table></div>
</div>
</div>
<script>
const SITE_IPS = {1:'10.30.20.210',2:'10.30.20.211',3:'10.30.20.212',4:'10.30.20.213',5:'10.30.20.214',6:'10.30.20.217',7:'10.30.20.218',8:'10.30.20.219',9:'10.30.20.220',10:'10.30.20.221'};
let ALL_DATA = {};
async function loadAll() {
const promises = Object.entries(SITE_IPS).map(async ([id,ip]) => {
try {
const r = await fetch(`http://${ip}:5000/api/dashboard-data`);
const d = await r.json();
d._site_id = parseInt(id);
d._ip = ip;
return d;
} catch(e) { return null; }
});
const results = await Promise.all(promises);
ALL_DATA = results.filter(r => r);
// Populate site dropdowns
const siteOpts = ALL_DATA.map(d => `<option value="${d._site_id}">${d.site}</option>`).join('');
['order-site','user-site','product-site','txn-site'].forEach(id => {
document.getElementById(id).innerHTML = '<option value="">All Sites</option>' + siteOpts;
});
updateOverview();
}
function updateOverview() {
let totalOrders=0, totalUsers=0, totalRevenue=0, pendingOrders=0;
ALL_DATA.forEach(d => {
totalOrders += d.orders.length;
totalUsers += d.users.length;
totalRevenue += d.orders.filter(o=>o.status==='completed').reduce((s,o)=>s+o.amount,0);
pendingOrders += d.orders.filter(o=>o.status==='pending').length;
});
document.getElementById('overview-stats').innerHTML = `
<div class="stat-card"><div class="sv">${totalUsers}</div><div class="sl">Total Users</div></div>
<div class="stat-card"><div class="sv">${totalOrders}</div><div class="sl">Total Orders</div></div>
<div class="stat-card"><div class="sv">$${totalRevenue.toFixed(0)}</div><div class="sl">Revenue</div></div>
<div class="stat-card"><div class="sv" style="color:var(--warning);">${pendingOrders}</div><div class="sl">Pending</div></div>
<div class="stat-card"><div class="sv">${ALL_DATA.length}</div><div class="sl">Sites Online</div></div>
`;
// Recent orders
const allOrders = ALL_DATA.flatMap(d => d.orders.map(o => ({...o, site:d.site, sid:d._site_id})));
allOrders.sort((a,b) => b.date.localeCompare(a.date));
document.getElementById('recent-orders').innerHTML = allOrders.slice(0,10).map(o =>
`<div style="padding:0.3rem 0;border-bottom:1px solid rgba(255,255,255,0.03);font-size:0.75rem;">
<span style="color:var(--accent);">${o.site}</span> · ${o.product?.slice(0,30)} ·
<span style="color:var(--accent2);">$${o.amount?.toFixed(2)}</span>
<span class="badge ${o.status==='completed'?'badge-ok':'badge-pending'}">${o.status}</span>
</div>`
).join('') || '<div style="color:var(--muted);">No orders yet</div>';
// Recent users
const allUsers = ALL_DATA.flatMap(d => d.users.map(u => ({...u, site:d.site})));
allUsers.sort((a,b) => b.joined.localeCompare(a.joined));
document.getElementById('recent-users').innerHTML = allUsers.slice(0,10).map(u =>
`<div style="padding:0.3rem 0;border-bottom:1px solid rgba(255,255,255,0.03);font-size:0.75rem;">
<span style="color:var(--accent);">${u.site}</span> · ${u.username} · ${u.email}
</div>`
).join('') || '<div style="color:var(--muted);">No users yet</div>';
}
function renderOrders() {
const q = (document.getElementById('order-search').value||'').toLowerCase();
const sid = parseInt(document.getElementById('order-site').value)||0;
const status = document.getElementById('order-status').value;
let orders = ALL_DATA.flatMap(d => d.orders.map(o => ({...o, site:d.site, sid:d._site_id})));
if(sid) orders = orders.filter(o => o.sid===sid);
if(status) orders = orders.filter(o => o.status===status);
if(q) orders = orders.filter(o => (o.product+o.email+o.code).toLowerCase().includes(q));
orders.sort((a,b) => b.date.localeCompare(a.date));
document.getElementById('orders-table').querySelector('tbody').innerHTML = orders.slice(0,200).map(o =>
`<tr>
<td style="color:var(--accent);">${o.site}</td>
<td>#${o.id} <span style="font-size:0.65rem;color:var(--muted);">${o.code||''}</span></td>
<td>${o.product?.slice(0,35)}</td>
<td>${o.email}</td>
<td style="color:var(--accent2);">$${o.amount?.toFixed(2)}</td>
<td><span class="badge ${o.status==='completed'?'badge-ok':'badge-pending'}">${o.status}</span></td>
<td style="font-size:0.7rem;color:var(--muted);">${new Date(o.date).toLocaleDateString()}</td>
</tr>`
).join('');
}
function renderUsers() {
const q = (document.getElementById('user-search').value||'').toLowerCase();
const sid = parseInt(document.getElementById('user-site').value)||0;
let users = ALL_DATA.flatMap(d => d.users.map(u => ({...u, site:d.site, sid:d._site_id})));
if(sid) users = users.filter(u => u.sid===sid);
if(q) users = users.filter(u => (u.username+u.email).toLowerCase().includes(q));
document.getElementById('users-table').querySelector('tbody').innerHTML = users.slice(0,200).map(u =>
`<tr>
<td style="color:var(--accent);">${u.site}</td>
<td><strong>${u.username}</strong></td>
<td>${u.email}</td>
<td style="color:var(--accent2);">$${u.balance?.toFixed(2)}</td>
<td>${u.orders}</td>
<td style="font-size:0.7rem;color:var(--muted);">${new Date(u.joined).toLocaleDateString()}</td>
</tr>`
).join('');
}
function renderProducts() {
const q = (document.getElementById('product-search').value||'').toLowerCase();
const sid = parseInt(document.getElementById('product-site').value)||0;
let prods = ALL_DATA.flatMap(d => d.products.map(p => ({...p, site:d.site, sid:d._site_id})));
if(sid) prods = prods.filter(p => p.sid===sid);
if(q) prods = prods.filter(p => p.name.toLowerCase().includes(q));
document.getElementById('products-table').querySelector('tbody').innerHTML = prods.slice(0,200).map(p =>
`<tr>
<td style="color:var(--accent);">${p.site}</td>
<td>${p.name}</td>
<td>${p.category}</td>
<td style="color:var(--accent2);">$${p.price?.toFixed(2)}</td>
<td>${p.stock}</td>
</tr>`
).join('');
}
function renderTransactions() {
const q = (document.getElementById('txn-search').value||'').toLowerCase();
const sid = parseInt(document.getElementById('txn-site').value)||0;
let txns = ALL_DATA.flatMap(d => d.transactions.map(t => ({...t, site:d.site, sid:d._site_id})));
if(sid) txns = txns.filter(t => t.sid===sid);
if(q) txns = txns.filter(t => (t.user+t.method).toLowerCase().includes(q));
document.getElementById('txns-table').querySelector('tbody').innerHTML = txns.slice(0,200).map(t =>
`<tr>
<td style="color:var(--accent);">${t.site}</td>
<td>${t.user}</td>
<td><span class="badge ${t.type==='deposit'?'badge-ok':'badge-danger'}">${t.type}</span></td>
<td>${t.method||'-'}</td>
<td style="color:${t.amount>0?'var(--success)':'var(--danger)'};">${t.amount>0?'+':''}$${t.amount?.toFixed(2)}</td>
<td style="font-size:0.7rem;color:var(--muted);">${new Date(t.date).toLocaleDateString()}</td>
</tr>`
).join('');
}
function switchTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
const tabMap = {'overview':1,'orders':2,'users':3,'products':4,'transactions':5};
document.querySelector('.tab:nth-child(' + tabMap[tab] + ')').classList.add('active');
document.getElementById('panel-'+tab).classList.add('active');
if(tab==='orders') renderOrders();
if(tab==='users') renderUsers();
if(tab==='products') renderProducts();
if(tab==='transactions') renderTransactions();
}
setInterval(() => document.getElementById('clock').textContent = new Date().toLocaleTimeString(), 1000);
loadAll();
setInterval(loadAll, 30000);
</script>
</body>
</html>