diff --git a/server.py b/server.py index ae88f51..c5543c1 100644 --- a/server.py +++ b/server.py @@ -9,6 +9,8 @@ import io import json import logging import os +import pathlib +import subprocess from urllib.parse import parse_qs from aiohttp import web @@ -23,6 +25,24 @@ logging.basicConfig( logger = logging.getLogger(__name__) +# ── Version (computed once at startup) ─────────────────────────── + +def _compute_version() -> str: + """Return `git describe --tags --always --dirty`, or 'unknown'.""" + try: + out = subprocess.run( + ["git", "describe", "--tags", "--always", "--dirty"], + cwd=pathlib.Path(__file__).parent, + capture_output=True, text=True, timeout=2, check=True, + ) + return out.stdout.strip() or "unknown" + except (subprocess.SubprocessError, FileNotFoundError, OSError): + return "unknown" + + +_VERSION = _compute_version() + + # ── Token (injected by start.py via env) ───────────────────────── def _get_bot_token() -> str: @@ -206,6 +226,11 @@ async def api_export_json(request: web.Request): return web.json_response({"records": data, "count": len(data)}) +async def api_version(request: web.Request): + """Return the running server version. Unauthenticated.""" + return web.json_response({"version": _VERSION}) + + @require_auth async def api_export_csv(request: web.Request): """Export all workouts as CSV.""" @@ -239,9 +264,9 @@ def create_app() -> web.Application: app.router.add_get("/api/stats", api_get_stats) app.router.add_get("/api/export/json", api_export_json) app.router.add_get("/api/export/csv", api_export_csv) + app.router.add_get("/api/version", api_version) # Serve the webapp/ folder - import pathlib webapp_dir = pathlib.Path(__file__).parent / "webapp" async def index_handler(request): diff --git a/webapp/app.js b/webapp/app.js index 86127a8..d4fc246 100644 --- a/webapp/app.js +++ b/webapp/app.js @@ -767,8 +767,22 @@ function fmtWeight(w) { return w === Math.floor(w) ? Math.floor(w).toString() : w.toString(); } +// ── Version badge ─────────────────────────────────────────────── +async function loadVersion() { + try { + const res = await fetch(API + "/version"); + if (!res.ok) return; + const data = await res.json(); + const badge = document.getElementById("version-badge"); + if (badge && data.version) badge.textContent = data.version; + } catch (e) { + // Silent — footer just stays empty if unreachable + } +} + // ── Init ──────────────────────────────────────────────────────── async function init() { + loadVersion(); if (!userId) return; try { const data = await api("GET", "/exercises"); diff --git a/webapp/index.html b/webapp/index.html index 8ed08e6..540e7aa 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -88,6 +88,10 @@
Loading...
+ + diff --git a/webapp/style.css b/webapp/style.css index 368fceb..b7796b8 100644 --- a/webapp/style.css +++ b/webapp/style.css @@ -472,3 +472,15 @@ details[open] .raw-toggle::before { transform: translateX(-50%) translateY(0); opacity: 1; } + +/* ── Footer / version badge ──────────────────────────────────── */ + +#app-footer { + margin-top: 24px; + padding: 12px 16px 20px; + text-align: center; + font-size: 11px; + color: var(--tg-theme-hint-color, #999); + opacity: 0.6; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; +}