my own status page
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: auto reload the dashboard

+63 -6
+60 -5
src/routes/index.ts
··· 86 86 </head> 87 87 <body> 88 88 <h1>infra.dunkirk.sh</h1> 89 - <p class="overall"><span class="dot ${overallClass}" title="${overallClass}"></span>${overallText}</p> 89 + <p class="overall"><span class="dot ${overallClass}" id="overall-dot" title="${overallClass}"></span><span id="overall-text">${overallText}</span></p> 90 90 ${servers 91 91 .map( 92 92 (m) => `<div class="machine"> 93 - <div class="machine-header"><span class="dot ${m.online ? "online" : m.services.length === 0 ? "unknown" : "offline"}" title="${m.online ? "online" : "offline"}"></span>${esc(m.name)}<span class="machine-type">${esc(m.type)}</span></div> 93 + <div class="machine-header"><span class="dot ${m.online ? "online" : m.services.length === 0 ? "unknown" : "offline"}" data-machine="${esc(m.name)}" title="${m.online ? "online" : "offline"}"></span>${esc(m.name)}<span class="machine-type">${esc(m.type)}</span></div> 94 94 ${m.services.length === 0 ? `<div class="no-services">no services</div>` : m.services 95 95 .map( 96 96 (s) => `<div class="service"> 97 97 <div class="svc-left"> 98 - <span class="dot ${s.status}" title="${s.status}"></span> 98 + <span class="dot ${s.status}" data-service="${esc(s.name)}" title="${s.status}"></span> 99 99 <span class="svc-name"><a href="${esc(s.url)}">${esc(s.name)}</a></span> 100 100 </div> 101 101 <div class="svc-right"> 102 - ${s.has_health ? `<span class="uptime">${s.uptime_7d}%</span><span class="latency">${s.latency_ms !== null ? s.latency_ms + "ms" : "—"}</span>` : `<span class="latency">no health check</span>`} 102 + ${s.has_health ? `<span class="uptime" data-service-uptime="${esc(s.name)}">${s.uptime_7d}%</span><span class="latency" data-service-latency="${esc(s.name)}">${s.latency_ms !== null ? s.latency_ms + "ms" : "—"}</span>` : `<span class="latency">no health check</span>`} 103 103 </div> 104 104 </div>`, 105 105 ) ··· 110 110 ${clients.length > 0 ? `<div class="clients"> 111 111 <div class="clients-header">devices</div> 112 112 <div class="clients-list"> 113 - ${clients.map((m) => `<span class="client"><span class="dot ${m.online ? "online" : "unknown"}" title="${m.online ? "online" : "offline"}"></span>${esc(m.name)}</span>`).join("\n")} 113 + ${clients.map((m) => `<span class="client"><span class="dot ${m.online ? "online" : "unknown"}" data-machine="${esc(m.name)}" title="${m.online ? "online" : "offline"}"></span>${esc(m.name)}</span>`).join("\n")} 114 114 </div> 115 115 </div>` : ""} 116 116 <footer><span>${lastCheckISO ? `updated <relative-time datetime="${lastCheckISO}" prefix="">loading</relative-time>` : "no checks yet"}</span><a href="https://github.com/taciturnaxolotl/status/commit/${COMMIT_SHA}">${COMMIT_SHA}</a></footer> ··· 143 143 } 144 144 } 145 145 customElements.define('relative-time', RelativeTimeElement); 146 + 147 + const CHECK_INTERVAL = 5 * 60 * 1000; 148 + const BUFFER = 10 * 1000; 149 + const OVERALL_LABELS = { up: 'All systems operational', degraded: 'Some systems degraded', down: 'On fire' }; 150 + 151 + function updateFavicon(status) { 152 + const colors = { up: '#2ecc71', degraded: '#f39c12', down: '#e74c3c' }; 153 + const color = colors[status] || '#8b949e'; 154 + const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><circle cx="8" cy="8" r="7" fill="' + color + '"/></svg>'; 155 + const link = document.querySelector('link[rel="icon"]'); 156 + if (link) link.href = 'data:image/svg+xml,' + encodeURIComponent(svg); 157 + } 158 + 159 + function setDot(el, cls) { 160 + el.className = 'dot ' + cls; 161 + el.title = cls; 162 + } 163 + 164 + function applyUpdate(data) { 165 + updateFavicon(data.status); 166 + const dot = document.getElementById('overall-dot'); 167 + const text = document.getElementById('overall-text'); 168 + if (dot) setDot(dot, data.status); 169 + if (text) text.textContent = OVERALL_LABELS[data.status] || data.status; 170 + 171 + for (const machine of data.machines) { 172 + const mDot = document.querySelector('[data-machine="' + machine.name + '"]'); 173 + if (mDot) setDot(mDot, machine.online ? 'online' : machine.services.length === 0 ? 'unknown' : 'offline'); 174 + for (const svc of machine.services) { 175 + const sDot = document.querySelector('[data-service="' + svc.id + '"]'); 176 + if (sDot) setDot(sDot, svc.status); 177 + const uEl = document.querySelector('[data-service-uptime="' + svc.id + '"]'); 178 + if (uEl) uEl.textContent = svc.uptime_7d + '%'; 179 + const lEl = document.querySelector('[data-service-latency="' + svc.id + '"]'); 180 + if (lEl) lEl.textContent = svc.latency_ms !== null ? svc.latency_ms + 'ms' : '—'; 181 + } 182 + } 183 + 184 + if (data.last_check) { 185 + const rt = document.querySelector('relative-time'); 186 + if (rt) rt.setAttribute('datetime', new Date(data.last_check * 1000).toISOString()); 187 + } 188 + } 189 + 190 + function scheduleRefresh() { 191 + fetch('/api/status').then(r => r.json()).then(data => { 192 + applyUpdate(data); 193 + if (!data.last_check) { setTimeout(scheduleRefresh, CHECK_INTERVAL); return; } 194 + const lastCheck = data.last_check * 1000; 195 + const nextCheck = lastCheck + CHECK_INTERVAL + BUFFER; 196 + const delay = Math.max(nextCheck - Date.now(), BUFFER); 197 + setTimeout(scheduleRefresh, delay); 198 + }).catch(() => { setTimeout(scheduleRefresh, CHECK_INTERVAL); }); 199 + } 200 + scheduleRefresh(); 146 201 </script> 147 202 </body> 148 203 </html>`;
+3 -1
src/routes/status.ts
··· 1 1 import type { Env } from "../types"; 2 2 import { getManifest } from "../manifest"; 3 - import { getLatestPing, getUptime7d } from "../db"; 3 + import { getLatestPing, getUptime7d, getLastCheckTime } from "../db"; 4 4 import { getDeviceStatus } from "../tailscale"; 5 5 import { getOverallStatus } from "../overall"; 6 6 ··· 77 77 ); 78 78 79 79 const { grade } = await getOverallStatus(env); 80 + const lastCheck = await getLastCheckTime(env.DB); 80 81 81 82 return Response.json( 82 83 { 83 84 ok: grade === "up", 84 85 status: grade, 86 + last_check: lastCheck, 85 87 machines, 86 88 } ); 87 89 }