The API emits naive ICT timestamps ("2026-09-12T02:00:00"). The page fed
them to new Date(), which applies the BROWSER's zone: a viewer in Europe
parsed a 02:00 ICT reading as 02:00 CEST, five hours in the future, so
"Last updated" clamped to "0 min ago" forever; a viewer in the Americas
saw thousands of minutes. parseTs() now pins +07:00 on naive strings and
every display formats with timeZone: Asia/Bangkok, so the site shows
river time regardless of where it is opened. Daily/hourly chart buckets
key on the Bangkok calendar day instead of UTC getters.
The tile also gets a real stale state: past 3 h (RID is hourly) it turns
red, reads "Feed stale · last reading <day>, N h ago", and the header
pill switches from LIVE DATA to STALE FEED. Age shows hours past 2 h.
scripts/dev_proxy.py serves the working-copy dashboard with API calls
proxied to water.buildfor.life so browser-side changes can be checked
against live data (and any browser timezone) before deploying.
52 lines
2.0 KiB
Python
52 lines
2.0 KiB
Python
"""Serve the working-copy dashboard locally with API calls proxied to the
|
|
live server, so browser-side changes can be checked against real data
|
|
before deploy. Usage: python scripts/dev_proxy.py [port]"""
|
|
|
|
import http.server
|
|
import sys
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
UPSTREAM = "https://water.buildfor.life"
|
|
STATIC = Path(__file__).resolve().parents[1] / "src" / "static"
|
|
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
if self.path == "/" or self.path.startswith("/?"):
|
|
body = (STATIC / "dashboard.html").read_bytes()
|
|
self._send(200, "text/html; charset=utf-8", body)
|
|
return
|
|
if self.path.startswith("/static/"):
|
|
f = STATIC / self.path[len("/static/"):].split("?")[0]
|
|
if f.is_file():
|
|
ctype = "application/json" if f.suffix in (".json", ".geojson") else "application/octet-stream"
|
|
self._send(200, ctype, f.read_bytes())
|
|
return
|
|
try:
|
|
req = urllib.request.Request(
|
|
UPSTREAM + self.path,
|
|
headers={"User-Agent": "Mozilla/5.0 (dev_proxy; +https://buildfor.life)", "Accept": "application/json"},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=60) as r:
|
|
self._send(r.status, r.headers.get("Content-Type", "application/json"), r.read())
|
|
except urllib.error.HTTPError as e:
|
|
self._send(e.code, "application/json", e.read())
|
|
|
|
def _send(self, code, ctype, body):
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", ctype)
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.send_header("Cache-Control", "no-store")
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def log_message(self, *a):
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765
|
|
print(f"http://localhost:{port}/ (API -> {UPSTREAM})")
|
|
http.server.ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
|