From 37ae9dbe5314872d549f62a9f2121dd20702c96e Mon Sep 17 00:00:00 2001 From: almazlar Date: Sat, 21 Feb 2026 10:34:08 +0300 Subject: [PATCH] feat: Introduce application version display in the frontend footer, showing both frontend and backend versions with integrated build arguments. --- .gitea/workflows/docker-build.yml | 4 +++ backend/Dockerfile | 2 ++ .../backend/controller/VersionController.java | 25 ++++++++++++++++ frontend/Dockerfile | 2 ++ frontend/src/App.css | 24 +++++++++++++++ frontend/src/App.jsx | 30 ++++++++++++++++--- frontend/src/services/api.js | 8 +++++ 7 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/todo/backend/controller/VersionController.java diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index c453f43..bfdcbba 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -125,6 +125,8 @@ jobs: push: true tags: ${{ steps.meta-backend.outputs.tags }} labels: ${{ steps.meta-backend.outputs.labels }} + build-args: | + APP_VERSION=${{ steps.generate.outputs.new_tag }} - name: Extract metadata (tags, labels) for Frontend id: meta-frontend @@ -142,3 +144,5 @@ jobs: push: true tags: ${{ steps.meta-frontend.outputs.tags }} labels: ${{ steps.meta-frontend.outputs.labels }} + build-args: | + VITE_APP_VERSION=${{ steps.generate.outputs.new_tag }} diff --git a/backend/Dockerfile b/backend/Dockerfile index 6db8f65..7bb1860 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -8,6 +8,8 @@ RUN mvn clean package -DskipTests # Run stage FROM eclipse-temurin:25-jre WORKDIR /app +ARG APP_VERSION=dev +ENV APP_VERSION=${APP_VERSION} COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/backend/src/main/java/com/todo/backend/controller/VersionController.java b/backend/src/main/java/com/todo/backend/controller/VersionController.java new file mode 100644 index 0000000..78f1ec3 --- /dev/null +++ b/backend/src/main/java/com/todo/backend/controller/VersionController.java @@ -0,0 +1,25 @@ +package com.todo.backend.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/version") +public class VersionController { + + @Value("${APP_VERSION:dev}") + private String appVersion; + + @GetMapping + public ResponseEntity> getVersion() { + Map response = new HashMap<>(); + response.put("version", appVersion); + return ResponseEntity.ok(response); + } +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 60ea45a..56c96be 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -4,6 +4,8 @@ WORKDIR /app COPY package*.json ./ RUN npm install COPY . . +ARG VITE_APP_VERSION=dev +ENV VITE_APP_VERSION=${VITE_APP_VERSION} RUN npm run build # Run stage diff --git a/frontend/src/App.css b/frontend/src/App.css index b1b6141..9b5b930 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,4 +1,7 @@ .app-container { + display: flex; + flex-direction: column; + min-height: 90vh; width: 100%; max-width: 600px; background: var(--panel-bg); @@ -11,6 +14,27 @@ animation: slideIn 0.5s ease-out; } +.main-content { + flex-grow: 1; +} + +.app-footer { + margin-top: 2rem; + padding-top: 1rem; + text-align: center; + color: var(--text-muted); + font-size: 0.85em; + border-top: 1px solid var(--border-color); +} + +.app-footer code { + background: rgba(139, 92, 246, 0.1); + padding: 0.2rem 0.4rem; + border-radius: 6px; + margin: 0 0.2rem; + color: var(--accent-primary); +} + @keyframes slideIn { from { opacity: 0; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 93b262e..d8d1e39 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,12 +1,34 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import TodoList from './components/TodoList'; +import { getVersion } from './services/api'; import './App.css'; function App() { + const [backendVersion, setBackendVersion] = useState('...'); + const frontendVersion = import.meta.env.VITE_APP_VERSION || 'dev'; + + useEffect(() => { + const fetchVersion = async () => { + try { + const data = await getVersion(); + setBackendVersion(data.version || 'dev'); + } catch (error) { + console.error('Error fetching backend version:', error); + setBackendVersion('unknown'); + } + }; + fetchVersion(); + }, []); + return ( -
- -
+
+
+ +
+ +
); } diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index e759163..dc397e7 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -33,3 +33,11 @@ export const deleteTodo = async (id) => { if (!response.ok) throw new Error('Failed to delete todo'); return true; }; + +export const getVersion = async () => { + // Use the origin to ensure it accesses the /api/version correctly regardless of environment + const VERSION_URL = BASE_URL.replace('/todos', '/version'); + const response = await fetch(VERSION_URL); + if (!response.ok) throw new Error('Failed to fetch version'); + return response.json(); +};