Neale Pickett
·
2025-07-10
status.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Betsy Button Status Checker</title>
7 <style>
8 /* Custom font for a clean look */
9 body {
10 font-family: "Inter", sans-serif;
11 background-color: #f0f4f8; /* Light gray background */
12 display: flex;
13 flex-direction: column; /* Arrange content vertically */
14 justify-content: center;
15 align-items: center;
16 min-height: 100vh; /* Full viewport height */
17 margin: 0;
18 padding: 1rem; /* Add some padding for small screens */
19 box-sizing: border-box;
20 color: #333; /* Default text color */
21 }
22
23 h1 {
24 font-size: 2rem; /* Larger heading */
25 font-weight: bold;
26 margin-bottom: 1.5rem;
27 text-align: center;
28 color: #2c3e50; /* Darker heading color */
29 }
30
31 /* Input container styling */
32 div.w-full { /* Re-using a class name from previous version, adjust if needed */
33 width: 100%;
34 max-width: 300px; /* Limit width for input */
35 margin-bottom: 1.5rem;
36 text-align: center;
37 }
38
39 label {
40 display: block;
41 font-size: 0.875rem; /* text-sm */
42 font-weight: 500; /* font-medium */
43 margin-bottom: 0.25rem;
44 color: #555; /* gray-700 */
45 }
46
47 input[type="text"] {
48 display: block;
49 width: 100%;
50 padding: 0.5rem 1rem; /* px-4 py-2 */
51 border: 1px solid #ccc; /* border-gray-300 */
52 border-radius: 0.375rem; /* rounded-md */
53 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* shadow-sm */
54 font-size: 0.875rem; /* sm:text-sm */
55 transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
56 }
57
58 input[type="text"]:focus {
59 outline: none;
60 border-color: #3b82f6; /* focus:border-blue-500 */
61 box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); /* focus:ring-blue-500 focus:ring-offset-2 */
62 }
63
64 /* Keyframe animations for blinking and pulsing */
65 @keyframes expired {
66 0%, 12.5%, 50%, 67.5%, 75% { opacity: 1.00; } /* On states */
67 62.5%, 87.5%, 100% { opacity: 0.20; } /* Off states */
68 }
69
70 @keyframes pulse {
71 0%, 50%, 100% { opacity: 0.8; }
72 25%, 75% { opacity: 1.0; }
73 12.5%, 37.5%, 62.5%, 87.5% { opacity: 0.9; }
74 }
75
76 /* Container for the light */
77 .no-light {
78 background-color: black;
79 border-radius: 50%; /* Make it a circle */
80 display: flex;
81 justify-content: center;
82 align-items: center;
83 height: 45vh; /* Responsive height */
84 width: 45vh; /* Responsive width, maintains aspect ratio */
85 max-height: 200px; /* Max size for larger screens */
86 max-width: 200px;
87 margin: 2rem auto; /* Center with margin */
88 box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.5); /* Inner shadow for depth */
89 }
90
91 /* The actual light element */
92 .light {
93 height: 80%; /* Takes up 80% of its parent (.no-light) */
94 width: 80%; /* Takes up 80% of its parent (.no-light) */
95 border-radius: 50%; /* Make it a circle */
96 background-color: red; /* Default color for 'expired' */
97 animation: expired 2s infinite; /* Default animation */
98 }
99
100 /* Style for 'OK' status */
101 .light.ok {
102 background-color: green; /* Green color for 'OK' */
103 animation: pulse 2s infinite; /* Pulse animation for 'OK' */
104 }
105
106 /* Hidden utility class */
107 .hidden {
108 display: none;
109 }
110
111 /* Error message styling */
112 p.error {
113 color: #ef4444; /* text-red-500 */
114 font-size: 0.875rem; /* text-sm */
115 margin-top: 1rem;
116 text-align: center;
117 }
118
119 .log {
120 max-height: 8em;
121 max-width: 90vw;
122 white-space: nowrap;
123 overflow: auto;
124 }
125 </style>
126</head>
127<body>
128 <div>
129 <h1>Betsy Button Status Checker</h1>
130
131 <!-- Input for Button ID -->
132 <div>
133 <label for="id">
134 Button ID:
135 </label>
136 <input name="id" placeholder="xxx-xxx-xxxx" maxlength="40" required>
137 </div>
138
139 <div class="no-light">
140 <div class="light"></div>
141 </div>
142 <p class="error hidden">Error fetching status.</p>
143 <div>
144 <h2>Checkin log</h2>
145 <div class="log"></div>
146 </div>
147 </div>
148
149 <script>
150 // Get references to DOM elements
151 const buttonIdInput = document.querySelector('[name=id]');
152 const statusLight = document.querySelector('.light');
153 const errorMessageDisplay = document.querySelector(".error");
154
155 // Key for local storage
156 const LOCAL_STORAGE_KEY = 'buttonStatusCheckerId';
157 // Interval for fetching status (e.g., every 10 seconds as per your code)
158 const FETCH_INTERVAL = 10 * 1000; // milliseconds
159
160 // Function to fetch the button status from the API
161 async function fetchButtonStatus() {
162 try {
163 // Hide any previous error messages
164 errorMessageDisplay.classList.add('hidden')
165
166 let id = buttonIdInput.value
167 fetch(`state/${id}`)
168 .then(resp => {
169 switch (resp.status) {
170 case 200:
171 statusLight.classList.add("ok");
172 break;
173 case 404:
174 statusLight.classList.remove("ok");
175 break;
176 default:
177 statusLight.classList.remove("ok");
178 throw new Error(`HTTP error! status: ${resp.status}`);
179 }
180 })
181
182 fetch(`log/${id}.log`, {cache: "no-store"})
183 .then(resp => resp.text())
184 .then(txt => {
185 let log = document.querySelector(".log")
186 log.replaceChildren()
187 for (let line of txt.split("\n").reverse()) {
188 let date = new Date(line)
189 if (!isFinite(date)) {
190 continue
191 }
192 log.appendChild(document.createElement("div")).textContent = date
193 }
194 })
195
196 } catch (error) {
197 console.error('Error fetching button status:', error);
198 // Show error message to the user
199 errorMessageDisplay.classList.remove('hidden');
200 // Ensure the light stops pulsing/blinking and shows error state
201 statusLight.classList.remove('ok'); // Ensure it's red/expired on error
202 }
203 }
204
205 // --- Initialize on page load ---
206 document.addEventListener('DOMContentLoaded', () => {
207 // Load saved button ID from local storage
208 const savedButtonId = localStorage.getItem(LOCAL_STORAGE_KEY);
209 if (savedButtonId) {
210 buttonIdInput.value = savedButtonId;
211 }
212
213 // Immediately fetch the status when the page loads
214 fetchButtonStatus();
215
216 // Set up an interval to fetch the status periodically
217 setInterval(fetchButtonStatus, FETCH_INTERVAL);
218 });
219
220 // --- Event Listener for Button ID Input ---
221 // Save button ID to local storage when input changes
222 buttonIdInput.addEventListener('input', () => {
223 localStorage.setItem(LOCAL_STORAGE_KEY, buttonIdInput.value);
224 // Re-fetch status immediately when the ID changes to reflect the new button's state
225 fetchButtonStatus();
226 });
227 </script>
228</body>
229</html>