Show app version in admin UI and /version endpoint

Embed APP_VERSION build arg in Docker image (default: dev).
build_push.sh passes the git tag as build arg. Proxy exposes
GET /version, admin UI shows it as read-only field in settings.
This commit is contained in:
Oliver Hofmann 2026-05-08 09:30:23 +02:00
parent 6761a73364
commit 07f6fec4bf
5 changed files with 17 additions and 1 deletions

View File

@ -6,6 +6,8 @@ COPY frontend/ frontend/
RUN npm run build --prefix frontend RUN npm run build --prefix frontend
FROM python:3.12-slim FROM python:3.12-slim
ARG APP_VERSION=dev
ENV APP_VERSION=$APP_VERSION
WORKDIR /app WORKDIR /app
COPY backend/requirements.txt . COPY backend/requirements.txt .

View File

@ -131,7 +131,10 @@ async def get_proxy_info(_ = Depends(require_admin_auth)):
host = os.getenv("PROXY_HOST", "0.0.0.0") host = os.getenv("PROXY_HOST", "0.0.0.0")
port = os.getenv("PROXY_PORT", "8000") port = os.getenv("PROXY_PORT", "8000")
display_host = "localhost" if host in ("0.0.0.0", "::") else host display_host = "localhost" if host in ("0.0.0.0", "::") else host
return {"endpoint": f"http://{display_host}:{port}"} return {
"endpoint": f"http://{display_host}:{port}",
"version": os.getenv("APP_VERSION", "dev"),
}
@app.get("/api/settings", response_model=schemas.Settings) @app.get("/api/settings", response_model=schemas.Settings)
async def read_settings(db: Session = Depends(get_db), _ = Depends(require_admin_auth)): async def read_settings(db: Session = Depends(get_db), _ = Depends(require_admin_auth)):

View File

@ -149,6 +149,10 @@ async def list_models(db: Session = Depends(get_db)):
response = await proxy_request(f"{ollama_url}/api/tags", method="GET") response = await proxy_request(f"{ollama_url}/api/tags", method="GET")
return JSONResponse(content=response.json(), status_code=response.status_code) return JSONResponse(content=response.json(), status_code=response.status_code)
@app.get("/version")
async def version():
return {"version": os.getenv("APP_VERSION", "dev")}
@app.get("/api/ps") @app.get("/api/ps")
async def running_models(db: Session = Depends(get_db)): async def running_models(db: Session = Depends(get_db)):
ollama_url = crud.get_setting(db, "ollama_url", os.getenv("OLLAMA_URL", "http://localhost:11434")) ollama_url = crud.get_setting(db, "ollama_url", os.getenv("OLLAMA_URL", "http://localhost:11434"))

View File

@ -37,6 +37,7 @@ echo ""
docker buildx build \ docker buildx build \
--platform "$PLATFORM" \ --platform "$PLATFORM" \
--push \ --push \
--build-arg APP_VERSION="$VERSION" \
-t "$IMAGE:$VERSION" \ -t "$IMAGE:$VERSION" \
-t "$IMAGE:latest" \ -t "$IMAGE:latest" \
. .

View File

@ -82,6 +82,7 @@ function SettingsSection({ password }) {
const [modelsLoading, setModelsLoading] = useState(false); const [modelsLoading, setModelsLoading] = useState(false);
const [ollamaReachable, setOllamaReachable] = useState(true); const [ollamaReachable, setOllamaReachable] = useState(true);
const [proxyEndpoint, setProxyEndpoint] = useState(null); const [proxyEndpoint, setProxyEndpoint] = useState(null);
const [appVersion, setAppVersion] = useState(null);
const [saved, setSaved] = useState(false); const [saved, setSaved] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
@ -115,6 +116,7 @@ function SettingsSection({ password }) {
const s = settingsRes.data; const s = settingsRes.data;
setSettings(s); setSettings(s);
setProxyEndpoint(proxyRes.data.endpoint); setProxyEndpoint(proxyRes.data.endpoint);
setAppVersion(proxyRes.data.version);
fetchModels(s.ollama_url, s.force_model); fetchModels(s.ollama_url, s.force_model);
}).catch(() => setError('Einstellungen konnten nicht geladen werden.')); }).catch(() => setError('Einstellungen konnten nicht geladen werden.'));
}, []); }, []);
@ -145,6 +147,10 @@ function SettingsSection({ password }) {
<small> (Änderung erfordert Neustart)</small> <small> (Änderung erfordert Neustart)</small>
</span> </span>
</div> </div>
<div className="settings-row">
<label>Version</label>
<span className="settings-value">{appVersion ?? '…'}</span>
</div>
<div className="settings-row"> <div className="settings-row">
<label>Ollama-Endpunkt</label> <label>Ollama-Endpunkt</label>
<div className="settings-input-wrap"> <div className="settings-input-wrap">