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