/ frontend / style.css
style.css
  1  * {
  2      margin: 0;
  3      padding: 0;
  4      box-sizing: border-box;
  5  }
  6  
  7  :root {
  8      --bg-primary: #0d1117;
  9      --bg-secondary: #161b22;
 10      --bg-tertiary: #21262d;
 11      --border-color: #30363d;
 12      --text-primary: #e6edf3;
 13      --text-secondary: #8b949e;
 14      --text-muted: #6e7681;
 15      --accent-blue: #58a6ff;
 16      --accent-green: #3fb950;
 17      --accent-red: #f85149;
 18      --accent-yellow: #d29922;
 19      --accent-orange: #db6d28;
 20      --accent-purple: #a371f7;
 21  }
 22  
 23  body {
 24      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
 25      background: var(--bg-primary);
 26      color: var(--text-primary);
 27      min-height: 100vh;
 28      line-height: 1.5;
 29  }
 30  
 31  header {
 32      background: var(--bg-secondary);
 33      border-bottom: 1px solid var(--border-color);
 34      padding: 1rem 2rem;
 35      display: flex;
 36      justify-content: space-between;
 37      align-items: center;
 38      position: sticky;
 39      top: 0;
 40      z-index: 100;
 41  }
 42  
 43  header h1 {
 44      font-size: 1.5rem;
 45      font-weight: 600;
 46  }
 47  
 48  .header-right {
 49      display: flex;
 50      align-items: center;
 51      gap: 1rem;
 52  }
 53  
 54  .system-stats {
 55      display: flex;
 56      gap: 0.75rem;
 57  }
 58  
 59  .system-stats .stat {
 60      display: flex;
 61      flex-direction: column;
 62      align-items: center;
 63      padding: 0.375rem 0.75rem;
 64      background: var(--bg-tertiary);
 65      border-radius: 6px;
 66      border: 1px solid var(--border-color);
 67      min-width: 60px;
 68  }
 69  
 70  .system-stats .stat-label {
 71      font-size: 0.625rem;
 72      font-weight: 600;
 73      color: var(--text-muted);
 74      text-transform: uppercase;
 75      letter-spacing: 0.05em;
 76  }
 77  
 78  .system-stats .stat-value {
 79      font-size: 0.875rem;
 80      font-weight: 600;
 81      font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
 82  }
 83  
 84  .stat-value.stat-normal {
 85      color: var(--accent-green);
 86  }
 87  
 88  .stat-value.stat-warning {
 89      color: var(--accent-yellow);
 90  }
 91  
 92  .stat-value.stat-critical {
 93      color: var(--accent-red);
 94  }
 95  
 96  .stat-value.stat-offline {
 97      color: var(--text-muted);
 98  }
 99  
