UNPKG

debug-time-machine-cli

Version:

πŸš€ Debug Time Machine CLI - μ™„μ „ μžλ™ν™”λœ React 디버깅 도ꡬ

351 lines (300 loc) β€’ 9.45 kB
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Debug Time Machine</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; } .container { background: white; border-radius: 20px; padding: 2rem; box-shadow: 0 20px 40px rgba(0,0,0,0.1); text-align: center; max-width: 900px; width: 95%; } .logo { font-size: 3rem; margin-bottom: 1rem; } h1 { color: #2d3748; margin-bottom: 1rem; font-size: 2.5rem; font-weight: bold; } .subtitle { color: #718096; margin-bottom: 2rem; font-size: 1.2rem; } .status { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.5rem; border-radius: 50px; margin-bottom: 2rem; font-weight: 600; } .status.connected { background: #dcfce7; color: #166534; } .status.disconnected { background: #fef2f2; color: #dc2626; } .status-dot { width: 10px; height: 10px; border-radius: 50%; } .status.connected .status-dot { background: #22c55e; animation: pulse 2s infinite; } .status.disconnected .status-dot { background: #ef4444; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } .info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .info-item { background: #f8fafc; padding: 1rem; border-radius: 10px; border: 1px solid #e2e8f0; } .info-item .label { font-size: 0.875rem; color: #64748b; margin-bottom: 0.25rem; } .info-item .value { font-size: 1.25rem; font-weight: bold; color: #1e293b; } .logs { background: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 10px; font-family: 'Monaco', 'Menlo', monospace; font-size: 0.875rem; max-height: 300px; overflow-y: auto; text-align: left; margin-bottom: 2rem; } .log-entry { margin-bottom: 0.5rem; padding: 0.25rem 0; } .log-entry.error { color: #fca5a5; } .log-entry.warning { color: #fbbf24; } .log-entry.info { color: #60a5fa; } .log-entry.success { color: #34d399; } .buttons { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; } .btn { padding: 0.75rem 1.5rem; border: none; border-radius: 10px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.2); } .btn-primary { background: #3b82f6; color: white; } .btn-success { background: #10b981; color: white; } .btn-warning { background: #f59e0b; color: white; } </style> </head> <body> <div class="container"> <div class="logo">πŸ•°οΈ</div> <h1>Debug Time Machine</h1> <p class="subtitle">μ‹œκ°„μ—¬ν–‰ 디버깅 λŒ€μ‹œλ³΄λ“œ</p> <div id="status" class="status disconnected"> <div class="status-dot"></div> <span>λ°±μ—”λ“œ μ—°κ²° 쀑...</span> </div> <div class="info-grid"> <div class="info-item"> <div class="label">μ—°κ²°λœ ν΄λΌμ΄μ–ΈνŠΈ</div> <div class="value" id="clientCount">0</div> </div> <div class="info-item"> <div class="label">λ©”μ‹œμ§€</div> <div class="value" id="messageCount">0</div> </div> <div class="info-item"> <div class="label">μ—…νƒ€μž„</div> <div class="value" id="uptime">0s</div> </div> <div class="info-item"> <div class="label">μƒνƒœ</div> <div class="value" id="serverStatus">λŒ€κΈ°μ€‘</div> </div> </div> <div class="logs" id="logs"> <div class="log-entry info">πŸš€ Debug Time Machine μ‹œμž‘ 쀑...</div> <div class="log-entry info">πŸ“‘ λ°±μ—”λ“œ μ„œλ²„ μ—°κ²° μ‹œλ„ 쀑...</div> </div> <div class="buttons"> <button class="btn btn-primary" onclick="reconnect()">πŸ”„ μž¬μ—°κ²°</button> <button class="btn btn-success" onclick="clearLogs()">🧹 둜그 μ§€μš°κΈ°</button> <button class="btn btn-warning" onclick="exportLogs()">πŸ’Ύ 둜그 μ €μž₯</button> </div> </div> <script> let ws = null; let messageCount = 0; let startTime = Date.now(); const statusEl = document.getElementById('status'); const clientCountEl = document.getElementById('clientCount'); const messageCountEl = document.getElementById('messageCount'); const uptimeEl = document.getElementById('uptime'); const serverStatusEl = document.getElementById('serverStatus'); const logsEl = document.getElementById('logs'); function addLog(message, type = 'info') { const logEntry = document.createElement('div'); logEntry.className = `log-entry ${type}`; logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; logsEl.appendChild(logEntry); logsEl.scrollTop = logsEl.scrollHeight; } function updateStatus(connected) { if (connected) { statusEl.className = 'status connected'; statusEl.innerHTML = '<div class="status-dot"></div><span>λ°±μ—”λ“œ 연결됨</span>'; serverStatusEl.textContent = '싀행쀑'; } else { statusEl.className = 'status disconnected'; statusEl.innerHTML = '<div class="status-dot"></div><span>λ°±μ—”λ“œ μ—°κ²° λŠκΉ€</span>'; serverStatusEl.textContent = 'μ—°κ²° μ‹€νŒ¨'; } } function connectWebSocket() { try { ws = new WebSocket('ws://localhost:4000/ws'); ws.onopen = function() { addLog('βœ… λ°±μ—”λ“œ μ„œλ²„μ— μ—°κ²°λ˜μ—ˆμŠ΅λ‹ˆλ‹€!', 'success'); updateStatus(true); }; ws.onmessage = function(event) { messageCount++; messageCountEl.textContent = messageCount; try { const data = JSON.parse(event.data); addLog(`πŸ“¨ λ©”μ‹œμ§€ μˆ˜μ‹ : ${data.type}`, 'info'); if (data.type === 'CONNECTION' && data.payload?.type === 'welcome') { addLog(`πŸŽ‰ ${data.payload.message}`, 'success'); } } catch (e) { addLog(`πŸ“¨ λ©”μ‹œμ§€: ${event.data}`, 'info'); } }; ws.onclose = function() { addLog('πŸ”Œ λ°±μ—”λ“œ 연결이 λŠμ–΄μ‘ŒμŠ΅λ‹ˆλ‹€.', 'warning'); updateStatus(false); // 5초 ν›„ μž¬μ—°κ²° μ‹œλ„ setTimeout(connectWebSocket, 5000); }; ws.onerror = function(error) { addLog('❌ WebSocket μ—λŸ¬κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.', 'error'); updateStatus(false); }; } catch (error) { addLog('❌ WebSocket μ—°κ²° μ‹€νŒ¨: ' + error.message, 'error'); updateStatus(false); } } function reconnect() { addLog('πŸ”„ μˆ˜λ™ μž¬μ—°κ²° μ‹œλ„...', 'info'); if (ws) { ws.close(); } setTimeout(connectWebSocket, 1000); } function clearLogs() { logsEl.innerHTML = '<div class="log-entry info">🧹 λ‘œκ·Έκ°€ μ§€μ›Œμ‘ŒμŠ΅λ‹ˆλ‹€.</div>'; messageCount = 0; messageCountEl.textContent = '0'; } function exportLogs() { const logs = Array.from(logsEl.children).map(el => el.textContent).join('\n'); const blob = new Blob([logs], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `debug-logs-${new Date().toISOString().slice(0,19).replace(/:/g,'-')}.txt`; a.click(); URL.revokeObjectURL(url); addLog('πŸ’Ύ λ‘œκ·Έκ°€ λ‹€μš΄λ‘œλ“œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', 'success'); } // μ—…νƒ€μž„ μ—…λ°μ΄νŠΈ setInterval(() => { const uptime = Math.floor((Date.now() - startTime) / 1000); uptimeEl.textContent = uptime + 's'; }, 1000); // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ—°κ²° μ‹œμž‘ connectWebSocket(); // λ°±μ—”λ“œ μ„œλ²„ μƒνƒœ 확인 function checkBackendStatus() { fetch('http://localhost:4000/health') .then(response => response.json()) .then(data => { clientCountEl.textContent = data.clients || 0; addLog(`πŸ“Š λ°±μ—”λ“œ μƒνƒœ: ${data.clients}개 ν΄λΌμ΄μ–ΈνŠΈ 연결됨`, 'info'); }) .catch(error => { addLog('❌ λ°±μ—”λ“œ μƒνƒœ 확인 μ‹€νŒ¨', 'error'); }); } // 10μ΄ˆλ§ˆλ‹€ λ°±μ—”λ“œ μƒνƒœ 확인 setInterval(checkBackendStatus, 10000); // 초기 μƒνƒœ 확인 setTimeout(checkBackendStatus, 2000); </script> </body> </html>