Spaces:
Running
Running
| from fastapi import APIRouter, status | |
| from fastapi.responses import JSONResponse, HTMLResponse | |
| import platform | |
| import psutil | |
| # Initialize the router for this feature | |
| router = APIRouter( | |
| prefix="/server-status", | |
| tags=["Server Info"], | |
| responses={404: {"description": "Not found"}}, | |
| ) | |
| # ========================== | |
| # π API Endpoint | |
| # ========================== | |
| def get_server_status(): | |
| """ | |
| Retrieves detailed hardware and software status of the server. | |
| """ | |
| # --- System Information --- | |
| system_info = { | |
| "system": platform.system(), | |
| "node_name": platform.node(), | |
| "release": platform.release(), | |
| "version": platform.version(), | |
| "machine": platform.machine(), | |
| "processor": platform.processor(), | |
| } | |
| # --- CPU Information --- | |
| # Note: cpu_percent(interval=None) is non-blocking but requires a previous call | |
| # or it returns 0.0 on first call. For an API, interval=0.1 is a good compromise. | |
| freq = psutil.cpu_freq() | |
| cpu_info = { | |
| "physical_cores": psutil.cpu_count(logical=False), | |
| "total_cores": psutil.cpu_count(logical=True), | |
| "cpu_usage_percent": psutil.cpu_percent(interval=0.1), | |
| "cpu_frequency_mhz": f"{freq.current:.2f}" if freq else "N/A", | |
| } | |
| # --- Memory Information (RAM) --- | |
| virtual_memory = psutil.virtual_memory() | |
| memory_info = { | |
| "total_gb": f"{virtual_memory.total / (1024**3):.2f}", | |
| "available_gb": f"{virtual_memory.available / (1024**3):.2f}", | |
| "used_gb": f"{virtual_memory.used / (1024**3):.2f}", | |
| "usage_percent": virtual_memory.percent, | |
| } | |
| # --- Disk Usage Information (Root partition) --- | |
| try: | |
| disk_usage = psutil.disk_usage('/') | |
| disk_info = { | |
| "total_gb": f"{disk_usage.total / (1024**3):.2f}", | |
| "used_gb": f"{disk_usage.used / (1024**3):.2f}", | |
| "free_gb": f"{disk_usage.free / (1024**3):.2f}", | |
| "usage_percent": disk_usage.percent, | |
| } | |
| except Exception: | |
| disk_info = {"error": "Could not read disk usage"} | |
| # --- Network Information (Simple) --- | |
| net_io = psutil.net_io_counters() | |
| network_info = { | |
| "bytes_sent_mb": f"{net_io.bytes_sent / (1024**2):.2f}", | |
| "bytes_recv_mb": f"{net_io.bytes_recv / (1024**2):.2f}", | |
| } | |
| response_data = { | |
| "system_information": system_info, | |
| "cpu_status": cpu_info, | |
| "memory_status": memory_info, | |
| "disk_status": disk_info, | |
| "network_io": network_info, | |
| } | |
| return JSONResponse(content=response_data, status_code=status.HTTP_200_OK) | |
| # ========================== | |
| # π₯οΈ Dashboard UI Endpoint | |
| # ========================== | |
| async def server_status_ui(): | |
| html_content = """ | |
| <!DOCTYPE html> | |
| <html lang="en" data-theme="light"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Server Monitor | by Sam</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #3b82f6; | |
| --primary-hover: #2563eb; | |
| --bg-body: #f8fafc; | |
| --bg-card: #ffffff; | |
| --text-main: #0f172a; | |
| --text-sub: #64748b; | |
| --border: #e2e8f0; | |
| --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --danger: #ef4444; | |
| } | |
| [data-theme="dark"] { | |
| --primary: #60a5fa; | |
| --bg-body: #0f172a; | |
| --bg-card: #1e293b; | |
| --text-main: #f8fafc; | |
| --text-sub: #94a3b8; | |
| --border: #334155; | |
| --shadow: 0 10px 15px -3px rgb(0 0 0 / 0.5); | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; transition: all 0.3s ease; } | |
| body { | |
| font-family: 'Plus Jakarta Sans', sans-serif; | |
| background-color: var(--bg-body); | |
| color: var(--text-main); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 2rem 1rem; | |
| } | |
| .navbar { | |
| width: 100%; max-width: 1000px; display: flex; | |
| justify-content: space-between; align-items: center; margin-bottom: 2rem; | |
| } | |
| .brand { font-size: 1.5rem; font-weight: 800; display: flex; align-items: center; gap: 0.5rem; } | |
| .brand span { color: var(--primary); } | |
| .theme-toggle { | |
| background: var(--bg-card); border: 1px solid var(--border); | |
| color: var(--text-main); padding: 8px; border-radius: 50%; cursor: pointer; | |
| } | |
| /* Grid Layout */ | |
| .dashboard { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| width: 100%; | |
| max-width: 1000px; | |
| } | |
| .card { | |
| background: var(--bg-card); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| border: 1px solid var(--border); | |
| box-shadow: var(--shadow); | |
| } | |
| .card-header { | |
| font-size: 0.9rem; font-weight: 600; color: var(--text-sub); | |
| text-transform: uppercase; letter-spacing: 0.5px; | |
| margin-bottom: 1rem; display: flex; justify-content: space-between; | |
| } | |
| .stat-value { | |
| font-size: 2rem; font-weight: 700; color: var(--text-main); | |
| font-family: 'JetBrains Mono', monospace; | |
| } | |
| .stat-sub { font-size: 0.9rem; color: var(--text-sub); margin-top: 0.2rem; } | |
| /* Progress Bars */ | |
| .progress-container { | |
| height: 12px; background: var(--border); border-radius: 6px; | |
| margin-top: 1rem; overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; width: 0%; background: var(--primary); | |
| border-radius: 6px; transition: width 0.5s ease-in-out; | |
| } | |
| /* System List */ | |
| .sys-list { list-style: none; } | |
| .sys-list li { | |
| display: flex; justify-content: space-between; | |
| padding: 0.5rem 0; border-bottom: 1px solid var(--border); | |
| font-size: 0.9rem; | |
| } | |
| .sys-list li:last-child { border-bottom: none; } | |
| .sys-label { color: var(--text-sub); } | |
| .sys-val { font-weight: 600; } | |
| /* Footer */ | |
| .dev-footer { | |
| margin-top: 3rem; text-align: center; font-size: 0.9rem; color: var(--text-sub); | |
| padding: 1rem; border-top: 1px solid var(--border); width: 100%; max-width: 600px; | |
| } | |
| .dev-badge { | |
| display: inline-block; background: var(--bg-card); padding: 5px 15px; | |
| border-radius: 20px; border: 1px solid var(--border); font-weight: 500; margin-top: 5px; | |
| } | |
| .dev-name { color: var(--primary); font-weight: 700; } | |
| .refresh-dot { | |
| width: 10px; height: 10px; background-color: var(--success); | |
| border-radius: 50%; display: inline-block; animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } } | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="navbar"> | |
| <div class="brand">π Server Monitor <span class="refresh-dot" title="Live Updates"></span></div> | |
| <button class="theme-toggle" onclick="toggleTheme()" id="themeBtn">π</button> | |
| </nav> | |
| <div class="dashboard"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <span>CPU Usage</span> | |
| <span id="cpuCores">-- Cores</span> | |
| </div> | |
| <div class="stat-value" id="cpuPercent">0%</div> | |
| <div class="progress-container"> | |
| <div class="progress-bar" id="cpuBar"></div> | |
| </div> | |
| <div class="stat-sub" id="cpuFreq">Freq: -- MHz</div> | |
| </div> | |
| <div class="card"> | |
| <div class="card-header">Memory (RAM)</div> | |
| <div class="stat-value" id="memPercent">0%</div> | |
| <div class="progress-container"> | |
| <div class="progress-bar" id="memBar"></div> | |
| </div> | |
| <div class="stat-sub" id="memDetails">Used: -- / -- GB</div> | |
| </div> | |
| <div class="card"> | |
| <div class="card-header">Disk (Root)</div> | |
| <div class="stat-value" id="diskPercent">0%</div> | |
| <div class="progress-container"> | |
| <div class="progress-bar" id="diskBar"></div> | |
| </div> | |
| <div class="stat-sub" id="diskDetails">Free: -- GB</div> | |
| </div> | |
| <div class="card"> | |
| <div class="card-header">Network I/O</div> | |
| <div class="sys-list"> | |
| <li> | |
| <span class="sys-label">β¬οΈ Received</span> | |
| <span class="sys-val" id="netRecv">-- MB</span> | |
| </li> | |
| <li> | |
| <span class="sys-label">β¬οΈ Sent</span> | |
| <span class="sys-val" id="netSent">-- MB</span> | |
| </li> | |
| </div> | |
| </div> | |
| <div class="card" style="grid-column: 1 / -1;"> | |
| <div class="card-header">System Information</div> | |
| <ul class="sys-list" id="sysList"> | |
| </ul> | |
| </div> | |
| </div> | |
| <footer class="dev-footer"> | |
| <div>Designed & Developed by</div> | |
| <div class="dev-badge"> | |
| <span class="dev-name">Sameer Banchhor</span> | Data Scientist | |
| </div> | |
| </footer> | |
| <script> | |
| // Theme Logic | |
| const html = document.documentElement; | |
| const themeBtn = document.getElementById('themeBtn'); | |
| function toggleTheme() { | |
| const current = html.getAttribute('data-theme'); | |
| const next = current === 'light' ? 'dark' : 'light'; | |
| html.setAttribute('data-theme', next); | |
| themeBtn.textContent = next === 'light' ? 'π' : 'βοΈ'; | |
| localStorage.setItem('theme', next); | |
| } | |
| const savedTheme = localStorage.getItem('theme') || 'light'; | |
| html.setAttribute('data-theme', savedTheme); | |
| themeBtn.textContent = savedTheme === 'light' ? 'π' : 'βοΈ'; | |
| // Data Fetching Logic | |
| async function fetchData() { | |
| try { | |
| const res = await fetch('/server-status/full-info'); | |
| const data = await res.json(); | |
| updateUI(data); | |
| } catch (error) { | |
| console.error("Failed to fetch status:", error); | |
| } | |
| } | |
| function updateUI(data) { | |
| // CPU | |
| const cpuP = data.cpu_status.cpu_usage_percent; | |
| document.getElementById('cpuPercent').textContent = cpuP + '%'; | |
| document.getElementById('cpuBar').style.width = cpuP + '%'; | |
| setColor('cpuBar', cpuP); | |
| document.getElementById('cpuCores').textContent = `${data.cpu_status.physical_cores}P / ${data.cpu_status.total_cores}L Cores`; | |
| document.getElementById('cpuFreq').textContent = `Freq: ${data.cpu_status.cpu_frequency_mhz} MHz`; | |
| // RAM | |
| const memP = data.memory_status.usage_percent; | |
| document.getElementById('memPercent').textContent = memP + '%'; | |
| document.getElementById('memBar').style.width = memP + '%'; | |
| setColor('memBar', memP); | |
| document.getElementById('memDetails').textContent = `Used: ${data.memory_status.used_gb} / ${data.memory_status.total_gb} GB`; | |
| // Disk | |
| const diskP = data.disk_status.usage_percent; | |
| document.getElementById('diskPercent').textContent = diskP + '%'; | |
| document.getElementById('diskBar').style.width = diskP + '%'; | |
| setColor('diskBar', diskP); | |
| document.getElementById('diskDetails').textContent = `Free: ${data.disk_status.free_gb} GB`; | |
| // Network | |
| document.getElementById('netRecv').textContent = data.network_io.bytes_recv_mb + ' MB'; | |
| document.getElementById('netSent').textContent = data.network_io.bytes_sent_mb + ' MB'; | |
| // System Info (Only populate once if empty) | |
| const sysList = document.getElementById('sysList'); | |
| if(sysList.children.length === 0) { | |
| const info = data.system_information; | |
| for (const [key, value] of Object.entries(info)) { | |
| const li = document.createElement('li'); | |
| li.innerHTML = `<span class="sys-label">${key}</span><span class="sys-val">${value}</span>`; | |
| sysList.appendChild(li); | |
| } | |
| } | |
| } | |
| function setColor(id, percent) { | |
| const el = document.getElementById(id); | |
| if(percent < 60) el.style.backgroundColor = 'var(--primary)'; | |
| else if(percent < 85) el.style.backgroundColor = 'var(--warning)'; | |
| else el.style.backgroundColor = 'var(--danger)'; | |
| } | |
| // Init | |
| fetchData(); | |
| // Refresh every 2 seconds | |
| setInterval(fetchData, 2000); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| return html_content |