feat: Introduce application version display in the frontend footer, showing both frontend and backend versions with integrated build arguments.
All checks were successful
Release and Build Docker Images / release-and-build (push) Successful in 1m29s

This commit is contained in:
almazlar
2026-02-21 10:34:08 +03:00
parent bbd3beb22a
commit 37ae9dbe53
7 changed files with 91 additions and 4 deletions

View File

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

View File

@@ -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"]

View File

@@ -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<Map<String, String>> getVersion() {
Map<String, String> response = new HashMap<>();
response.put("version", appVersion);
return ResponseEntity.ok(response);
}
}

View File

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

View File

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

View File

@@ -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 (
<main>
<TodoList />
</main>
<div className="app-container">
<main className="main-content">
<TodoList />
</main>
<footer className="app-footer">
<p>Frontend <code>{frontendVersion}</code> | Backend <code>{backendVersion}</code></p>
</footer>
</div>
);
}

View File

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