debug-time-machine-cli
Version:
π Debug Time Machine CLI - μμ μλνλ React λλ²κΉ λꡬ
351 lines (300 loc) β’ 9.45 kB
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>