<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link crossorigin rel="stylesheet" href="https://cdn.alemi.dev/web/alemi.css"> <title>uppe.rs</title> <style> span.cell { position: relative; display: inline-block; width: .5rem; height: 1.2rem; border: 1px solid var(--secondary); font-size: 8pt; line-height: 1.2rem; background-color: rgba(var(--secondary-rgb), 0.4); margin-top: .3rem; margin-bottom: .3rem; padding-top: 0; padding-bottom: 0; transition: .1s; text-align: center; cursor: default; border-radius: .3rem; color: #ffffff00; } span.cell:hover { padding-top: .3rem; padding-bottom: .3rem; width: 2rem; font-size: 8pt; margin-top: 0; margin-bottom: 0; margin-right: -0.75rem; margin-left: -0.75rem; color: var(--background); background-color: rgba(var(--secondary-rgb), 1); z-index: 1; font-weight: bold; } span.cell.warning { border-color: var(--accent); } span.cell.error { border-color: var(--accent); background-color: rgba(var(--accent-rgb), 0.4); } span.cell.error:hover { background-color: rgba(var(--accent-rgb), 1); } span.sep { color: var(--background-secondary); margin-right: 1em; margin-left: 1em; } hr.color { color: var(--accent); border-color: var(--accent); } div.card { display: inline-block; white-space: nowrap; overflow-x: scroll; max-width: 100%; margin-top: 2em; border-radius: 1em; border: 1px solid var(--background-secondary); padding: 1em; box-sizing: border-box; transition: .3s; } div.card:hover { background-color: var(--background-dim); } body { margin: 0; } footer { width: 100%; } div.content { min-height: calc(100vh - 3.5rem); } span.dots { &:after { animation: dots 1.5s linear infinite; display: inline-block; content: "\00a0\00a0\00a0"; } } @keyframes dots { 0% { content: "\00a0\00a0\00a0"; } 25% { content: ".\00a0\00a0"; } 50% { content: "..\00a0"; } 75% { content: "..."; } 100% { content: "\00a0\00a0\00a0"; } } .grid { display: grid; grid-template-columns: 1fr 1fr; } </style> </head> <body> <div class="content"> <div class="ml-1"> <h1 class="mt-0 pt-1">uppe.rs</h1> <p>%%DESCRIPTION%%</p> </div> <hr class="color"/> <main id="uppe-rs-content"> <h2 class="center mt-3">loading<span class="dots"></span></h2> </main> </div> <footer> <hr /> <p class="mt-s mb-s"> <span class="cell error">...</span> down <span class="sep">|</span> <span class="cell warning">...</span> slow <span class="sep">|</span> <span class="cell">...</span> up </p> </footer> </body> <script> function cell(timestamp, rtt) { let d = new Date(timestamp * 1000); let warning = ""; if (rtt !== null && rtt >= %%THRESHOLD%%) { warning = " warning"; } if (rtt === null) { return `<span class="cell error" title="${d}">╳</span>`; } else { return `<span class="cell${warning}" title="${rtt}ms -- ${d}">${rtt}</span>`; } } function card(key, history, last_rtt) { let bar = ""; let now = Math.floor(Date.now() / 1000); let first = history[history.length - 1][0]; let hrs_ago = (now - first) / 3600; for (let el of history) { bar += cell(el[0], el[1]); } return `<div class="card"> <h3 class="mt-0">${key} <code class="color">${last_rtt ? last_rtt + 'ms' : 'DOWN'}</code></h3> <table class="align"> <tr> <td colspan="2">${bar}</td> </tr> <tr> <td><small>^ now</small></td> <td class="rev"><small>~${hrs_ago.toFixed(1)}h ago ^</small></td> </tr> </table> </div>`; } let main = document.getElementById("uppe-rs-content"); async function updateStatus() { let res = await fetch("/api/status") let status = await res.json() if (status.error) { console.error("server error:", status); return; } let keys = Object.keys(status); keys.sort(); let out = ""; for (let key of keys) { let res = await fetch(`/api/status/${key}?limit=120`); let history = await res.json(); out += card(key, history, status[key]); out += "\n"; } main.innerHTML = out; } setInterval(updateStatus, 60 * 1000) updateStatus() </script> </html>