diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 1e59a34..0e183e1 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -4,9 +4,10 @@ from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict # Single source of truth for the running app's version — surfaced in the API -# title and embedded in exported backups so an old file can be told apart -# from a current one. -APP_VERSION = "0.20.0" +# title, /api/health (which the frontend reads to show it in Settings), and +# embedded in exported backups so an old file can be told apart from a +# current one. +APP_VERSION = "1.1.0" class Settings(BaseSettings): diff --git a/backend/app/main.py b/backend/app/main.py index 14e00ec..ce2dacd 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -88,4 +88,7 @@ app.include_router(crypto.router, prefix="/api") @app.get("/api/health") async def health() -> dict[str, str]: - return {"status": "ok"} + # version rides along so the frontend's Settings page can show which + # release is actually running without a separate authenticated endpoint — + # this route is already auth_basic-exempt for Docker's HEALTHCHECK. + return {"status": "ok", "version": APP_VERSION} diff --git a/frontend/package.json b/frontend/package.json index 0c78088..ed56531 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "aurum-frontend", "private": true, - "version": "0.26.0", + "version": "1.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/api/health.ts b/frontend/src/api/health.ts new file mode 100644 index 0000000..d6f395b --- /dev/null +++ b/frontend/src/api/health.ts @@ -0,0 +1,6 @@ +import { api } from "@/api/client"; +import type { HealthStatus } from "@/types"; + +export function fetchHealth() { + return api.get("/health"); +} diff --git a/frontend/src/hooks/useHealth.ts b/frontend/src/hooks/useHealth.ts new file mode 100644 index 0000000..310f818 --- /dev/null +++ b/frontend/src/hooks/useHealth.ts @@ -0,0 +1,8 @@ +import { useQuery } from "@tanstack/react-query"; +import { fetchHealth } from "@/api/health"; + +// The running version only changes on a deploy (a full page reload), so +// there's no need to ever refetch within a session. +export function useHealth() { + return useQuery({ queryKey: ["health"], queryFn: fetchHealth, staleTime: Infinity }); +} diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts index e6d4f09..f3abaf6 100644 --- a/frontend/src/lib/i18n.ts +++ b/frontend/src/lib/i18n.ts @@ -341,6 +341,7 @@ const ru = { "Восстановить данные из «{{filename}}»?\n\nЭто ПОЛНОСТЬЮ заменит все текущие счета, категории, транзакции и активы данными из файла. Отменить это действие нельзя — если хотите сохранить текущие данные, сначала сделайте экспорт.", "backup.footerWarning": "Восстановление полностью заменяет текущие данные содержимым файла — это нельзя отменить.", + "settings.version": "Aurum v{{version}}", "insights.negativeCashFlow": "Расходы превышают доходы {{months}} мес. подряд (по последним завершённым месяцам)", "insights.netWorthDecline": "Капитал снижается {{months}} мес. подряд", @@ -823,6 +824,7 @@ const en: Record = { "Restore data from “{{filename}}”?\n\nThis will COMPLETELY replace all current accounts, categories, transactions, and assets with the data from the file. This action cannot be undone — if you want to keep your current data, export it first.", "backup.footerWarning": "Restoring completely replaces your current data with the file's contents — this cannot be undone.", + "settings.version": "Aurum v{{version}}", "insights.negativeCashFlow": "Expenses have exceeded income for {{months}} months in a row (based on the last completed months)", "insights.netWorthDecline": "Net worth has been declining for {{months}} months in a row", diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 16255ae..d2ee61c 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -2,14 +2,26 @@ import { AlertThresholdsCard } from "@/components/settings/AlertThresholdsCard"; import { BackupCard } from "@/components/settings/BackupCard"; import { CurrencyCard } from "@/components/settings/CurrencyCard"; import { PreferencesCard } from "@/components/settings/PreferencesCard"; +import { useHealth } from "@/hooks/useHealth"; +import { t } from "@/lib/i18n"; export function SettingsPage() { + // Purely informational — if /api/health hasn't answered yet (or is + // unreachable), just show nothing rather than a loading/error state for + // one line of fine print. + const { data: health } = useHealth(); + return (
+ {health?.version && ( +

+ {t("settings.version", { version: health.version })} +

+ )}
); } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index b1c124a..dc85c4e 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -517,3 +517,8 @@ export interface AppSettings { idle_cash_threshold_days: number; } +export interface HealthStatus { + status: string; + version: string; +} +