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
All checks were successful
Release and Build Docker Images / release-and-build (push) Successful in 1m29s
This commit is contained in:
@@ -125,6 +125,8 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta-backend.outputs.tags }}
|
tags: ${{ steps.meta-backend.outputs.tags }}
|
||||||
labels: ${{ steps.meta-backend.outputs.labels }}
|
labels: ${{ steps.meta-backend.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
APP_VERSION=${{ steps.generate.outputs.new_tag }}
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Frontend
|
- name: Extract metadata (tags, labels) for Frontend
|
||||||
id: meta-frontend
|
id: meta-frontend
|
||||||
@@ -142,3 +144,5 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta-frontend.outputs.tags }}
|
tags: ${{ steps.meta-frontend.outputs.tags }}
|
||||||
labels: ${{ steps.meta-frontend.outputs.labels }}
|
labels: ${{ steps.meta-frontend.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
VITE_APP_VERSION=${{ steps.generate.outputs.new_tag }}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ RUN mvn clean package -DskipTests
|
|||||||
# Run stage
|
# Run stage
|
||||||
FROM eclipse-temurin:25-jre
|
FROM eclipse-temurin:25-jre
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
ARG APP_VERSION=dev
|
||||||
|
ENV APP_VERSION=${APP_VERSION}
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
COPY --from=build /app/target/*.jar app.jar
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["java", "-jar", "app.jar"]
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ WORKDIR /app
|
|||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
|
ARG VITE_APP_VERSION=dev
|
||||||
|
ENV VITE_APP_VERSION=${VITE_APP_VERSION}
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Run stage
|
# Run stage
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
.app-container {
|
.app-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 90vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
background: var(--panel-bg);
|
background: var(--panel-bg);
|
||||||
@@ -11,6 +14,27 @@
|
|||||||
animation: slideIn 0.5s ease-out;
|
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 {
|
@keyframes slideIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import TodoList from './components/TodoList';
|
import TodoList from './components/TodoList';
|
||||||
|
import { getVersion } from './services/api';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
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 (
|
return (
|
||||||
<main>
|
<div className="app-container">
|
||||||
<TodoList />
|
<main className="main-content">
|
||||||
</main>
|
<TodoList />
|
||||||
|
</main>
|
||||||
|
<footer className="app-footer">
|
||||||
|
<p>Frontend <code>{frontendVersion}</code> | Backend <code>{backendVersion}</code></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,3 +33,11 @@ export const deleteTodo = async (id) => {
|
|||||||
if (!response.ok) throw new Error('Failed to delete todo');
|
if (!response.ok) throw new Error('Failed to delete todo');
|
||||||
return true;
|
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();
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user