From b091cf70038260df83a12a50cda4268160e0a062 Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr Date: Tue, 21 Apr 2026 20:19:40 +0200 Subject: [PATCH] feat(ui): version badge in footer + /api/version endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI now shows a small footer badge with: - release channel + CalVer version (e.g. 'stable-2026.04.47') - floating image tag (e.g. 'stable') - time since last container restart (proxy for 'last deployed') Backend: - app/api/health.py: /api/health returns image_tag, commit_sha, deployed_at - app/api/health.py: new /api/version endpoint (lightweight, no DB hit, for footer badge polling) Infra: - startup-script.sh.tpl: resolves image digest from ghcr pull, derives channel + version from the tag name, and writes AGNES_VERSION / RELEASE_CHANNEL / AGNES_COMMIT_SHA into .env so the app can surface them to the UI. UI: - app/web/templates/base.html: footer loads /api/version asynchronously and renders '- · · deployed ()'. Tooltip shows full detail (commit sha, schema version). --- app/api/health.py | 21 ++++++++++++++++ app/web/templates/base.html | 24 +++++++++++++++++++ .../customer-instance/startup-script.sh.tpl | 22 +++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/app/api/health.py b/app/api/health.py index 2cc670e..e6734c6 100644 --- a/app/api/health.py +++ b/app/api/health.py @@ -12,6 +12,11 @@ from src.repositories.sync_state import SyncStateRepository router = APIRouter(tags=["health"]) +# Captured at module import (i.e., app process start) — proxy for "deployed at". +# When the cron auto-upgrade pulls a new digest and recreates the container, +# this resets. Accurate enough for a UI "last updated" badge. +_DEPLOYED_AT = datetime.now(timezone.utc).isoformat() + @router.get("/api/health") async def health_check(conn: duckdb.DuckDBPyConnection = Depends(_get_db)): @@ -73,7 +78,23 @@ async def health_check(conn: duckdb.DuckDBPyConnection = Depends(_get_db)): "status": overall, "version": os.environ.get("AGNES_VERSION", "dev"), "channel": os.environ.get("RELEASE_CHANNEL", "dev"), + "image_tag": os.environ.get("AGNES_TAG", "unknown"), + "commit_sha": os.environ.get("AGNES_COMMIT_SHA", "unknown"), "schema_version": SCHEMA_VERSION, + "deployed_at": _DEPLOYED_AT, "timestamp": datetime.now(timezone.utc).isoformat(), "services": checks, } + + +@router.get("/api/version") +async def version_info(): + """Lightweight version info — cacheable, no DB touch. Used by UI footer badge.""" + return { + "version": os.environ.get("AGNES_VERSION", "dev"), + "channel": os.environ.get("RELEASE_CHANNEL", "dev"), + "image_tag": os.environ.get("AGNES_TAG", "unknown"), + "commit_sha": os.environ.get("AGNES_COMMIT_SHA", "unknown"), + "schema_version": SCHEMA_VERSION, + "deployed_at": _DEPLOYED_AT, + } diff --git a/app/web/templates/base.html b/app/web/templates/base.html index 4f9c8ac..6fce72f 100644 --- a/app/web/templates/base.html +++ b/app/web/templates/base.html @@ -46,7 +46,31 @@

© {{ now().year if now is defined else 2024 }} {{ config.INSTANCE_COPYRIGHT or 'AI Data Analyst' }}

+

+ Loading version… +

+ diff --git a/infra/modules/customer-instance/startup-script.sh.tpl b/infra/modules/customer-instance/startup-script.sh.tpl index ef4b1ef..01e0a40 100644 --- a/infra/modules/customer-instance/startup-script.sh.tpl +++ b/infra/modules/customer-instance/startup-script.sh.tpl @@ -68,6 +68,25 @@ if [ "$DATA_SOURCE" = "keboola" ]; then fi JWT_KEY=$(gcloud secrets versions access latest --secret=agnes-$${CUSTOMER_NAME}-jwt-secret) +# Resolve the actual version/commit behind the requested tag so the UI can +# show specific `stable-2026.04.47` + commit SHA instead of just `stable`. +IMAGE_DIGEST=$(docker pull "$IMAGE_REPO:$IMAGE_TAG" 2>/dev/null | grep -o 'sha256:[a-f0-9]*' | head -1 || echo "unknown") +IMAGE_INFO=$(curl -fsSL "https://ghcr.io/v2/keboola/agnes-the-ai-analyst/manifests/$IMAGE_TAG" -H "Accept: application/vnd.oci.image.manifest.v1+json" 2>/dev/null || echo "{}") + +# Channel derived from tag prefix (stable-*/dev-*/release-*) — simple heuristic. +case "$IMAGE_TAG" in + stable*) RELEASE_CHANNEL="stable" ;; + dev*) RELEASE_CHANNEL="dev" ;; + release*) RELEASE_CHANNEL="release" ;; + *) RELEASE_CHANNEL="custom" ;; +esac + +# Version extracted from versioned tags (stable-2026.04.N); floating tags stay "dev". +case "$IMAGE_TAG" in + *-[0-9]*.[0-9]*.[0-9]*) AGNES_VERSION="$${IMAGE_TAG#*-}" ;; + *) AGNES_VERSION="$IMAGE_TAG" ;; +esac + cat > "$APP_DIR/.env" <