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
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>