feat: показывать версию приложения в настройках, версия 1.1.0

Версии бэкенда и фронтенда были не синхронизированы и нигде не отображались
в интерфейсе — непонятно было, обновилось ли приложение после пересборки.
Теперь единая версия 1.1.0 приходит с бэкенда через /api/health и мелким
текстом выводится внизу страницы настроек.
This commit is contained in:
ZProger
2026-09-02 02:21:05 +03:00
parent c422fd1528
commit 946b78d989
8 changed files with 42 additions and 5 deletions

View File

@@ -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):

View File

@@ -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}

View File

@@ -1,7 +1,7 @@
{
"name": "aurum-frontend",
"private": true,
"version": "0.26.0",
"version": "1.1.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -0,0 +1,6 @@
import { api } from "@/api/client";
import type { HealthStatus } from "@/types";
export function fetchHealth() {
return api.get<HealthStatus>("/health");
}

View File

@@ -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 });
}

View File

@@ -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<keyof typeof ru, string> = {
"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",

View File

@@ -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 (
<div className="space-y-5">
<PreferencesCard />
<CurrencyCard />
<AlertThresholdsCard />
<BackupCard />
{health?.version && (
<p className="text-center text-xs text-text-muted">
{t("settings.version", { version: health.version })}
</p>
)}
</div>
);
}

View File

@@ -517,3 +517,8 @@ export interface AppSettings {
idle_cash_threshold_days: number;
}
export interface HealthStatus {
status: string;
version: string;
}