Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

admin-status.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

admin-status.tsxBlame254 lines · 1 contributor
b1be050CC LABS App1/**
2 * BLOCK S4 — Site-admin synthetic-monitor dashboard.
3 *
4 * GET /admin/status — red/green dashboard (SSE-live)
5 * POST /admin/status/run — run the suite synchronously
6 *
7 * Both gated behind `isSiteAdmin`. The public `/status` page (see
8 * `src/routes/status.tsx`) stays open to everyone; this is the detailed
9 * 14-row health table for the owner.
10 */
11
12import { Hono } from "hono";
13import { Layout } from "../views/layout";
14import { softAuth } from "../middleware/auth";
15import type { AuthEnv } from "../middleware/auth";
16import { isSiteAdmin } from "../lib/admin";
17import {
18 latestStatusByCheck,
19 recentRedChecks,
20 runSyntheticChecks,
21 persistChecks,
22 SSE_TOPIC,
23 SYNTHETIC_CHECKS,
24 type SyntheticCheckResult,
25} from "../lib/synthetic-monitor";
26
27const adminStatus = new Hono<AuthEnv>();
28adminStatus.use("*", softAuth);
29
30async function gate(c: any): Promise<{ user: any } | Response> {
31 const user = c.get("user");
32 if (!user) return c.redirect("/login?next=/admin/status");
33 if (!(await isSiteAdmin(user.id))) {
34 return c.html(
35 <Layout title="Forbidden" user={user}>
36 <div class="empty-state">
37 <h2>403 — Not a site admin</h2>
38 <p>You don't have permission to view this page.</p>
39 </div>
40 </Layout>,
41 403
42 );
43 }
44 return { user };
45}
46
47function statusDot(status: SyntheticCheckResult["status"] | undefined): string {
48 if (status === "green") return "\u{1F7E2}"; // green circle
49 if (status === "red") return "\u{1F534}"; // red circle
50 if (status === "yellow") return "\u{1F7E1}"; // yellow circle
51 return "⚪"; // white circle — never run
52}
53
54function fmtAgo(checkedAt: Date | undefined): string {
55 if (!checkedAt) return "never";
56 const diffMs = Date.now() - checkedAt.getTime();
57 if (diffMs < 0) return "just now";
58 const s = Math.floor(diffMs / 1000);
59 if (s < 5) return "just now";
60 if (s < 60) return `${s}s ago`;
61 const m = Math.floor(s / 60);
62 if (m < 60) return `${m}m ago`;
63 const h = Math.floor(m / 60);
64 if (h < 24) return `${h}h ago`;
65 const d = Math.floor(h / 24);
66 return `${d}d ago`;
67}
68
69adminStatus.get("/admin/status", async (c) => {
70 const g = await gate(c);
71 if (g instanceof Response) return g;
72 const { user } = g;
73
74 const latest = await latestStatusByCheck();
75 const recent = await recentRedChecks(24, 25);
76
77 const allGreen = SYNTHETIC_CHECKS.every(
78 (spec) => latest[spec.name]?.status === "green"
79 );
80
81 // Find the most-recent checkedAt across all rows so we can render the
82 // "last run Xs ago" badge.
83 let lastRunAt: Date | null = null;
84 for (const spec of SYNTHETIC_CHECKS) {
85 const row = latest[spec.name];
86 if (!row) continue;
87 if (!lastRunAt || row.checkedAt > lastRunAt) lastRunAt = row.checkedAt;
88 }
89
90 return c.html(
91 <Layout title="Synthetic monitor — admin" user={user}>
92 <div style="max-width: 960px; margin: 0 auto; padding: 24px 16px">
93 <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px">
94 <span
95 style={`display: inline-block; width: 14px; height: 14px; border-radius: 50%; background: ${allGreen ? "var(--green, #2da44e)" : "var(--red, #cf222e)"}`}
96 />
97 <h1 style="margin: 0; font-size: 26px">
98 {allGreen ? "All checks green" : "One or more checks failing"}
99 </h1>
100 </div>
101 <p style="color: var(--text-muted); margin-bottom: 24px">
102 Synthetic monitor — runs every autopilot tick. Last run{" "}
103 <span data-last-run-at>{fmtAgo(lastRunAt)}</span>.
104 </p>
105
106 <div class="panel" style="margin-bottom: 20px">
107 <table
108 style="width: 100%; border-collapse: collapse; font-size: 14px"
109 id="synthetic-table"
110 >
111 <thead>
112 <tr style="text-align: left; color: var(--text-muted); font-size: 12px; text-transform: uppercase">
113 <th style="padding: 8px 12px; width: 28px"></th>
114 <th style="padding: 8px 12px">Check</th>
115 <th style="padding: 8px 12px; width: 80px">Status</th>
116 <th style="padding: 8px 12px; width: 90px">Duration</th>
117 <th style="padding: 8px 12px; width: 110px">Last run</th>
118 </tr>
119 </thead>
120 <tbody>
121 {SYNTHETIC_CHECKS.map((spec) => {
122 const row = latest[spec.name];
123 return (
124 <tr
125 data-check-name={spec.name}
126 style="border-top: 1px solid var(--border)"
127 >
128 <td
129 style="padding: 8px 12px"
130 data-cell="dot"
131 >
132 {statusDot(row?.status)}
133 </td>
134 <td style="padding: 8px 12px">
135 <code>{spec.name}</code>
136 {row?.error ? (
137 <div
138 style="font-size: 11px; color: var(--red, #cf222e); margin-top: 2px"
139 data-cell="error"
140 >
141 {row.error}
142 </div>
143 ) : null}
144 </td>
145 <td style="padding: 8px 12px" data-cell="status-code">
146 {row?.statusCode ?? "—"}
147 </td>
148 <td style="padding: 8px 12px" data-cell="duration">
149 {row ? `${row.durationMs}ms` : "—"}
150 </td>
151 <td
152 style="padding: 8px 12px; color: var(--text-muted)"
153 data-cell="ago"
154 >
155 {fmtAgo(row?.checkedAt)}
156 </td>
157 </tr>
158 );
159 })}
160 </tbody>
161 </table>
162 </div>
163
164 <form
165 action="/admin/status/run"
166 method="post"
167 style="margin-bottom: 32px"
168 >
169 <button class="btn-primary" type="submit">
170 Run all checks now
171 </button>
172 </form>
173
174 <h2 style="font-size: 16px; margin-bottom: 12px">
175 Recent red checks (last 24h)
176 </h2>
177 {recent.length === 0 ? (
178 <p style="color: var(--text-muted); font-size: 13px">
179 No red checks in the last 24 hours.
180 </p>
181 ) : (
182 <div class="panel">
183 {recent.map((r) => (
184 <div
185 class="panel-item"
186 style="justify-content: space-between; font-size: 13px"
187 >
188 <div>
189 <code>{r.name}</code>{" "}
190 <span style="color: var(--text-muted)">
191 — {r.error || "(no error message)"}
192 </span>
193 </div>
194 <span style="color: var(--text-muted); font-size: 12px">
195 {fmtAgo(r.checkedAt)}
196 </span>
197 </div>
198 ))}
199 </div>
200 )}
201
202 <script
203 // Live update via SSE. Each event is a SyntheticCheckResult; we
204 // patch the matching <tr data-check-name=...> in place.
205 dangerouslySetInnerHTML={{
206 __html: `
207(function() {
208 try {
209 var src = new EventSource('/live-events/${SSE_TOPIC}');
210 src.addEventListener('check', function(ev) {
211 var data;
212 try { data = JSON.parse(ev.data); } catch (e) { return; }
213 var row = document.querySelector('tr[data-check-name="' + data.name + '"]');
214 if (!row) return;
215 var dot = row.querySelector('[data-cell="dot"]');
216 var statusCode = row.querySelector('[data-cell="status-code"]');
217 var duration = row.querySelector('[data-cell="duration"]');
218 var ago = row.querySelector('[data-cell="ago"]');
219 if (dot) dot.textContent = data.status === 'green' ? '\\uD83D\\uDFE2' : (data.status === 'red' ? '\\uD83D\\uDD34' : '\\uD83D\\uDFE1');
220 if (statusCode) statusCode.textContent = data.statusCode != null ? String(data.statusCode) : '\\u2014';
221 if (duration) duration.textContent = data.durationMs + 'ms';
222 if (ago) ago.textContent = 'just now';
223 var lastRun = document.querySelector('[data-last-run-at]');
224 if (lastRun) lastRun.textContent = 'just now';
225 });
226 } catch (e) { /* SSE not available — page still renders the SSR snapshot */ }
227})();
228`,
229 }}
230 />
231 </div>
232 </Layout>
233 );
234});
235
236adminStatus.post("/admin/status/run", async (c) => {
237 const g = await gate(c);
238 if (g instanceof Response) return g;
239
240 // Fire-and-forget the run so we don't block the redirect on a slow
241 // network. Persist + SSE happens inside the helper.
242 void (async () => {
243 try {
244 const results = await runSyntheticChecks();
245 await persistChecks(results);
246 } catch (err) {
247 console.error("[admin-status] manual run failed:", err);
248 }
249 })();
250
251 return c.redirect("/admin/status");
252});
253
254export default adminStatus;