Created
December 17, 2025 22:07
-
-
Save IgorHalfeld/1ec9b51fe148105542cf5b4009098a5e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Startup Status Widget (Scriptable) — colorized text + emoji | |
| // Checks: | |
| // - UptimeRobot public status page (HTML) | |
| // - Your /health endpoint (HTTP + optional JSON) | |
| // Refresh request: every 30 minutes (not guaranteed by iOS) | |
| const CHECK_INTERVAL_MIN = 30; | |
| const TITLE_STATUS = "Company Servers" // eg. add your company name | |
| const TARGETS = [ | |
| { name: "Service 1", url: "https://...", type: "uptimerobot_html" }, | |
| { name: "API Health", url: "https://...", type: "health_http" }, | |
| // ... add more here | |
| ]; | |
| async function fetchReq(url, timeoutMs = 12000) { | |
| const req = new Request(url); | |
| req.timeoutInterval = timeoutMs / 1000; | |
| req.headers = { "Cache-Control": "no-cache" }; | |
| return req; | |
| } | |
| function classifyUptimeRobotPage(html) { | |
| const text = (html || "").toLowerCase(); | |
| if (text.includes("all systems operational")) return { state: "UP", detail: "All operational" }; | |
| if (text.includes("major outage")) return { state: "DOWN", detail: "Major outage" }; | |
| if (text.includes("partial outage")) return { state: "DEGRADED", detail: "Partial outage" }; | |
| if (text.includes("degraded performance")) return { state: "DEGRADED", detail: "Degraded" }; | |
| if (text.includes("operational")) return { state: "UP", detail: "Operational" }; | |
| return { state: "UNKNOWN", detail: "Could not classify" }; | |
| } | |
| async function checkTarget(t) { | |
| const started = Date.now(); | |
| try { | |
| const req = await fetchReq(t.url); | |
| if (t.type === "uptimerobot_html") { | |
| const html = await req.loadString(); | |
| const c = classifyUptimeRobotPage(html); | |
| return { ...c, ms: Date.now() - started }; | |
| } | |
| // health_http | |
| let body = null; | |
| let isJson = false; | |
| try { | |
| body = await req.loadJSON(); | |
| isJson = true; | |
| } catch { | |
| body = await req.loadString(); | |
| } | |
| let detail = isJson ? "JSON OK" : "HTTP OK"; | |
| if (isJson) { | |
| const statusVal = (body?.status || body?.state || body?.health || "").toString().toLowerCase(); | |
| if (statusVal && !["ok", "healthy", "up"].includes(statusVal)) { | |
| return { state: "DEGRADED", detail: `Status: ${statusVal}`, ms: Date.now() - started }; | |
| } | |
| if (body?.version) detail = `OK (v${body.version})`; | |
| } | |
| return { state: "UP", detail, ms: Date.now() - started }; | |
| } catch (e) { | |
| return { state: "DOWN", detail: (e?.message || "Request failed").slice(0, 70), ms: Date.now() - started }; | |
| } | |
| } | |
| function emojiFor(state) { | |
| if (state === "DOWN") return "🚨"; | |
| if (state === "DEGRADED") return "⚠️"; | |
| if (state === "UP") return "🤑"; | |
| return "❓"; | |
| } | |
| function colorFor(state) { | |
| if (state === "UP") return new Color("#2ECC71"); | |
| if (state === "DEGRADED") return new Color("#F1C40F"); | |
| if (state === "DOWN") return new Color("#E74C3C"); | |
| return new Color("#95A5A6"); | |
| } | |
| // Run checks | |
| const results = []; | |
| for (const t of TARGETS) results.push({ target: t, result: await checkTarget(t) }); | |
| // Build widget | |
| const w = new ListWidget(); | |
| w.setPadding(14, 14, 14, 14); | |
| const title = w.addText(TITLE_STATUS); | |
| title.font = Font.boldSystemFont(16); | |
| w.addSpacer(10); | |
| for (const r of results) { | |
| // Line 1: emoji + name | |
| const line1 = w.addText(`${emojiFor(r.result.state)} ${r.target.name}`); | |
| line1.font = Font.semiboldSystemFont(13); | |
| // Line 2: colorized STATE + gray details | |
| const line2 = w.addStack(); | |
| line2.centerAlignContent(); | |
| const stateTxt = line2.addText(r.result.state); | |
| stateTxt.font = Font.semiboldSystemFont(11); | |
| stateTxt.textColor = colorFor(r.result.state); | |
| const restTxt = line2.addText(` • ${r.result.detail} • ${r.result.ms}ms`); | |
| restTxt.font = Font.systemFont(11); | |
| restTxt.textColor = Color.gray(); | |
| w.addSpacer(10); | |
| } | |
| const checked = w.addText(`Last check: ${new Date().toLocaleTimeString()}`); | |
| checked.font = Font.systemFont(10); | |
| checked.textColor = Color.gray(); | |
| // Best-effort refresh request | |
| w.refreshAfterDate = new Date(Date.now() + CHECK_INTERVAL_MIN * 60 * 1000); | |
| Script.setWidget(w); | |
| Script.complete(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
the result: