Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Stock Alchemist | System Logs</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-color: #0F172A; | |
| --card-bg: #1E293B; | |
| --text-primary: #F8FAFC; | |
| --text-secondary: #94A3B8; | |
| --accent-green: #10B981; | |
| --accent-blue: #3B82F6; | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Outfit', sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-primary); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 2rem; | |
| } | |
| header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1rem; | |
| padding-bottom: 1rem; | |
| border-bottom: 1px solid rgba(255,255,255,0.1); | |
| } | |
| h1 { font-weight: 700; font-size: 1.5rem; } | |
| .btn { | |
| background: var(--card-bg); | |
| color: var(--text-primary); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| padding: 0.5rem 1rem; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| transition: all 0.2s; | |
| } | |
| .btn:hover { background: #334155; } | |
| #log-container { | |
| flex: 1; | |
| background: #000; | |
| color: #d4d4d4; | |
| font-family: 'Roboto Mono', monospace; | |
| font-size: 0.85rem; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| overflow-y: auto; | |
| border: 1px solid rgba(255,255,255,0.1); | |
| white-space: pre-wrap; | |
| box-shadow: inset 0 0 20px rgba(0,0,0,0.5); | |
| } | |
| .log-entry { margin-bottom: 2px; } | |
| .log-info { color: #d4d4d4; } | |
| .log-error { color: #f87171; } | |
| .log-warning { color: #fbbf24; } | |
| .controls { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| align-items: center; | |
| } | |
| #status-dot { | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| background: var(--accent-green); | |
| display: inline-block; | |
| margin-right: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div style="display:flex; align-items:center; gap:1rem;"> | |
| <h1>📜 System Logs</h1> | |
| <span style="font-size: 0.9rem; color: var(--text-secondary);"><span id="status-dot"></span>Live</span> | |
| </div> | |
| <a href="/" class="btn">← Back to Dashboard</a> | |
| </header> | |
| <div class="controls"> | |
| <button class="btn" onclick="fetchLogs()">Refresh Now</button> | |
| <label style="font-size: 0.9rem; display:flex; align-items:center; gap:0.5rem; cursor:pointer;"> | |
| <input type="checkbox" id="autoScroll" checked> Auto-scroll | |
| </label> | |
| </div> | |
| <div id="log-container">Loading logs...</div> | |
| <script> | |
| const container = document.getElementById('log-container'); | |
| let autoScroll = true; | |
| document.getElementById('autoScroll').addEventListener('change', (e) => { | |
| autoScroll = e.target.checked; | |
| }); | |
| async function fetchLogs() { | |
| try { | |
| const res = await fetch('/api/logs'); | |
| const data = await res.json(); | |
| if (data.logs && data.logs.length > 0) { | |
| container.innerHTML = data.logs.map(line => { | |
| let cls = 'log-info'; | |
| if (line.includes('ERROR') || line.includes('❌')) cls = 'log-error'; | |
| else if (line.includes('WARNING') || line.includes('⚠️')) cls = 'log-warning'; | |
| return `<div class="log-entry ${cls}">${line}</div>`; | |
| }).join(''); | |
| if (autoScroll) { | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| } | |
| } catch (e) { | |
| console.error("Failed to fetch logs", e); | |
| // Only show error if container is empty or we really want to know | |
| if (container.innerText === 'Loading logs...') { | |
| container.innerHTML = `<div class="log-error">Failed to load logs: ${e.message}. \nCheck console for details.</div>`; | |
| } | |
| } | |
| } | |
| // Poll every 3 seconds | |
| fetchLogs(); | |
| setInterval(fetchLogs, 3000); | |
| </script> | |
| </body> | |
| </html> | |