index.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>CI Dashboard</title> 7 <link rel="stylesheet" href="/dash/style.css?v=10"> 8 <style> 9 /* Tab styles */ 10 .tabs { display: flex; background: var(--bg-secondary, #161b22); border-bottom: 1px solid var(--border, #30363d); padding: 0 1rem; gap: 0; } 11 .tab { padding: 0.75rem 1.25rem; cursor: pointer; color: var(--text-secondary, #8b949e); border-bottom: 2px solid transparent; font-size: 0.9rem; transition: all 0.2s; background: none; border-top: none; border-left: none; border-right: none; } 12 .tab:hover { color: var(--text-primary, #e6edf3); } 13 .tab.active { color: var(--text-primary, #e6edf3); border-bottom-color: var(--accent, #58a6ff); } 14 .tab-content { display: none; } 15 .tab-content.active { display: block; } 16 17 /* Overview 2-column grid with repos top-right */ 18 .overview-grid { 19 display: grid; 20 grid-template-columns: 1fr 1fr; 21 grid-template-areas: 22 "runners repos" 23 "queue history"; 24 gap: 1.5rem; 25 } 26 .overview-grid .runners-card { grid-area: runners; } 27 .overview-grid .repos-card { grid-area: repos; } 28 .overview-grid .queue-card { grid-area: queue; } 29 .overview-grid .history-card { grid-area: history; } 30 @media (max-width: 900px) { 31 .overview-grid { grid-template-columns: 1fr; grid-template-areas: "runners" "repos" "queue" "history"; } 32 } 33 34 /* Pipeline specific styles */ 35 .pipeline-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; } 36 .metric-card { background: var(--bg-secondary, #161b22); border: 1px solid var(--border, #30363d); border-radius: 8px; padding: 1rem; } 37 .metric-card .label { font-size: 0.75rem; text-transform: uppercase; color: var(--text-secondary, #8b949e); margin-bottom: 0.25rem; } 38 .metric-card .value { font-size: 1.75rem; font-weight: 600; color: var(--text-primary, #e6edf3); } 39 .metric-card .value.green { color: #3fb950; } 40 .metric-card .value.blue { color: #58a6ff; } 41 .events-container { background: var(--bg-tertiary, #21262d); border-radius: 6px; padding: 0.75rem; font-family: monospace; font-size: 0.8rem; max-height: 350px; overflow-y: auto; margin-top: 1rem; } 42 .event-line { padding: 0.25rem 0; border-bottom: 1px solid var(--border, #30363d); } 43 .event-line:last-child { border-bottom: none; } 44 .event-time { color: var(--text-secondary, #8b949e); } 45 .event-type { font-weight: 500; margin: 0 0.5rem; } 46 .event-type.TASK_START { color: #3fb950; } 47 .event-type.TASK_END { color: #d29922; } 48 .event-type.ERROR, .event-type.ISSUE { color: #f85149; } 49 .event-type.WORKSPACE_CREATE, .event-type.WORKSPACE_REMOVE { color: #8b949e; } 50 </style> 51 </head> 52 <body> 53 <noscript><div style="background:yellow;color:black;padding:1em;text-align:center;">JavaScript is disabled. This dashboard requires JavaScript.</div></noscript> 54 <header> 55 <h1>CI Server Dashboard</h1> 56 <div class="header-right"> 57 <div class="system-stats-container"> 58 <div class="system-stats" id="system-stats-local"> 59 <span class="server-label">Source</span> 60 <div class="stat" title="Source CPU Usage"> 61 <span class="stat-label">CPU</span> 62 <span class="stat-value" id="local-cpu-percent">--%</span> 63 </div> 64 <div class="stat" title="Source Memory Usage"> 65 <span class="stat-label">MEM</span> 66 <span class="stat-value" id="local-mem-percent">--%</span> 67 </div> 68 <div class="stat" title="Source Disk Usage"> 69 <span class="stat-label">DISK</span> 70 <span class="stat-value" id="local-disk-percent">--%</span> 71 </div> 72 </div> 73 <div class="system-stats" id="system-stats-ci"> 74 <span class="server-label">CI</span> 75 <div class="stat" title="CI CPU Usage"> 76 <span class="stat-label">CPU</span> 77 <span class="stat-value" id="ci-cpu-percent">--%</span> 78 </div> 79 <div class="stat" title="CI Memory Usage"> 80 <span class="stat-label">MEM</span> 81 <span class="stat-value" id="ci-mem-percent">--%</span> 82 </div> 83 <div class="stat" title="CI Disk Usage"> 84 <span class="stat-label">DISK</span> 85 <span class="stat-value" id="ci-disk-percent">--%</span> 86 </div> 87 </div> 88 </div> 89 <div class="health-status" id="health-status"> 90 <span class="status-dot"></span> 91 <span class="status-text">Connecting...</span> 92 </div> 93 </div> 94 </header> 95 96 <div class="tabs"> 97 <button class="tab active" data-tab="overview">Overview</button> 98 <button class="tab" data-tab="pipeline">Pipeline Monitor</button> 99 </div> 100 101 <main> 102 <!-- Overview Tab --> 103 <div id="overview" class="tab-content active"> 104 <div class="overview-grid"> 105 <section class="card runners-card"> 106 <h2>Runners</h2> 107 <div class="card-content"> 108 <table id="runners-table"> 109 <thead> 110 <tr> 111 <th>Runner</th> 112 <th>Status</th> 113 <th>Current Job</th> 114 <th>Uptime</th> 115 </tr> 116 </thead> 117 <tbody> 118 <tr><td colspan="4" class="loading">Loading...</td></tr> 119 </tbody> 120 </table> 121 </div> 122 </section> 123 124 <section class="card repos-card"> 125 <h2>Repositories</h2> 126 <div class="card-content"> 127 <ul id="repos-list" class="repo-list"> 128 <li class="loading">Loading...</li> 129 </ul> 130 </div> 131 </section> 132 133 <section class="card queue-card"> 134 <h2>Queue <span class="badge" id="queue-count">0</span></h2> 135 <div class="card-content"> 136 <ul id="queue-list" class="job-list"> 137 <li class="loading">Loading...</li> 138 </ul> 139 </div> 140 </section> 141 142 <section class="card history-card"> 143 <h2>Recent Runs</h2> 144 <div class="card-content"> 145 <table id="history-table"> 146 <thead> 147 <tr> 148 <th>Repo</th> 149 <th>Branch</th> 150 <th>Job</th> 151 <th>Result</th> 152 <th>Duration</th> 153 <th>When</th> 154 </tr> 155 </thead> 156 <tbody> 157 <tr><td colspan="6" class="loading">Loading...</td></tr> 158 </tbody> 159 </table> 160 </div> 161 </section> 162 </div> 163 </div> 164 165 <!-- Pipeline Monitor Tab --> 166 <div id="pipeline" class="tab-content"> 167 <div class="pipeline-grid"> 168 <div class="metric-card"> 169 <div class="label">System Load</div> 170 <div class="value" id="pipeline-load">--</div> 171 </div> 172 <div class="metric-card"> 173 <div class="label">Memory Used</div> 174 <div class="value" id="pipeline-memory">--</div> 175 </div> 176 <div class="metric-card"> 177 <div class="label">Active Tasks</div> 178 <div class="value blue" id="pipeline-active">--</div> 179 </div> 180 <div class="metric-card"> 181 <div class="label">Completed</div> 182 <div class="value green" id="pipeline-completed">--</div> 183 </div> 184 </div> 185 186 <div class="dashboard-grid"> 187 <section class="card"> 188 <h2>Runner Activity</h2> 189 <div class="card-content"> 190 <table id="pipeline-runners-table"> 191 <thead> 192 <tr> 193 <th>Runner</th> 194 <th>Task</th> 195 <th>Repository</th> 196 <th>Status</th> 197 </tr> 198 </thead> 199 <tbody> 200 <tr><td colspan="4" class="loading">Loading...</td></tr> 201 </tbody> 202 </table> 203 </div> 204 </section> 205 206 <section class="card"> 207 <h2>Event Stream</h2> 208 <div class="card-content"> 209 <div class="events-container" id="pipeline-events"> 210 <div class="loading">Loading events...</div> 211 </div> 212 </div> 213 </section> 214 </div> 215 </div> 216 </main> 217 218 <footer> 219 <span>Last updated: <span id="last-updated">Never</span></span> 220 <span>Refresh interval: <span id="refresh-interval">5s</span></span> 221 </footer> 222 223 <script> 224 const CONFIG = { 225 apiBaseUrl: "", 226 endpoints: { 227 health: "/dash/api/health/status", 228 runners: "/dash/api/runners", 229 queue: "/dash/api/queue", 230 history: "/dash/api/jobs/history", 231 repos: "/dash/api/repos", 232 system: "/dash/api/system", 233 pipelineMetrics: "/dash/api/pipeline/metrics", 234 pipelineEvents: "/dash/api/pipeline/events", 235 pipelineRunners: "/dash/api/pipeline/runners" 236 }, 237 refreshInterval: 5000, 238 historyLimit: 25, 239 requestTimeout: 10000, 240 dateFormat: { locale: "en-US", options: { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" } } 241 }; 242 243 // Tab switching 244 document.querySelectorAll(".tab").forEach(tab => { 245 tab.addEventListener("click", () => { 246 document.querySelectorAll(".tab").forEach(t => t.classList.remove("active")); 247 document.querySelectorAll(".tab-content").forEach(c => c.classList.remove("active")); 248 tab.classList.add("active"); 249 document.getElementById(tab.dataset.tab).classList.add("active"); 250 }); 251 }); 252 253 // Pipeline data fetching 254 async function updatePipeline() { 255 try { 256 const [metricsRes, eventsRes, runnersRes] = await Promise.all([ 257 fetch(CONFIG.endpoints.pipelineMetrics), 258 fetch(CONFIG.endpoints.pipelineEvents), 259 fetch(CONFIG.endpoints.pipelineRunners) 260 ]); 261 262 if (metricsRes.ok) { 263 const m = await metricsRes.json(); 264 document.getElementById("pipeline-load").textContent = (m.load || 0).toFixed(1); 265 document.getElementById("pipeline-memory").textContent = ((m.mem_mb || 0) / 1024).toFixed(1) + " GB"; 266 document.getElementById("pipeline-active").textContent = m.active_tasks || 0; 267 document.getElementById("pipeline-completed").textContent = m.completed || 0; 268 } 269 270 if (eventsRes.ok) { 271 const events = await eventsRes.json(); 272 const container = document.getElementById("pipeline-events"); 273 if (events.length > 0) { 274 container.innerHTML = events.slice().reverse().map(e => 275 `<div class="event-line"><span class="event-time">${e.time}</span><span class="event-type ${e.type}">[${e.type}]</span>${e.message}</div>` 276 ).join(""); 277 } else { 278 container.innerHTML = "<div>No events yet</div>"; 279 } 280 } 281 282 if (runnersRes.ok) { 283 const runners = await runnersRes.json(); 284 const tbody = document.querySelector("#pipeline-runners-table tbody"); 285 if (runners.length > 0) { 286 tbody.innerHTML = runners.map(r => ` 287 <tr> 288 <td>${r.name}</td> 289 <td>${r.task || "--"}</td> 290 <td>${r.repo || "--"}</td> 291 <td><span class="status-badge ${r.status}">${r.status}</span></td> 292 </tr> 293 `).join(""); 294 } 295 } 296 } catch (e) { 297 console.error("Pipeline update error:", e); 298 } 299 } 300 301 // Start pipeline updates 302 setInterval(updatePipeline, 5000); 303 updatePipeline(); 304 </script> 305 <script src="/dash/dashboard.js?v=9"></script> 306 </body> 307 </html>