/ frontend / index.html
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>