JARVIS / static /dashboard.html
Khanna, Videh Rakesh Rakesh
feat: JARVIS security, cross-device, intelligence & polish overhaul
e2a2dda
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>J.A.R.V.I.S. — Dashboard</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg: #0a0e17; --surface: #111827; --surface2: #1a2332;
--accent: #00b4d8; --accent2: #0077b6; --accent-glow: rgba(0,180,216,0.3);
--text: #e0e6ed; --text2: #8899aa; --success: #00e676;
--warning: #ffab00; --danger: #ff5252;
}
body {
background: var(--bg); color: var(--text);
font-family: 'SF Mono', 'Fira Code', monospace;
padding: 20px; min-height: 100vh;
}
.header { display: flex; align-items: center; gap: 16px; margin-bottom: 24px; }
.header h1 { font-size: 20px; color: var(--accent); letter-spacing: 4px; }
.header a { color: var(--text2); font-size: 12px; text-decoration: none; }
.header a:hover { color: var(--accent); }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 16px; }
.card {
background: var(--surface); border: 1px solid rgba(0,180,216,0.12);
border-radius: 12px; padding: 20px;
}
.card h2 {
font-size: 11px; letter-spacing: 2px; color: var(--accent);
text-transform: uppercase; margin-bottom: 12px;
}
.device-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04);
}
.status-dot {
width: 8px; height: 8px; border-radius: 50%;
flex-shrink: 0;
}
.status-dot.online { background: var(--success); box-shadow: 0 0 6px var(--success); }
.status-dot.offline { background: var(--text2); }
.device-name { font-size: 13px; flex: 1; }
.device-platform { font-size: 10px; color: var(--text2); }
.stat-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 13px; }
.stat-value { color: var(--accent); font-weight: bold; }
.task-item { padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.04); font-size: 13px; }
.task-priority { font-size: 10px; padding: 2px 6px; border-radius: 4px; }
.task-priority.high { background: rgba(255,82,82,0.15); color: var(--danger); }
.task-priority.med { background: rgba(255,171,0,0.15); color: var(--warning); }
.memory-item { padding: 6px 0; font-size: 12px; border-bottom: 1px solid rgba(255,255,255,0.03); }
.memory-cat { color: var(--accent); font-size: 10px; }
.bar-chart { margin-top: 8px; }
.bar-row { display: flex; align-items: center; gap: 8px; margin: 4px 0; }
.bar-label { width: 100px; font-size: 11px; color: var(--text2); text-align: right; }
.bar { height: 16px; background: linear-gradient(90deg, var(--accent2), var(--accent));
border-radius: 4px; min-width: 4px; transition: width 0.5s; }
.bar-count { font-size: 11px; color: var(--text2); width: 30px; }
.empty { color: var(--text2); font-size: 12px; font-style: italic; padding: 12px 0; }
.refresh-btn {
background: var(--surface2); border: 1px solid rgba(0,180,216,0.2);
color: var(--text2); padding: 4px 12px; border-radius: 8px;
font-size: 10px; cursor: pointer; font-family: inherit;
}
.refresh-btn:hover { border-color: var(--accent); color: var(--accent); }
</style>
</head>
<body>
<div class="header">
<h1>J.A.R.V.I.S.</h1>
<span style="color:var(--text2); font-size:11px;">DASHBOARD</span>
<div style="flex:1"></div>
<a href="/">← Chat</a>
<button class="refresh-btn" onclick="loadDashboard()">REFRESH</button>
</div>
<div class="grid">
<div class="card">
<h2>Registered Devices</h2>
<div id="devices"><div class="empty">Loading...</div></div>
</div>
<div class="card">
<h2>Command Analytics (30d)</h2>
<div id="analytics"><div class="empty">Loading...</div></div>
</div>
<div class="card">
<h2>Pending Tasks</h2>
<div id="tasks"><div class="empty">Loading...</div></div>
</div>
<div class="card">
<h2>Long-Term Memory</h2>
<div id="memories"><div class="empty">Loading...</div></div>
</div>
<div class="card">
<h2>Top Tools</h2>
<div id="top-tools"><div class="empty">Loading...</div></div>
</div>
<div class="card">
<h2>Peak Usage Hours</h2>
<div id="peak-hours"><div class="empty">Loading...</div></div>
</div>
</div>
<script>
async function loadDashboard() {
try {
const resp = await fetch('/api/dashboard');
if (resp.status === 401) { location.href = '/'; return; }
const data = await resp.json();
renderDevices(data.devices, data.connected_device_ids);
renderAnalytics(data.analytics);
renderTasks(data.pending_tasks);
renderMemories(data.memories);
renderTopTools(data.analytics.top_tools || []);
renderPeakHours(data.analytics.peak_hours || []);
} catch(e) {
console.error('Dashboard load failed:', e);
}
}
function renderDevices(devices, connected) {
const el = document.getElementById('devices');
if (!devices.length) { el.innerHTML = '<div class="empty">No devices registered</div>'; return; }
el.innerHTML = devices.map(d => {
const online = connected.includes(d.device_id) || d.status === 'online';
return `<div class="device-row">
<div class="status-dot ${online ? 'online' : 'offline'}"></div>
<div class="device-name">${d.alias || d.device_id}</div>
<div class="device-platform">${d.platform || '?'}</div>
</div>`;
}).join('');
}
function renderAnalytics(a) {
const el = document.getElementById('analytics');
el.innerHTML = `
<div class="stat-row"><span>Total Commands</span><span class="stat-value">${a.total_commands || 0}</span></div>
<div class="stat-row"><span>Unique Tools Used</span><span class="stat-value">${(a.top_tools||[]).length}</span></div>
<div class="stat-row"><span>Peak Hour</span><span class="stat-value">${(a.peak_hours||[]).length ? a.peak_hours[0].hour + ':00' : 'N/A'}</span></div>
`;
}
function renderTasks(tasks) {
const el = document.getElementById('tasks');
if (!tasks.length) { el.innerHTML = '<div class="empty">No pending tasks</div>'; return; }
el.innerHTML = tasks.slice(0, 8).map(t => {
const prio = t.priority >= 2 ? 'high' : t.priority >= 1 ? 'med' : '';
const prioLabel = prio ? `<span class="task-priority ${prio}">${prio.toUpperCase()}</span>` : '';
return `<div class="task-item">${prioLabel} ${t.title}</div>`;
}).join('');
}
function renderMemories(mems) {
const el = document.getElementById('memories');
if (!mems.length) { el.innerHTML = '<div class="empty">No memories stored</div>'; return; }
el.innerHTML = mems.slice(0, 10).map(m =>
`<div class="memory-item"><span class="memory-cat">[${m.category}]</span> ${m.key}: ${m.value}</div>`
).join('');
}
function renderTopTools(tools) {
const el = document.getElementById('top-tools');
if (!tools.length) { el.innerHTML = '<div class="empty">No tool usage recorded yet</div>'; return; }
const max = tools[0].count;
el.innerHTML = '<div class="bar-chart">' + tools.map(t =>
`<div class="bar-row">
<div class="bar-label">${t.tool}</div>
<div class="bar" style="width:${Math.max(4, (t.count/max)*200)}px"></div>
<div class="bar-count">${t.count}</div>
</div>`
).join('') + '</div>';
}
function renderPeakHours(hours) {
const el = document.getElementById('peak-hours');
if (!hours.length) { el.innerHTML = '<div class="empty">No usage data yet</div>'; return; }
const max = hours[0].count;
el.innerHTML = '<div class="bar-chart">' + hours.map(h =>
`<div class="bar-row">
<div class="bar-label">${h.hour}:00</div>
<div class="bar" style="width:${Math.max(4, (h.count/max)*200)}px"></div>
<div class="bar-count">${h.count}</div>
</div>`
).join('') + '</div>';
}
loadDashboard();
setInterval(loadDashboard, 30000);
</script>
</body>
</html>