Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>View the grid — GPU cluster monitor</title> | |
| <style> | |
| :root { | |
| color-scheme: dark; | |
| font-family: system-ui, sans-serif; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 24px; | |
| background: #111; | |
| color: #eee; | |
| } | |
| main { | |
| max-width: 960px; | |
| margin: 0 auto; | |
| } | |
| header { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: flex-start; | |
| justify-content: space-between; | |
| gap: 16px; | |
| margin-bottom: 24px; | |
| } | |
| h1 { | |
| margin: 0 0 8px; | |
| font-size: 1.4rem; | |
| } | |
| p { | |
| color: #bdbdbd; | |
| line-height: 1.5; | |
| margin: 0 0 8px; | |
| } | |
| a { | |
| color: #9eb6ff; | |
| } | |
| .nav { | |
| display: flex; | |
| gap: 16px; | |
| font-size: 14px; | |
| } | |
| .stats { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 12px; | |
| margin-bottom: 24px; | |
| } | |
| .stat { | |
| flex: 1; | |
| min-width: 140px; | |
| padding: 14px 16px; | |
| border: 1px solid #2b2b2b; | |
| border-radius: 12px; | |
| background: #0d1117; | |
| } | |
| .stat-label { | |
| font-size: 12px; | |
| color: #8b949e; | |
| text-transform: uppercase; | |
| letter-spacing: 0.04em; | |
| } | |
| .stat-value { | |
| margin-top: 6px; | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| } | |
| .stat-value.ok { | |
| color: #3fb950; | |
| } | |
| .stat-value.warn { | |
| color: #d29922; | |
| } | |
| .stat-value.err { | |
| color: #f85149; | |
| } | |
| #hosts { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .host-card { | |
| border: 1px solid #2b2b2b; | |
| border-radius: 12px; | |
| background: #0d1117; | |
| overflow: hidden; | |
| } | |
| .host-header { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 12px; | |
| padding: 16px; | |
| border-bottom: 1px solid #21262d; | |
| } | |
| .host-id { | |
| font: 600 1rem/1.2 ui-monospace, monospace; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 4px 10px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| } | |
| .badge.online { | |
| background: #23863633; | |
| color: #3fb950; | |
| } | |
| .badge.offline { | |
| background: #6e768133; | |
| color: #8b949e; | |
| } | |
| .host-meta { | |
| padding: 0 16px 16px; | |
| font-size: 13px; | |
| color: #8b949e; | |
| } | |
| .host-meta span + span::before { | |
| content: ' · '; | |
| } | |
| .curl-details { | |
| border-top: 1px solid #21262d; | |
| } | |
| .curl-details summary { | |
| padding: 12px 16px; | |
| font-size: 13px; | |
| color: #9eb6ff; | |
| cursor: pointer; | |
| list-style: none; | |
| user-select: none; | |
| } | |
| .curl-details summary::-webkit-details-marker { | |
| display: none; | |
| } | |
| .curl-details summary::before { | |
| content: '▸ '; | |
| display: inline-block; | |
| transition: transform 0.15s ease; | |
| } | |
| .curl-details[open] summary::before { | |
| transform: rotate(90deg); | |
| } | |
| .curl-details .summary-hide { | |
| display: none; | |
| } | |
| .curl-details[open] .summary-show { | |
| display: none; | |
| } | |
| .curl-details[open] .summary-hide { | |
| display: inline; | |
| } | |
| .curl-body { | |
| border-top: 1px solid #21262d; | |
| } | |
| pre { | |
| margin: 0; | |
| padding: 16px; | |
| overflow-x: auto; | |
| background: #010409; | |
| font: 12px/1.5 ui-monospace, monospace; | |
| color: #c9d1d9; | |
| white-space: pre-wrap; | |
| } | |
| .empty { | |
| padding: 32px; | |
| text-align: center; | |
| border: 1px dashed #30363d; | |
| border-radius: 12px; | |
| color: #8b949e; | |
| } | |
| #refresh-note { | |
| margin-top: 16px; | |
| font-size: 12px; | |
| color: #6e7681; | |
| } | |
| #error { | |
| margin-bottom: 16px; | |
| padding: 12px 16px; | |
| border-radius: 8px; | |
| background: #490202; | |
| color: #ffa198; | |
| font-size: 14px; | |
| display: none; | |
| } | |
| button.copy { | |
| margin: 12px 16px 0; | |
| padding: 6px 12px; | |
| border: 1px solid #30363d; | |
| border-radius: 6px; | |
| background: #21262d; | |
| color: #c9d1d9; | |
| font: inherit; | |
| font-size: 12px; | |
| cursor: pointer; | |
| } | |
| button.copy:hover { | |
| background: #30363d; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <header> | |
| <div> | |
| <h1>View the grid</h1> | |
| <p> | |
| Registered browser hosts and live connection status from | |
| <code>GET /v1/hosts</code>. | |
| </p> | |
| </div> | |
| <nav class="nav"> | |
| <a href="/">Home</a> | |
| <a href="/host.html">Join the grid</a> | |
| </nav> | |
| </header> | |
| <div id="error"></div> | |
| <section class="stats" aria-label="Cluster summary"> | |
| <div class="stat"> | |
| <div class="stat-label">Broker</div> | |
| <div id="broker-status" class="stat-value">…</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Hosts online</div> | |
| <div id="online-count" class="stat-value">—</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="stat-label">Hosts registered</div> | |
| <div id="total-count" class="stat-value">—</div> | |
| </div> | |
| </section> | |
| <div id="hosts"></div> | |
| <p id="refresh-note">Refreshing every 3s</p> | |
| </main> | |
| <script type="module" src="/src/pages/clusterMonitor.ts"></script> | |
| </body> | |
| </html> | |