Khanna, Videh Rakesh Rakesh
feat: JARVIS security, cross-device, intelligence & polish overhaul
e2a2dda | <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> | |