Show app version at the bottom

This commit is contained in:
2026-03-07 10:59:49 +11:00
parent bb9afffe4b
commit ace053a547
6 changed files with 80 additions and 6 deletions
+9 -3
View File
@@ -2,7 +2,8 @@ name: Docker Build & Push
on: on:
push: push:
branches: [master] branches: ["**"]
tags: ["v*"]
pull_request: pull_request:
branches: [master] branches: [master]
@@ -25,7 +26,7 @@ jobs:
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/master' if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
@@ -40,13 +41,18 @@ jobs:
tags: | tags: |
type=sha type=sha
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} push: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')) }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=${{ steps.meta.outputs.version }}
APP_COMMIT=${{ github.sha }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
+6
View File
@@ -27,6 +27,12 @@ COPY --from=frontend-build /app/frontend/dist /app/static
# Persistent data directory for SQLite DB and configuration # Persistent data directory for SQLite DB and configuration
ENV DATA_DIR=/app/data ENV DATA_DIR=/app/data
ENV REDIS_URL="" ENV REDIS_URL=""
# Version injected at build time via --build-arg
ARG APP_VERSION=dev
ARG APP_COMMIT=unknown
ENV APP_VERSION=${APP_VERSION}
ENV APP_COMMIT=${APP_COMMIT}
VOLUME ["/app/data"] VOLUME ["/app/data"]
EXPOSE 8000 EXPOSE 8000
+8
View File
@@ -15,6 +15,7 @@ from app.settings import get_all_settings, get_setting, set_setting
from app.cache import is_cache_available, flush_cache, reconnect_redis from app.cache import is_cache_available, flush_cache, reconnect_redis
from app.history import get_history, delete_history_entry, clear_history from app.history import get_history, delete_history_entry, clear_history
from app import stats as _stats from app import stats as _stats
from app.version import get_version_info
router = APIRouter() router = APIRouter()
@@ -690,3 +691,10 @@ async def api_record_click(body: ClickEventRequest, request: Request):
) )
async def api_get_stats(days: int = Query(7, ge=1, le=365)): async def api_get_stats(days: int = Query(7, ge=1, le=365)):
return _stats.get_summary(days) return _stats.get_summary(days)
# --- Version ---
@router.get("/version", summary="App version", tags=["System"])
async def api_version():
return get_version_info()
+27
View File
@@ -0,0 +1,27 @@
"""Version detection — reads env vars injected at Docker build time, falls back to git for local dev."""
from __future__ import annotations
import os
import subprocess
def _git(cmd: list[str]) -> str:
try:
return subprocess.check_output(cmd, stderr=subprocess.DEVNULL, cwd=os.path.dirname(__file__)).decode().strip()
except Exception:
return ""
def get_version_info() -> dict[str, str]:
"""Return {"version": ..., "commit": ..., "local": bool_as_str}."""
version = os.environ.get("APP_VERSION", "")
commit = os.environ.get("APP_COMMIT", "")
if not version and not commit:
# Local dev — derive from git
version = _git(["git", "describe", "--tags", "--abbrev=0"]) or "local"
commit = _git(["git", "rev-parse", "--short", "HEAD"]) or "unknown"
return {"version": version, "commit": commit, "local": "true"}
return {"version": version, "commit": commit[:7] if commit else "", "local": "false"}
+16 -3
View File
@@ -11,7 +11,7 @@ import { Bookmarks } from "@/components/Bookmarks";
import { AppHeader } from "@/components/AppHeader"; import { AppHeader } from "@/components/AppHeader";
import { StatsPage } from "@/components/StatsPage"; import { StatsPage } from "@/components/StatsPage";
import { History } from "@/components/History"; import { History } from "@/components/History";
import { search as apiSearch, isImageResult, getBackground, refreshBackground, getBookmarkedUrls, addBookmark, removeBookmarkByUrl, type SearchResponse, type WebResult, type ImageResult, type BackgroundInfo } from "@/lib/api"; import { search as apiSearch, isImageResult, getBackground, refreshBackground, getBookmarkedUrls, addBookmark, removeBookmarkByUrl, getVersion, type SearchResponse, type WebResult, type ImageResult, type BackgroundInfo, type VersionInfo } from "@/lib/api";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
type Category = "web" | "images"; type Category = "web" | "images";
@@ -82,10 +82,14 @@ function App() {
// Bookmarked URLs for toggle state // Bookmarked URLs for toggle state
const [bookmarkedUrls, setBookmarkedUrls] = useState<Set<string>>(new Set()); const [bookmarkedUrls, setBookmarkedUrls] = useState<Set<string>>(new Set());
// Fetch background and bookmarked URLs on mount // App version
const [versionInfo, setVersionInfo] = useState<VersionInfo | null>(null);
// Fetch background, bookmarked URLs, and version on mount
useEffect(() => { useEffect(() => {
getBackground().then(setBgInfo).catch(() => {}); getBackground().then(setBgInfo).catch(() => {});
getBookmarkedUrls().then(setBookmarkedUrls).catch(() => {}); getBookmarkedUrls().then(setBookmarkedUrls).catch(() => {});
getVersion().then(setVersionInfo).catch(() => {});
}, []); }, []);
const bgUrl = bgInfo?.enabled && bgInfo?.url ? bgInfo.url : null; const bgUrl = bgInfo?.enabled && bgInfo?.url ? bgInfo.url : null;
@@ -704,7 +708,16 @@ function App() {
{/* Footer */} {/* Footer */}
<footer className="border-t px-4 py-3 text-xs text-muted-foreground"> <footer className="border-t px-4 py-3 text-xs text-muted-foreground">
<div className="mx-auto max-w-6xl text-center">Hey Search</div> <div className="mx-auto max-w-6xl text-center">
Hey Search
{versionInfo && (
<span className="ml-1.5 opacity-60">
{versionInfo.local === "true"
? `local · ${versionInfo.commit}`
: versionInfo.version}
</span>
)}
</div>
</footer> </footer>
{/* Error toasts */} {/* Error toasts */}
+14
View File
@@ -359,3 +359,17 @@ export async function deleteHistoryEntry(id: number): Promise<void> {
export async function clearHistory(): Promise<void> { export async function clearHistory(): Promise<void> {
await fetch(`${API_BASE}/history`, { method: "DELETE" }); await fetch(`${API_BASE}/history`, { method: "DELETE" });
} }
// --- Version ---
export interface VersionInfo {
version: string;
commit: string;
local: string; // "true" | "false"
}
export async function getVersion(): Promise<VersionInfo> {
const resp = await fetch(`${API_BASE}/version`);
if (!resp.ok) throw new Error("Failed to fetch version");
return resp.json();
}