need a status for my status for my .. nate.tngl.io
1
fork

Configure Feed

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

extract inline script to app.js, add source link footer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 5ca0d492 f8980f6d

+164 -142
+142
site/app.js
··· 1 + import { GROUPS } from "./services.js"; 2 + import { startAutoRefresh, checkNow } from "./checker.js"; 3 + 4 + const groupsEl = document.getElementById("groups"); 5 + const summaryEl = document.getElementById("summary"); 6 + const countdownEl = document.getElementById("countdown"); 7 + const checkNowBtn = document.getElementById("check-now"); 8 + 9 + const totalServices = GROUPS.reduce((n, g) => n + g.services.length, 0); 10 + 11 + // build DOM 12 + 13 + for (const group of GROUPS) { 14 + const div = document.createElement("div"); 15 + div.className = "group collapsed"; 16 + div.setAttribute("role", "listitem"); 17 + 18 + const slug = group.name.replace(/\s+/g, "-"); 19 + 20 + div.innerHTML = ` 21 + <button class="group-header" aria-expanded="false" aria-controls="content-${slug}" id="group-${slug}"> 22 + <span class="group-chevron" aria-hidden="true">&#9660;</span> 23 + <span class="group-name">${group.name}</span> 24 + <span class="group-count" data-group="${group.name}"></span> 25 + <span class="status-dot checking" data-group-dot="${group.name}" role="img" aria-label="group status: checking"></span> 26 + </button> 27 + <div class="group-services" id="content-${slug}" role="list" aria-labelledby="group-${slug}"> 28 + ${group.services.map(s => ` 29 + <div class="service-row" data-url="${s.url}" role="listitem"> 30 + <a class="service-name" href="${s.href}" target="_blank" rel="noopener">${s.name}</a> 31 + <div class="service-status" aria-label="status"> 32 + <span class="status-code"></span> 33 + <span class="status-ms"></span> 34 + <span class="status-dot checking" role="img" aria-label="checking"></span> 35 + </div> 36 + </div> 37 + `).join("")} 38 + </div> 39 + `; 40 + 41 + const btn = div.querySelector(".group-header"); 42 + btn.addEventListener("click", () => { 43 + const collapsed = div.classList.toggle("collapsed"); 44 + btn.setAttribute("aria-expanded", String(!collapsed)); 45 + }); 46 + 47 + groupsEl.appendChild(div); 48 + } 49 + 50 + // status updates 51 + 52 + function setChecking() { 53 + for (const dot of document.querySelectorAll(".status-dot")) { 54 + dot.className = "status-dot checking"; 55 + dot.setAttribute("aria-label", "checking"); 56 + } 57 + for (const code of document.querySelectorAll(".status-code")) { 58 + code.textContent = ""; 59 + } 60 + for (const ms of document.querySelectorAll(".status-ms")) { 61 + ms.textContent = ""; 62 + } 63 + } 64 + 65 + function statusLabel(ok, status) { 66 + if (ok) return "healthy"; 67 + if (status >= 400 || status === 0) return "down"; 68 + return "degraded"; 69 + } 70 + 71 + function onResult(byUrl) { 72 + let up = 0; 73 + 74 + for (const group of GROUPS) { 75 + let groupUp = 0; 76 + let groupHasError = false; 77 + 78 + for (const s of group.services) { 79 + const r = byUrl.get(s.url); 80 + const row = document.querySelector(`[data-url="${s.url}"]`); 81 + if (!row || !r) continue; 82 + 83 + const dot = row.querySelector(".status-dot"); 84 + const code = row.querySelector(".status-code"); 85 + const ms = row.querySelector(".status-ms"); 86 + 87 + dot.className = "status-dot"; 88 + if (r.ok) { 89 + dot.classList.add("ok"); 90 + up++; 91 + groupUp++; 92 + } else if (r.status >= 400 || r.status === 0) { 93 + dot.classList.add("error"); 94 + groupHasError = true; 95 + } else { 96 + dot.classList.add("warn"); 97 + } 98 + 99 + dot.setAttribute("aria-label", statusLabel(r.ok, r.status)); 100 + code.textContent = r.status || "---"; 101 + ms.textContent = r.ms ? `${r.ms}ms` : ""; 102 + } 103 + 104 + const groupDot = document.querySelector(`[data-group-dot="${group.name}"]`); 105 + if (groupDot) { 106 + groupDot.className = "status-dot"; 107 + if (groupUp === group.services.length) { 108 + groupDot.classList.add("ok"); 109 + groupDot.setAttribute("aria-label", "group status: all healthy"); 110 + } else if (groupHasError) { 111 + groupDot.classList.add("error"); 112 + groupDot.setAttribute("aria-label", "group status: some down"); 113 + } else { 114 + groupDot.classList.add("warn"); 115 + groupDot.setAttribute("aria-label", "group status: degraded"); 116 + } 117 + } 118 + 119 + const countEl = document.querySelector(`[data-group="${group.name}"]`); 120 + if (countEl) { 121 + countEl.textContent = `${groupUp}/${group.services.length}`; 122 + } 123 + } 124 + 125 + summaryEl.textContent = `${up}/${totalServices} up`; 126 + 127 + const ts = new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); 128 + countdownEl.textContent = `checked ${ts}`; 129 + } 130 + 131 + function onTick(secs) { 132 + countdownEl.textContent = `next check in ${secs}s`; 133 + } 134 + 135 + checkNowBtn.addEventListener("click", () => { 136 + setChecking(); 137 + summaryEl.textContent = "checking..."; 138 + checkNow(onResult, onTick); 139 + }); 140 + 141 + setChecking(); 142 + startAutoRefresh(onResult, onTick);
+4 -142
site/index.html
··· 28 28 </div> 29 29 </header> 30 30 <div id="groups" role="list" aria-label="service groups"></div> 31 + <footer class="footer"> 32 + <a href="https://tangled.sh/zzstoatzz.io/evergreen" target="_blank" rel="noopener">source</a> 33 + </footer> 31 34 </div> 32 - <script type="module"> 33 - import { GROUPS } from "./services.js"; 34 - import { startAutoRefresh, checkNow } from "./checker.js"; 35 - 36 - const groupsEl = document.getElementById("groups"); 37 - const summaryEl = document.getElementById("summary"); 38 - const countdownEl = document.getElementById("countdown"); 39 - const checkNowBtn = document.getElementById("check-now"); 40 - 41 - const totalServices = GROUPS.reduce((n, g) => n + g.services.length, 0); 42 - 43 - for (const group of GROUPS) { 44 - const div = document.createElement("div"); 45 - div.className = "group collapsed"; 46 - div.setAttribute("role", "listitem"); 47 - 48 - const headerId = `group-${group.name.replace(/\s+/g, "-")}`; 49 - const contentId = `content-${group.name.replace(/\s+/g, "-")}`; 50 - 51 - div.innerHTML = ` 52 - <button class="group-header" aria-expanded="false" aria-controls="${contentId}" id="${headerId}"> 53 - <span class="group-chevron" aria-hidden="true">&#9660;</span> 54 - <span class="group-name">${group.name}</span> 55 - <span class="group-count" data-group="${group.name}"></span> 56 - <span class="status-dot checking" data-group-dot="${group.name}" role="img" aria-label="group status: checking"></span> 57 - </button> 58 - <div class="group-services" id="${contentId}" role="list" aria-labelledby="${headerId}"> 59 - ${group.services.map(s => ` 60 - <div class="service-row" data-url="${s.url}" role="listitem"> 61 - <a class="service-name" href="${s.href}" target="_blank" rel="noopener">${s.name}</a> 62 - <div class="service-status" aria-label="status"> 63 - <span class="status-code"></span> 64 - <span class="status-ms"></span> 65 - <span class="status-dot checking" role="img" aria-label="checking"></span> 66 - </div> 67 - </div> 68 - `).join("")} 69 - </div> 70 - `; 71 - 72 - const btn = div.querySelector(".group-header"); 73 - btn.addEventListener("click", () => { 74 - const collapsed = div.classList.toggle("collapsed"); 75 - btn.setAttribute("aria-expanded", String(!collapsed)); 76 - }); 77 - 78 - groupsEl.appendChild(div); 79 - } 80 - 81 - function setChecking() { 82 - for (const dot of document.querySelectorAll(".status-dot")) { 83 - dot.className = "status-dot checking"; 84 - dot.setAttribute("aria-label", "checking"); 85 - } 86 - for (const code of document.querySelectorAll(".status-code")) { 87 - code.textContent = ""; 88 - } 89 - for (const ms of document.querySelectorAll(".status-ms")) { 90 - ms.textContent = ""; 91 - } 92 - } 93 - 94 - function statusLabel(ok, status) { 95 - if (ok) return "healthy"; 96 - if (status >= 400 || status === 0) return "down"; 97 - return "degraded"; 98 - } 99 - 100 - function onResult(byUrl) { 101 - let up = 0; 102 - 103 - for (const group of GROUPS) { 104 - let groupUp = 0; 105 - let groupHasError = false; 106 - 107 - for (const s of group.services) { 108 - const r = byUrl.get(s.url); 109 - const row = document.querySelector(`[data-url="${s.url}"]`); 110 - if (!row || !r) continue; 111 - 112 - const dot = row.querySelector(".status-dot"); 113 - const code = row.querySelector(".status-code"); 114 - const ms = row.querySelector(".status-ms"); 115 - 116 - dot.className = "status-dot"; 117 - if (r.ok) { 118 - dot.classList.add("ok"); 119 - up++; 120 - groupUp++; 121 - } else if (r.status >= 400 || r.status === 0) { 122 - dot.classList.add("error"); 123 - groupHasError = true; 124 - } else { 125 - dot.classList.add("warn"); 126 - } 127 - 128 - dot.setAttribute("aria-label", statusLabel(r.ok, r.status)); 129 - code.textContent = r.status || "---"; 130 - ms.textContent = r.ms ? `${r.ms}ms` : ""; 131 - } 132 - 133 - const groupDot = document.querySelector(`[data-group-dot="${group.name}"]`); 134 - if (groupDot) { 135 - groupDot.className = "status-dot"; 136 - if (groupUp === group.services.length) { 137 - groupDot.classList.add("ok"); 138 - groupDot.setAttribute("aria-label", "group status: all healthy"); 139 - } else if (groupHasError) { 140 - groupDot.classList.add("error"); 141 - groupDot.setAttribute("aria-label", "group status: some down"); 142 - } else { 143 - groupDot.classList.add("warn"); 144 - groupDot.setAttribute("aria-label", "group status: degraded"); 145 - } 146 - } 147 - 148 - const countEl = document.querySelector(`[data-group="${group.name}"]`); 149 - if (countEl) { 150 - countEl.textContent = `${groupUp}/${group.services.length}`; 151 - } 152 - } 153 - 154 - summaryEl.textContent = `${up}/${totalServices} up`; 155 - 156 - const now = new Date(); 157 - const ts = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); 158 - countdownEl.textContent = `checked ${ts}`; 159 - } 160 - 161 - function onTick(secs) { 162 - countdownEl.textContent = `next check in ${secs}s`; 163 - } 164 - 165 - checkNowBtn.addEventListener("click", () => { 166 - setChecking(); 167 - summaryEl.textContent = "checking..."; 168 - checkNow(onResult, onTick); 169 - }); 170 - 171 - setChecking(); 172 - startAutoRefresh(onResult, onTick); 173 - </script> 35 + <script type="module" src="app.js"></script> 174 36 </body> 175 37 </html>
+18
site/style.css
··· 238 238 text-align: right; 239 239 } 240 240 241 + /* footer */ 242 + .footer { 243 + margin-top: 2rem; 244 + padding-top: 1rem; 245 + border-top: 1px solid var(--border); 246 + font-size: 0.75rem; 247 + color: var(--text-muted); 248 + } 249 + 250 + .footer a { 251 + color: var(--text-muted); 252 + text-decoration: none; 253 + } 254 + 255 + .footer a:hover { 256 + color: var(--accent); 257 + } 258 + 241 259 /* responsive */ 242 260 @media (max-width: 640px) { 243 261 .container {