100  .system-stats-container {
101      display: flex;
102      gap: 1rem;
103  }
104  
105  .system-stats .server-label {
106      font-size: 0.625rem;
107      font-weight: 700;
108      color: var(--text-secondary);
109      text-transform: uppercase;
110      letter-spacing: 0.05em;
111      padding: 0.375rem 0;
112      border-right: 1px solid var(--border-color);
113      padding-right: 0.75rem;
114      margin-right: 0.25rem;
115  }
116  
117  .health-status {
118      display: flex;
119      align-items: center;
120      gap: 0.5rem;
121      padding: 0.5rem 1rem;
122      background: var(--bg-tertiary);
123      border-radius: 6px;
124      border: 1px solid var(--border-color);
125  }
126  
127  .status-dot {
128      width: 10px;
129      height: 10px;
130      border-radius: 50%;
131      background: var(--text-muted);
132      animation: pulse 2s infinite;
133  }
134  
135  .health-status.healthy .status-dot {
136      background: var(--accent-green);
137  }
138  
139  .health-status.unhealthy .status-dot {
140      background: var(--accent-red);
141  }
142  
143  .health-status.degraded .status-dot {
144      background: var(--accent-yellow);
145  }
146  
147  @keyframes pulse {
148      0%, 100% { opacity: 1; }
149      50% { opacity: 0.5; }
150  }
151  
152  main {
153      padding: 2rem;
154      max-width: 1800px;
155      margin: 0 auto;
156  }
157  
158  .dashboard-grid {
159      display: grid;
160      grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
161      gap: 1.5rem;
162  }
163  
164  .card {
165      background: var(--bg-secondary);
166      border: 1px solid var(--border-color);
167      border-radius: 8px;
168      overflow: hidden;
169  }
170  
171  .card.full-width {
172      grid-column: 1 / -1;
173  }
174  
175  .card h2 {
176      padding: 1rem 1.5rem;
177      font-size: 1rem;
178      font-weight: 600;
179      background: var(--bg-tertiary);
180      border-bottom: 1px solid var(--border-color);
181      display: flex;
182      align-items: center;
183      gap: 0.75rem;
184  }
185  
186  .badge {
187      background: var(--accent-blue);
188      color: white;
189      padding: 0.125rem 0.5rem;
190      border-radius: 10px;
191      font-size: 0.75rem;
192      font-weight: 600;
193  }
194  
195  .card-content {
196      padding: 1rem;
197      max-height: 400px;
198      overflow-y: auto;
199  }
200  
201  .repos-card .card-content {
202      max-height: none;
203      overflow-y: visible;
204  }
205  
206  /* Tables */
207  table {
208      width: 100%;
209      border-collapse: collapse;
210      font-size: 0.875rem;
211  }
212  
213  th, td {
214      padding: 0.75rem 1rem;
215      text-align: left;
216      border-bottom: 1px solid var(--border-color);
217  }
218  
219  th {
220      font-weight: 600;
221      color: var(--text-secondary);
222      text-transform: uppercase;
223      font-size: 0.75rem;
224      letter-spacing: 0.05em;
225  }
226  
227  tbody tr:hover {
228      background: var(--bg-tertiary);
229  }
230  
231  tbody tr:last-child td {
232      border-bottom: none;
233  }
234  
235  .loading {
236      color: var(--text-muted);
237      text-align: center;
238      padding: 2rem;
239  }
240  
241  /* Status badges */
242  .status-badge {
243      display: inline-flex;
244      align-items: center;
245      gap: 0.375rem;
246      padding: 0.25rem 0.625rem;
247      border-radius: 4px;
248      font-size: 0.75rem;
249      font-weight: 500;
250  }
251  
252  .status-badge.running {
253      background: rgba(88, 166, 255, 0.15);
254      color: var(--accent-blue);
255  }
256  
257  .status-badge.idle {
258      background: rgba(139, 148, 158, 0.15);
259      color: var(--text-secondary);
260  }
261  
262  .status-badge.success, .status-badge.passed {
263      background: rgba(63, 185, 80, 0.15);
264      color: var(--accent-green);
265  }
266  
267  .status-badge.failed, .status-badge.failure {
268      background: rgba(248, 81, 73, 0.15);
269      color: var(--accent-red);
270  }
271  
272  .status-badge.pending, .status-badge.queued {
273      background: rgba(210, 153, 34, 0.15);
274      color: var(--accent-yellow);
275  }
276  
277  .status-badge.skipped {
278      background: rgba(110, 118, 129, 0.15);
279      color: var(--text-muted);
280  }
281  
282  .status-badge.cancelled {
283      background: rgba(219, 109, 40, 0.15);
284      color: var(--accent-orange);
285  }
286  
287  /* CI Job status icons */
288  .ci-status {
289      display: inline-flex;
290      align-items: center;
291      justify-content: center;
292      width: 24px;
293      height: 24px;
294      border-radius: 4px;
295      font-size: 0.875rem;
296  }
297  
298  .ci-status.pass {
299      background: rgba(63, 185, 80, 0.15);
300      color: var(--accent-green);
301  }
302  
303  .ci-status.fail {
304      background: rgba(248, 81, 73, 0.15);
305      color: var(--accent-red);
306  }
307  
308  .ci-status.skip {
309      background: rgba(110, 118, 129, 0.15);
310      color: var(--text-muted);
311  }
312  
313  .ci-status.running {
314      background: rgba(88, 166, 255, 0.15);
315      color: var(--accent-blue);
316  }
317  
318  .ci-status.pending {
319      background: rgba(210, 153, 34, 0.15);
320      color: var(--accent-yellow);
321  }
322  
323  /* Queue list */
324  .job-list {
325      list-style: none;
326  }
327  
328  .job-list li {
329      padding: 0.75rem 1rem;
330      border-bottom: 1px solid var(--border-color);
331      display: flex;
332      justify-content: space-between;
333      align-items: center;
334  }
335  
336  .job-list li:last-child {
337      border-bottom: none;
338  }
339  
340  .job-list li:hover {
341      background: var(--bg-tertiary);
342  }
343  
344  .job-info {
345      display: flex;
346      flex-direction: column;
347      gap: 0.25rem;
348  }
349  
350  .job-name {
351      font-weight: 500;
352  }
353  
354  .job-meta {
355      font-size: 0.75rem;
356      color: var(--text-secondary);
357  }
358  
359  .job-position {
360      color: var(--text-muted);
361      font-size: 0.875rem;
362  }
363  
364  .empty-state {
365      text-align: center;
366      padding: 2rem;
367      color: var(--text-muted);
368  }
369  
370  /* Links */
371  a {
372      color: var(--accent-blue);
373      text-decoration: none;
374  }
375  
376  a:hover {
377      text-decoration: underline;
378  }
379  
380  /* Footer */
381  footer {
382      padding: 1rem 2rem;
383      border-top: 1px solid var(--border-color);
384      background: var(--bg-secondary);
385      display: flex;
386      justify-content: space-between;
387      color: var(--text-muted);
388      font-size: 0.875rem;
389  }
390  
391  /* Scrollbar styling */
392  ::-webkit-scrollbar {
393      width: 8px;
394      height: 8px;
395  }
396  
397  ::-webkit-scrollbar-track {
398      background: var(--bg-primary);
399  }
400  
401  ::-webkit-scrollbar-thumb {
402      background: var(--border-color);
403      border-radius: 4px;
404  }
405  
406  ::-webkit-scrollbar-thumb:hover {
407      background: var(--text-muted);
408  }
409  
410  /* Responsive */
411  @media (max-width: 900px) {
412      .dashboard-grid {
413          grid-template-columns: 1fr;
414      }
415  
416      header {
417          flex-direction: column;
418          gap: 1rem;
419      }
420  
421      main {
422          padding: 1rem;
423      }
424  }
425  
426  /* Runner name styling */
427  .runner-name {
428      font-weight: 500;
429      color: var(--accent-purple);
430  }
431  
432  /* Repo name styling */
433  .repo-name {
434      font-weight: 500;
435  }
436  
437  /* Branch styling */
438  .branch-name {
439      font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
440      font-size: 0.8125rem;
441      background: var(--bg-tertiary);
442      padding: 0.125rem 0.5rem;
443      border-radius: 4px;
444  }
445  
446  /* Time ago styling */
447  .time-ago {
448      color: var(--text-secondary);
449      font-size: 0.8125rem;
450  }
451  
452  /* Duration styling */
453  .duration {
454      font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
455      font-size: 0.8125rem;
456      color: var(--text-secondary);
457  }
458  
459  /* Repository list with sync status */
460  .repo-list {
461      list-style: none;
462      display: grid;
463      grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
464      gap: 0.5rem;
465  }
466  
467  .repo-item {
468      display: flex;
469      align-items: center;
470      gap: 0.5rem;
471      padding: 0.5rem 0.75rem;
472      background: var(--bg-tertiary);
473      border-radius: 6px;
474      border: 1px solid var(--border-color);
475  }
476  
477  .sync-indicator {
478      width: 12px;
479      height: 12px;
480      border-radius: 50%;
481      flex-shrink: 0;
482  }
483  
484  .sync-indicator.sync-green {
485      background: var(--accent-green);
486      box-shadow: 0 0 6px var(--accent-green);
487  }
488  
489  .sync-indicator.sync-yellow {
490      background: var(--accent-yellow);
491      box-shadow: 0 0 6px var(--accent-yellow);
492  }
493  
494  .sync-indicator.sync-red {
495      background: var(--accent-red);
496      box-shadow: 0 0 6px var(--accent-red);
497  }
498  
499  .repo-item .repo-name {
500      font-size: 0.875rem;
501      overflow: hidden;
502      text-overflow: ellipsis;
503      white-space: nowrap;
504  }
505  
506  /* Queue progress display */
507  .queue-item {
508      padding: 0.75rem 1rem;
509      border-bottom: 1px solid var(--border-color);
510  }
511  
512  .queue-item:last-child {
513      border-bottom: none;
514  }
515  
516  .queue-info {
517      display: flex;
518      justify-content: space-between;
519      align-items: center;
520      margin-bottom: 0.5rem;
521  }
522  
523  .queue-repo {
524      font-weight: 500;
525      font-size: 0.9375rem;
526  }
527  
528  .queue-progress {
529      font-size: 0.8125rem;
530      color: var(--text-secondary);
531      font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
532  }
533  
534  .queue-bar {
535      height: 6px;
536      background: var(--bg-tertiary);
537      border-radius: 3px;
538      overflow: hidden;
539  }
540  
541  .queue-bar-fill {
542      height: 100%;
543      background: var(--accent-blue);
544      border-radius: 3px;
545      transition: width 0.3s ease;
546  }
547  
548  .queue-item.running .queue-bar-fill {
549      background: var(--accent-blue);
550  }
551  
552  .queue-item.pending .queue-bar-fill {
553      background: var(--accent-yellow);
554  }
555  
556  .queue-item.done .queue-bar-fill {
557      background: var(--accent-green);
558  }
559