mirror of
https://github.com/wahyd4/hey-search.git
synced 2026-08-08 21:05:14 +10:00
Show app version at the bottom
This commit is contained in:
@@ -2,7 +2,8 @@ name: Docker Build & Push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: ["**"]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
@@ -25,7 +26,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- 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
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
@@ -40,13 +41,18 @@ jobs:
|
||||
tags: |
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
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 }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
APP_VERSION=${{ steps.meta.outputs.version }}
|
||||
APP_COMMIT=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -27,6 +27,12 @@ COPY --from=frontend-build /app/frontend/dist /app/static
|
||||
# Persistent data directory for SQLite DB and configuration
|
||||
ENV DATA_DIR=/app/data
|
||||
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"]
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
@@ -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.history import get_history, delete_history_entry, clear_history
|
||||
from app import stats as _stats
|
||||
from app.version import get_version_info
|
||||
|
||||
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)):
|
||||
return _stats.get_summary(days)
|
||||
|
||||
|
||||
# --- Version ---
|
||||
|
||||
@router.get("/version", summary="App version", tags=["System"])
|
||||
async def api_version():
|
||||
return get_version_info()
|
||||
|
||||
@@ -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
@@ -11,7 +11,7 @@ import { Bookmarks } from "@/components/Bookmarks";
|
||||
import { AppHeader } from "@/components/AppHeader";
|
||||
import { StatsPage } from "@/components/StatsPage";
|
||||
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";
|
||||
|
||||
type Category = "web" | "images";
|
||||
@@ -82,10 +82,14 @@ function App() {
|
||||
// Bookmarked URLs for toggle state
|
||||
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(() => {
|
||||
getBackground().then(setBgInfo).catch(() => {});
|
||||
getBookmarkedUrls().then(setBookmarkedUrls).catch(() => {});
|
||||
getVersion().then(setVersionInfo).catch(() => {});
|
||||
}, []);
|
||||
|
||||
const bgUrl = bgInfo?.enabled && bgInfo?.url ? bgInfo.url : null;
|
||||
@@ -704,7 +708,16 @@ function App() {
|
||||
|
||||
{/* Footer */}
|
||||
<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>
|
||||
|
||||
{/* Error toasts */}
|
||||
|
||||
@@ -359,3 +359,17 @@ export async function deleteHistoryEntry(id: number): Promise<void> {
|
||||
export async function clearHistory(): Promise<void> {
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user