UNPKG

shell-mirror

Version:

Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.

440 lines (403 loc) 15.6 kB
<!DOCTYPE html> <html> <head> <title>Shell Mirror Debug Dashboard</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #1e1e1e; color: #d4d4d4; } .header { border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 20px; } .header h1 { margin: 0; color: #4ec9b0; } .header .subtitle { color: #808080; margin-top: 5px; } .tabs { display: flex; margin-bottom: 20px; border-bottom: 1px solid #333; } .tab { padding: 10px 20px; background: #2d2d30; border: none; color: #d4d4d4; cursor: pointer; margin-right: 2px; } .tab.active { background: #007acc; color: white; } .tab-content { display: none; } .tab-content.active { display: block; } .log-container { background: #2d2d30; border-radius: 4px; padding: 15px; margin-bottom: 20px; max-height: 500px; overflow-y: auto; } .log-entry { margin-bottom: 10px; padding: 8px; border-left: 4px solid #333; background: #252526; border-radius: 2px; } .log-entry.DEBUG { border-left-color: #007acc; } .log-entry.INFO { border-left-color: #4ec9b0; } .log-entry.WARN { border-left-color: #ffcc02; } .log-entry.ERROR { border-left-color: #f14c4c; } .log-entry.CRITICAL { border-left-color: #ff6b6b; } .timestamp { color: #808080; font-size: 0.9em; margin-right: 10px; } .level { font-weight: bold; padding: 2px 6px; border-radius: 3px; font-size: 0.8em; margin-right: 10px; } .level.DEBUG { background: #007acc; color: white; } .level.INFO { background: #4ec9b0; color: white; } .level.WARN { background: #ffcc02; color: black; } .level.ERROR { background: #f14c4c; color: white; } .level.CRITICAL { background: #ff6b6b; color: white; } .message { margin-top: 5px; } .data { background: #1e1e1e; padding: 10px; border-radius: 4px; margin-top: 5px; font-family: monospace; font-size: 0.9em; white-space: pre-wrap; overflow-x: auto; } .controls { margin-bottom: 20px; } .btn { background: #007acc; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin-right: 10px; } .btn:hover { background: #005a9e; } .btn.danger { background: #f14c4c; } .btn.danger:hover { background: #d32f2f; } .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; } .status-card { background: #2d2d30; padding: 15px; border-radius: 4px; border-left: 4px solid #007acc; } .status-card h3 { margin: 0 0 10px 0; color: #4ec9b0; } .status-card .value { font-size: 1.5em; font-weight: bold; margin-bottom: 5px; } .status-card .description { color: #808080; font-size: 0.9em; } .error { color: #f14c4c; } .success { color: #4ec9b0; } .warning { color: #ffcc02; } </style> </head> <body> <div class="header"> <h1>🔍 Shell Mirror Debug Dashboard</h1> <div class="subtitle">Real-time debugging and logging for Shell Mirror system</div> </div> <div class="status-grid" id="statusGrid"> <!-- Status cards will be populated by JavaScript --> </div> <div class="tabs"> <button class="tab active" onclick="showTab('server-logs')">Server Logs</button> <button class="tab" onclick="showTab('client-logs')">Client Logs</button> <button class="tab" onclick="showTab('agent-status')">Agent Status</button> <button class="tab" onclick="showTab('system-info')">System Info</button> </div> <div id="server-logs" class="tab-content active"> <div class="controls"> <button class="btn" onclick="refreshServerLogs()">Refresh Server Logs</button> <button class="btn" onclick="downloadServerLogs()">Download</button> <button class="btn danger" onclick="clearServerLogs()">Clear</button> <label> Level: <select id="serverLogLevel" onchange="refreshServerLogs()"> <option value="">All</option> <option value="debug">Debug</option> <option value="info">Info</option> <option value="warn">Warning</option> <option value="error">Error</option> </select> </label> </div> <div id="serverLogContainer" class="log-container"> <div class="loading">Loading server logs...</div> </div> </div> <div id="client-logs" class="tab-content"> <div class="controls"> <button class="btn" onclick="refreshClientLogs()">Refresh Client Logs</button> <button class="btn" onclick="downloadClientLogs()">Download</button> <button class="btn danger" onclick="clearClientLogs()">Clear</button> </div> <div id="clientLogContainer" class="log-container"> <div class="loading">Loading client logs...</div> </div> </div> <div id="agent-status" class="tab-content"> <div class="controls"> <button class="btn" onclick="refreshAgentStatus()">Refresh Agent Status</button> </div> <div id="agentStatusContainer" class="log-container"> <div class="loading">Loading agent status...</div> </div> </div> <div id="system-info" class="tab-content"> <div class="controls"> <button class="btn" onclick="refreshSystemInfo()">Refresh System Info</button> </div> <div id="systemInfoContainer" class="log-container"> <div class="loading">Loading system info...</div> </div> </div> <script> let autoRefresh = true; let refreshInterval; function showTab(tabName) { // Hide all tab contents document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.tab').forEach(tab => { tab.classList.remove('active'); }); // Show selected tab document.getElementById(tabName).classList.add('active'); event.target.classList.add('active'); // Load content based on tab switch(tabName) { case 'server-logs': refreshServerLogs(); break; case 'client-logs': refreshClientLogs(); break; case 'agent-status': refreshAgentStatus(); break; case 'system-info': refreshSystemInfo(); break; } } async function refreshServerLogs() { const container = document.getElementById('serverLogContainer'); const level = document.getElementById('serverLogLevel').value; try { const url = `/php-backend/api/debug-logs.php?lines=50${level ? '&level=' + level : ''}`; const response = await fetch(url, { credentials: 'include' }); const data = await response.json(); if (data.success) { displayLogs(container, data.data.logs); } else { container.innerHTML = `<div class="error">Failed to load server logs: ${data.message}</div>`; } } catch (error) { container.innerHTML = `<div class="error">Error loading server logs: ${error.message}</div>`; } } function refreshClientLogs() { const container = document.getElementById('clientLogContainer'); if (window.clientLogger) { const logs = window.clientLogger.getLogs({ limit: 50 }); displayLogs(container, logs); } else { container.innerHTML = '<div class="warning">Client logger not available</div>'; } } async function refreshAgentStatus() { const container = document.getElementById('agentStatusContainer'); try { const response = await fetch('/php-backend/api/agents-list.php', { credentials: 'include' }); const data = await response.json(); if (data.success) { displayAgentStatus(container, data.data); } else { container.innerHTML = `<div class="error">Failed to load agent status: ${data.message}</div>`; } } catch (error) { container.innerHTML = `<div class="error">Error loading agent status: ${error.message}</div>`; } } async function refreshSystemInfo() { const container = document.getElementById('systemInfoContainer'); const systemInfo = { browser: { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, cookieEnabled: navigator.cookieEnabled, onLine: navigator.onLine }, window: { innerWidth: window.innerWidth, innerHeight: window.innerHeight, devicePixelRatio: window.devicePixelRatio, location: window.location.href }, timing: { loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart, domReady: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart } }; container.innerHTML = `<div class="data">${JSON.stringify(systemInfo, null, 2)}</div>`; } function displayLogs(container, logs) { if (!logs || logs.length === 0) { container.innerHTML = '<div>No logs available</div>'; return; } const logsHtml = logs.reverse().map(log => { const timestamp = new Date(log.timestamp).toLocaleString(); const dataHtml = log.data ? `<div class="data">${JSON.stringify(log.data, null, 2)}</div>` : ''; return ` <div class="log-entry ${log.level}"> <span class="timestamp">${timestamp}</span> <span class="level ${log.level}">${log.level}</span> <div class="message">${log.message}</div> ${dataHtml} </div> `; }).join(''); container.innerHTML = logsHtml; } function displayAgentStatus(container, data) { const agentsHtml = data.agents.map(agent => { const statusClass = agent.onlineStatus === 'online' ? 'success' : 'error'; const lastSeen = new Date(agent.lastSeen * 1000).toLocaleString(); return ` <div class="log-entry"> <strong>${agent.machineName || agent.agentId}</strong> <div class="message"> Status: <span class="${statusClass}">${agent.onlineStatus}</span><br> Owner: ${agent.ownerEmail}<br> Last Seen: ${lastSeen}<br> Version: ${agent.agentVersion || 'Unknown'} </div> </div> `; }).join(''); container.innerHTML = agentsHtml || '<div>No agents registered</div>'; } function downloadServerLogs() { window.open('/php-backend/api/debug-logs.php?format=text', '_blank'); } function downloadClientLogs() { if (window.clientLogger) { window.clientLogger.downloadLogs(); } } function clearClientLogs() { if (window.clientLogger) { window.clientLogger.clearLogs(); refreshClientLogs(); } } function updateStatusCards() { // This would update the status grid with real-time info // Implementation depends on available APIs } // Auto-refresh functionality function startAutoRefresh() { if (refreshInterval) clearInterval(refreshInterval); refreshInterval = setInterval(() => { const activeTab = document.querySelector('.tab-content.active'); if (activeTab) { const tabId = activeTab.id; switch(tabId) { case 'server-logs': refreshServerLogs(); break; case 'agent-status': refreshAgentStatus(); break; } } }, 10000); // Refresh every 10 seconds } // Initialize document.addEventListener('DOMContentLoaded', function() { refreshServerLogs(); startAutoRefresh(); }); // Keyboard shortcuts document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey) { switch(e.key) { case 'R': e.preventDefault(); refreshServerLogs(); break; case 'C': e.preventDefault(); refreshClientLogs(); break; case 'A': e.preventDefault(); refreshAgentStatus(); break; } } }); console.log('🔍 Debug Dashboard loaded. Keyboard shortcuts: Ctrl+Shift+R (server logs), Ctrl+Shift+C (client logs), Ctrl+Shift+A (agent status)'); </script> </body> </html>