From 1e7587e11b9c1677efe7c28134aeecb79ab8dee9 Mon Sep 17 00:00:00 2001 From: Danil-Bodry Date: Sat, 9 May 2026 14:05:25 +0300 Subject: [PATCH] feat: implement version display with commit hash and date --- build.gradle.kts | 38 ++++++++++++- frontend/src/App.vue | 7 ++- frontend/src/components/Layout/AppLayout.vue | 12 ++++ frontend/src/locales/en.json | 4 +- frontend/src/locales/ru.json | 4 +- frontend/src/stores/version.ts | 55 +++++++++++++++++++ frontend/src/views/auth/Login.vue | 8 +++ frontend/src/views/auth/Register.vue | 8 +++ .../xserver/iikocon/BuildVersionProvider.java | 51 +++++++++++++++++ .../java/su/xserver/iikocon/MainVerticle.java | 14 ++++- src/main/resources/version.properties | 3 + 11 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 frontend/src/stores/version.ts create mode 100644 src/main/java/su/xserver/iikocon/BuildVersionProvider.java create mode 100644 src/main/resources/version.properties diff --git a/build.gradle.kts b/build.gradle.kts index e79de0c..38a47c3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,8 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.tasks.testing.logging.TestLogEvent.* import java.nio.file.Files import java.nio.file.Paths +import java.time.Instant +import java.time.format.DateTimeFormatter plugins { java @@ -20,7 +22,7 @@ node { } group = "com.example" -version = "1.0.0-SNAPSHOT" +version = "1.0.0-beta" repositories { mavenCentral() @@ -189,3 +191,37 @@ tasks.register("countCodeLines") { } } +tasks.register("generateVersionFile") { + doLast { + // Версия из gradle.properties (по умолчанию 'unspecified', если не задана) + val version = project.version.takeIf { it.toString() != "unspecified" }?.toString() ?: "0.0.0" + + // Получение короткого хэша коммита (с обработкой ошибки, если git не доступен) + val commitHash = try { + providers.exec { + commandLine("git", "rev-parse", "--short", "HEAD") + }.standardOutput.asText.get().trim() + } catch (e: Exception) { + logger.warn("Не удалось получить хэш коммита: ${e.message}") + "unknown" + } + + val buildTime = DateTimeFormatter.ISO_INSTANT.format(Instant.now()) + + val propertiesContent = """ + version=$version + commit.hash=$commitHash + build.time=$buildTime + """.trimIndent() + + val resourceDir = file("src/main/resources") + resourceDir.mkdirs() + file("$resourceDir/version.properties").writeText(propertiesContent) + + logger.lifecycle("✅ Файл version.properties создан: версия=$version, коммит=$commitHash") + } +} + +tasks.processResources { + dependsOn("generateVersionFile") +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue index a6f7814..519631e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -21,10 +21,12 @@ diff --git a/frontend/src/components/Layout/AppLayout.vue b/frontend/src/components/Layout/AppLayout.vue index 421cb14..da78c24 100644 --- a/frontend/src/components/Layout/AppLayout.vue +++ b/frontend/src/components/Layout/AppLayout.vue @@ -167,6 +167,16 @@ {{ userInitials }} + + +
+ {{ versionStore.getFormattedVersion(t) }} +
+
+
+ {{ versionStore.version?.commitHash?.slice(0, 6) }} +
+
@@ -256,9 +266,11 @@ import { useSettingsStore } from '@/stores/settings' import { useUserStore } from '@/stores/user' import { useI18n } from 'vue-i18n' import { useNotification } from '@/composables/useNotification' +import { useVersionStore } from '@/stores/version' const { notification, showNotification } = useNotification() const settings = useSettingsStore() +const versionStore = useVersionStore() const userStore = useUserStore() const route = useRoute() const router = useRouter() diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 6ca9663..f891727 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -46,7 +46,9 @@ "deleteConfirmation": "Are you sure you want to delete this item? This action cannot be undone.", "operationSuccess": "Operation completed successfully", "operationFailed": "Operation failed", - "networkError": "Network error" + "networkError": "Network error", + "version": "Version", + "versionFrom": "from" }, "dashboard": { "totalUsers": "Total Users", diff --git a/frontend/src/locales/ru.json b/frontend/src/locales/ru.json index d53c2f0..d43fb76 100644 --- a/frontend/src/locales/ru.json +++ b/frontend/src/locales/ru.json @@ -46,7 +46,9 @@ "deleteConfirmation": "Вы уверены, что хотите удалить этот элемент? Это действие необратимо.", "operationSuccess": "Операция выполнена успешно", "operationFailed": "Операция не удалась", - "networkError": "Ошибка сети" + "networkError": "Ошибка сети", + "version": "Версия", + "versionFrom": "от" }, "dashboard": { "totalUsers": "Всего пользователей", diff --git a/frontend/src/stores/version.ts b/frontend/src/stores/version.ts new file mode 100644 index 0000000..89f4b26 --- /dev/null +++ b/frontend/src/stores/version.ts @@ -0,0 +1,55 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export interface BuildVersion { + version: string + commitHash: string + buildTime: string // ISO строка, например "2025-04-03T12:34:56Z" +} + +export const useVersionStore = defineStore('version', () => { + const version = ref(null) + const loading = ref(false) + const error = ref(null) + + async function fetchVersion() { + if (version.value) return + loading.value = true + try { + const res = await fetch('/api/build-info') + if (!res.ok) throw new Error('Failed to fetch version') + const data = await res.json() + version.value = { + version: data.version || '0.0.0', + commitHash: data.commitHash || 'unknown', + buildTime: data.buildTime || '' + } + } catch (err: any) { + console.error(err) + error.value = err.message + version.value = { version: 'dev', commitHash: 'unknown', buildTime: '' } + } finally { + loading.value = false + } + } + + // Отформатированная дата сборки (только дата, без времени) + const buildDateFormatted = computed(() => { + if (!version.value?.buildTime) return '' + const date = new Date(version.value.buildTime) + if (isNaN(date.getTime())) return '' + // Формат YYYY-MM-DD (универсальный, без локализации) + return date.toISOString().split('T')[0] + }) + + // Полная строка версии: "Версия: 1.2.3 (build abc1234 от 2025-04-03)" + // Принимает функцию перевода для слова "от"/"from" + const getFormattedVersion = (t: (key: string) => string) => { + if (!version.value) return t('common.version') + ': ...' + const { version: ver, commitHash } = version.value + const datePart = buildDateFormatted.value ? ` ${t('common.versionFrom')} ${buildDateFormatted.value}` : '' + return `${t('common.version')}: ${ver} (build ${commitHash}${datePart})` + } + + return { version, loading, error, fetchVersion, buildDateFormatted, getFormattedVersion } +}) diff --git a/frontend/src/views/auth/Login.vue b/frontend/src/views/auth/Login.vue index 2f6dc24..1bdaa80 100644 --- a/frontend/src/views/auth/Login.vue +++ b/frontend/src/views/auth/Login.vue @@ -96,6 +96,12 @@ + + +
+ {{ versionStore.getFormattedVersion(t) }} +
+ @@ -105,12 +111,14 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import { useSettingsStore } from '@/stores/settings' import { useUserStore } from '@/stores/user' +import { useVersionStore } from '@/stores/version' import { useI18n } from 'vue-i18n' const settings = useSettingsStore() const userStore = useUserStore() const router = useRouter() const { t, locale } = useI18n() +const versionStore = useVersionStore() const form = ref({ login: '', password: '' }) const loading = ref(false) const error = ref('') diff --git a/frontend/src/views/auth/Register.vue b/frontend/src/views/auth/Register.vue index de4b8f5..9520952 100644 --- a/frontend/src/views/auth/Register.vue +++ b/frontend/src/views/auth/Register.vue @@ -35,6 +35,12 @@ {{ t('register.alreadyHaveAccount') }} {{ t('login.signin') }}

+ + +
+ {{ versionStore.getFormattedVersion(t) }} +
+ @@ -42,7 +48,9 @@