feat: Versionsanzeige (VERSION-Datei, Footer, /api/version, Image-Tag)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ GRAFANA_CTR_NAME='finance-grafana_ctr'
|
|||||||
# needed).
|
# needed).
|
||||||
POSTGRES_IMAGE='docker.io/library/postgres:17.10'
|
POSTGRES_IMAGE='docker.io/library/postgres:17.10'
|
||||||
GRAFANA_IMAGE='docker.io/grafana/grafana-oss:12.1.0'
|
GRAFANA_IMAGE='docker.io/grafana/grafana-oss:12.1.0'
|
||||||
API_IMAGE='localhost/finance-api:0.1.0'
|
API_IMAGE="localhost/finance-api:$(cat "$HOME/bin/finance/VERSION")"
|
||||||
|
|
||||||
HOST_LOCAL_IP='127.0.0.1'
|
HOST_LOCAL_IP='127.0.0.1'
|
||||||
# Expose API on 8096 -> 8000
|
# Expose API on 8096 -> 8000
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
COPY app/ app/
|
COPY app/ app/
|
||||||
COPY alembic/ alembic/
|
COPY alembic/ alembic/
|
||||||
COPY alembic.ini .
|
COPY alembic.ini .
|
||||||
|
COPY VERSION .
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|||||||
1
finance/VERSION
Normal file
1
finance/VERSION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0.2.0
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import hmac
|
import hmac
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI, Form, Request
|
from fastapi import Depends, FastAPI, Form, Request
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from app import auth
|
from app import auth
|
||||||
|
from app.auth import require_auth
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
from app.db import get_engine
|
from app.db import get_engine
|
||||||
from app.models.views import create_views
|
from app.models.views import create_views
|
||||||
from app.routers import (accounts, categories, gui, imports, planning,
|
from app.routers import (accounts, categories, gui, imports, planning,
|
||||||
scenarios, transactions)
|
scenarios, transactions)
|
||||||
from app.routers.gui import templates
|
from app.routers.gui import templates
|
||||||
|
from app.version import get_version
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -55,3 +57,8 @@ def logout():
|
|||||||
resp = RedirectResponse("/login", status_code=303)
|
resp = RedirectResponse("/login", status_code=303)
|
||||||
resp.delete_cookie(auth.COOKIE)
|
resp.delete_cookie(auth.COOKIE)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/version", dependencies=[Depends(require_auth)])
|
||||||
|
def api_version():
|
||||||
|
return {"version": get_version()}
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ from app.routers.planning import recurring_suggestions as _recurring_suggestions
|
|||||||
from app.routers.scenarios import list_scenarios as _list_scenarios
|
from app.routers.scenarios import list_scenarios as _list_scenarios
|
||||||
from app.routers.transactions import list_transactions
|
from app.routers.transactions import list_transactions
|
||||||
from app.services.balances import account_balance, total_balance
|
from app.services.balances import account_balance, total_balance
|
||||||
|
from app.version import get_version
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
templates.env.globals["app_version"] = get_version()
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -151,3 +151,9 @@ section.planning-section {
|
|||||||
section.planning-section fieldset {
|
section.planning-section fieldset {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,5 +64,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<footer class="version">Finanzberatungs-Tool v{{ app_version }}</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
9
finance/app/version.py
Normal file
9
finance/app/version.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def get_version() -> str:
|
||||||
|
f = Path(__file__).resolve().parent.parent / "VERSION"
|
||||||
|
try:
|
||||||
|
return f.read_text().strip()
|
||||||
|
except OSError:
|
||||||
|
return "0.0.0-dev"
|
||||||
@@ -33,6 +33,13 @@ def test_import_list_fragment_renders_after_login(client):
|
|||||||
assert "Noch keine Imports" in r.text
|
assert "Noch keine Imports" in r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_visible(client):
|
||||||
|
r = client.get("/api/version", headers={"Authorization": "Bearer test-key"})
|
||||||
|
assert r.status_code == 200 and r.json()["version"] == "0.2.0"
|
||||||
|
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||||
|
assert "v0.2.0" in client.get("/").text
|
||||||
|
|
||||||
|
|
||||||
def test_planning_page_has_all_sections(client):
|
def test_planning_page_has_all_sections(client):
|
||||||
client.post("/login", data={"username": "admin", "password": "geheim"})
|
client.post("/login", data={"username": "admin", "password": "geheim"})
|
||||||
r = client.get("/planung")
|
r = client.get("/planung")
|
||||||
|
|||||||
Reference in New Issue
Block a user