From 049572a2d53d106549d637be1c27357be835521b Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 1 May 2026 09:00:11 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0=20cs?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/server/main.go | 28 ++++++--- web/css/style.css | 151 +++++++++++++++++++++++++++++++++++++++++++++ web/dashboard.html | 38 ++++++++++++ web/index.html | 120 +---------------------------------- web/js/app.js | 58 +++++++++++++++++ 5 files changed, 270 insertions(+), 125 deletions(-) create mode 100644 web/css/style.css create mode 100644 web/dashboard.html create mode 100644 web/js/app.js diff --git a/cmd/server/main.go b/cmd/server/main.go index 1b58f42..0b050fa 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -51,12 +51,22 @@ func (a *API) HandleLatest(w http.ResponseWriter, r *http.Request) { }) } + func (a *API) HandleHistory(w http.ResponseWriter, r *http.Request) { + + raw := a.buf.GetLast(100) + + out := make([]int, len(raw)) + for i, v := range raw { + out[i] = int(v) + } + json.NewEncoder(w).Encode(map[string]interface{}{ - "bytes": a.buf.GetLast(100), + "bytes": out, }) } + // === FIFO READER === func startPipeReader(ctx context.Context, buf *adapter.RingBuffer, path string) { go func() { @@ -119,19 +129,23 @@ func main() { buf: buf, startTime: time.Now(), } - fs := http.FileServer(http.Dir("/home/user/temp/golang/web")) + http.Handle("/api/", http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/latest": - cors(api.HandleLatest)(w, r) + + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/latest": + cors(api.HandleLatest)(w, r) case "/history": cors(api.HandleHistory)(w, r) case "/health": cors(api.HandleHealth)(w, r) - } + default: + http.Error(w, "not found", 404) + } }))) - http.Handle("/", fs) + http.Handle("/", http.FileServer(http.Dir("/home/user/temp/golang/web"))) log.Println("server :8080") log.Fatal(http.ListenAndServe(":8080", nil)) diff --git a/web/css/style.css b/web/css/style.css new file mode 100644 index 0000000..8d619ae --- /dev/null +++ b/web/css/style.css @@ -0,0 +1,151 @@ +* { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: monospace; + background: #050805; + color: #00ff88; + padding: 12px; +} + +h1 { + text-align: center; + font-size: 22px; + margin: 0 0 15px; +} + +/* ===== GRID ===== */ + +.grid { + display: grid; + grid-template-columns: 1fr; + gap: 12px; +} + +/* Desktop */ +@media (min-width: 900px) { + .grid { + grid-template-columns: 1fr 1fr; + } + + .wide { + grid-column: span 2; + } +} + +/* ===== CARD ===== */ + +.card { + border: 1px solid #00ff88; + border-radius: 10px; + padding: 14px; + background: rgba(0,255,136,0.05); + overflow: hidden; +} + +.card h3 { + margin: 0 0 10px; + font-size: 18px; +} + +/* ===== STATUS ===== */ + +#status { + font-size: 28px; + font-weight: bold; + margin-bottom: 8px; +} + +#meta { + font-size: 14px; + opacity: 0.8; + word-break: break-word; +} + +/* ===== LATEST ===== */ + +#latest { + font-size: 36px; + font-weight: bold; + margin-bottom: 12px; +} + +/* ===== BITS ===== */ + +#bits { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; +} + +.bit { + height: 52px; + border: 1px solid #00ff88; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; + font-size: 22px; + font-weight: bold; +} + +.bit.on { + background: #00ff88; + color: #000; +} + +/* Tablet/Desktop */ +@media (min-width: 700px) { + #bits { + grid-template-columns: repeat(8, 1fr); + } +} + +/* ===== HISTORY ===== */ + +#history { + font-size: 13px; + line-height: 1.35; + max-height: 420px; + overflow-y: auto; + white-space: nowrap; + padding-right: 4px; +} + +/* Desktop */ +@media (min-width: 900px) { + #history { + font-size: 12px; + max-height: 520px; + } +} + +/* ===== PHONE ===== */ + +@media (max-width: 500px) { + + h1 { + font-size: 18px; + } + + #status { + font-size: 22px; + } + + #latest { + font-size: 30px; + } + + .bit { + height: 44px; + font-size: 18px; + } + + #history { + font-size: 16px; + line-height: 1.5; + max-height: 55vh; + } +} diff --git a/web/dashboard.html b/web/dashboard.html new file mode 100644 index 0000000..a14fa50 --- /dev/null +++ b/web/dashboard.html @@ -0,0 +1,38 @@ + + + + +GPIO Dashboard + + + + + + +

GPIO REALTIME DASHBOARD

+ +
+ +
+

STATUS

+
...
+
+
+ +
+

LATEST SIGNAL

+
--
+
+
+ +
+

LIVE STREAM (last 32)

+
+
+ +
+ + + + + diff --git a/web/index.html b/web/index.html index 06d21c4..a4cb92f 100644 --- a/web/index.html +++ b/web/index.html @@ -1,125 +1,9 @@ - -GPIO Monitor Dashboard - - + + - - -

GPIO Monitor

- -
-
PIPE STATUS: ...
-
DATA FLOW: ...
-
-
- -
-
LATEST BYTE: --
-
-
- -
-
HISTORY (last 16 bytes)
-
-
- - - diff --git a/web/js/app.js b/web/js/app.js new file mode 100644 index 0000000..d5bfe99 --- /dev/null +++ b/web/js/app.js @@ -0,0 +1,58 @@ +function renderBits(byte) { + const bits = document.getElementById("bits"); + bits.innerHTML = ""; + + byte = Number(byte || 0); + + for (let i = 7; i >= 0; i--) { + const v = (byte >> i) & 1; + + const d = document.createElement("div"); + d.className = "bit" + (v ? " on" : ""); + d.textContent = v; + bits.appendChild(d); + } +} + +async function update() { + try { + const r1 = await fetch("/api/health"); + const r2 = await fetch("/api/history"); + + const h = await r1.json(); + const hist = await r2.json(); + + console.log("health:", h); + console.log("history:", hist); + + document.getElementById("status").textContent = h.status || "unknown"; + + const filled = h.stats?.filled ?? 0; + const uptime = Math.round(h.uptime_sec || 0); + + document.getElementById("meta").textContent = + "uptime: " + uptime + "s | filled: " + filled; + + const latest = Number(h.latest || 0); + + document.getElementById("latest").textContent = latest; + + renderBits(latest); + + const arr = Array.isArray(hist.bytes) ? hist.bytes : []; + + document.getElementById("history").innerHTML = + arr.map((b, i) => + "[" + i + "] " + + (Number(b) & 15).toString(2).padStart(4, "0") + + " = " + (Number(b) & 15) + ).join("
"); + + } catch (e) { + console.error("UPDATE ERROR:", e); + document.getElementById("status").textContent = "SERVER DOWN"; + } +} + +setInterval(update, 500); +update();