From 8753e84a03245694ffff46f2fecb3b170adab858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Fri, 17 Jul 2026 21:48:25 +0300 Subject: [PATCH] feat: Spec product version, build bake, Admin UI label. Resolve VERSION from EventHubSpec; VITE_APP_BUILD from run_number; show Admin UI / API as version.build (sha). Co-authored-by: Cursor --- .gitea/workflows/ci.yml | 8 +++++- e2e/mocks/adminApi.ts | 1 + scripts/resolve-product-version.sh | 40 +++++++++++++++++++++++++++++ src/api/healthApi.ts | 1 + src/layouts/ControlCenterLayout.tsx | 15 ++++++++--- src/lib/errorReporter.ts | 1 + src/locales/en.json | 2 +- src/locales/ru.json | 2 +- vite.config.ts | 2 ++ 9 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 scripts/resolve-product-version.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 47e00f9..f62c3ad 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -50,12 +50,18 @@ jobs: - name: Bake build identity id: build_meta + env: + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + GITEA_TOKEN: ${{ secrets.REGISTRY_PASSWORD }} run: | set -euo pipefail - APP_VERSION="$(tr -d '[:space:]' < VERSION 2>/dev/null || echo 0.0)" + APP_VERSION="$(bash scripts/resolve-product-version.sh)" + APP_BUILD="${GITHUB_RUN_NUMBER:-0}" GIT_SHA="${GITHUB_SHA:0:12}" BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + echo "Bake: version=${APP_VERSION} build=${APP_BUILD} git_sha=${GIT_SHA}" echo "VITE_APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV" + echo "VITE_APP_BUILD=${APP_BUILD}" >> "$GITHUB_ENV" echo "VITE_GIT_SHA=${GIT_SHA}" >> "$GITHUB_ENV" echo "VITE_BUILT_AT=${BUILT_AT}" >> "$GITHUB_ENV" diff --git a/e2e/mocks/adminApi.ts b/e2e/mocks/adminApi.ts index e0ccd14..9490c94 100644 --- a/e2e/mocks/adminApi.ts +++ b/e2e/mocks/adminApi.ts @@ -84,6 +84,7 @@ export async function installAdminApiMocks(page: Page, options: MockOptions = {} status: 'ok', service: 'eventhub', version: '0.1', + build: 1, git_sha: 'e2etestsha000', built_at: '2026-07-14T00:00:00Z', }); diff --git a/scripts/resolve-product-version.sh b/scripts/resolve-product-version.sh new file mode 100644 index 0000000..344deab --- /dev/null +++ b/scripts/resolve-product-version.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Product version (MAJOR.MINOR) из EventHubSpec/VERSION. +# Fallback: локальный VERSION (офлайн / без токена). +set -euo pipefail + +BASE="${GITEA_BASE_URL:-https://git.sabilin.com}" +TOKEN="${GITEA_TOKEN:-${REGISTRY_PASSWORD:-}}" +REF="${PRODUCT_VERSION_REF:-master}" +FALLBACK_FILE="${PRODUCT_VERSION_FILE:-VERSION}" + +fetch_spec() { + local url="${BASE}/api/v1/repos/EventHub/EventHubSpec/raw/VERSION?ref=${REF}" + if [[ -n "${TOKEN}" ]]; then + curl -fsSL -H "Authorization: token ${TOKEN}" "${url}" + else + curl -fsSL "${url}" + fi +} + +ver="" +if raw="$(fetch_spec 2>/dev/null)"; then + ver="$(printf '%s' "${raw}" | grep -Eo '[0-9]+\.[0-9]+' | head -1 || true)" +fi + +if [[ -z "${ver}" && -f "${FALLBACK_FILE}" ]]; then + echo "WARN: EventHubSpec/VERSION недоступен — fallback ${FALLBACK_FILE}" >&2 + ver="$(grep -Eo '[0-9]+\.[0-9]+' "${FALLBACK_FILE}" | head -1 || true)" +fi + +if [[ -z "${ver}" ]]; then + echo "ERROR: не удалось получить product version (Spec + local VERSION)" >&2 + exit 1 +fi + +if ! [[ "${ver}" =~ ^[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: некорректный VERSION '${ver}' (ожидается MAJOR.MINOR)" >&2 + exit 1 +fi + +printf '%s\n' "${ver}" diff --git a/src/api/healthApi.ts b/src/api/healthApi.ts index 0236861..663a20a 100644 --- a/src/api/healthApi.ts +++ b/src/api/healthApi.ts @@ -4,6 +4,7 @@ export type AdminHealthInfo = { status: string; service?: string; version?: string; + build?: number; git_sha?: string; built_at?: string; }; diff --git a/src/layouts/ControlCenterLayout.tsx b/src/layouts/ControlCenterLayout.tsx index b69efd9..fb0ce5c 100644 --- a/src/layouts/ControlCenterLayout.tsx +++ b/src/layouts/ControlCenterLayout.tsx @@ -57,9 +57,13 @@ import { useCommandPaletteHotkey } from '@/hooks/useCommandPaletteHotkey'; import { HeaderNodeMetrics } from '@/components/HeaderNodeMetrics'; import { healthApi } from '@/api/healthApi'; -function formatBuildLabel(version: string, sha: string): string { +function formatBuildLabel(version: string, build: number | string | undefined, sha: string): string { const shortSha = sha && sha !== 'unknown' && sha !== 'dev' ? sha.slice(0, 12) : sha; - return `${version} (${shortSha})`; + const buildPart = + build === undefined || build === null || build === '' || Number(build) === 0 + ? version + : `${version}.${build}`; + return `${buildPart} (${shortSha})`; } const navIconByKey: Record> = { @@ -125,6 +129,7 @@ const ControlCenterLayout: React.FC = () => { const adminVersionLabel = formatBuildLabel( import.meta.env.VITE_APP_VERSION || '0.0', + import.meta.env.VITE_APP_BUILD || '0', import.meta.env.VITE_GIT_SHA || 'dev' ); @@ -135,7 +140,11 @@ const ControlCenterLayout: React.FC = () => { .then((info) => { if (cancelled) return; setApiVersionLabel( - formatBuildLabel(info.version || '0.0', info.git_sha || 'unknown') + formatBuildLabel( + info.version || '0.0', + info.build, + info.git_sha || 'unknown' + ) ); }) .catch(() => { diff --git a/src/lib/errorReporter.ts b/src/lib/errorReporter.ts index 6c8c943..da95c57 100644 --- a/src/lib/errorReporter.ts +++ b/src/lib/errorReporter.ts @@ -32,6 +32,7 @@ function buildContext(extra?: Record): Record route: typeof window !== 'undefined' ? window.location.pathname : undefined, user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined, build: import.meta.env.VITE_APP_VERSION || '0.0', + build_number: import.meta.env.VITE_APP_BUILD || '0', git_sha: import.meta.env.VITE_GIT_SHA || 'dev', ...extra, }; diff --git a/src/locales/en.json b/src/locales/en.json index 35078be..a97cdad 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -195,7 +195,7 @@ "nodeMemory": "Memory: {{used}} / {{total}} MB ({{percent}}%)", "nodeWas": "was {{value}}%", "nodeAria": "{{node}}: CPU {{cpu}}%, memory {{memory}}%", - "adminVersion": "Admin {{version}}", + "adminVersion": "Admin UI {{version}}", "apiVersion": "API {{version}}", "apiVersionUnavailable": "API — unavailable" }, diff --git a/src/locales/ru.json b/src/locales/ru.json index d2b8cd2..ff8355e 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -195,7 +195,7 @@ "nodeMemory": "Память: {{used}} / {{total}} МБ ({{percent}}%)", "nodeWas": "было {{value}}%", "nodeAria": "{{node}}: CPU {{cpu}}%, память {{memory}}%", - "adminVersion": "Admin {{version}}", + "adminVersion": "Admin UI {{version}}", "apiVersion": "API {{version}}", "apiVersionUnavailable": "API — нет данных" }, diff --git a/vite.config.ts b/vite.config.ts index 8790350..f3405fa 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,6 +7,7 @@ const apiBaseUrl = process.env.VITE_API_BASE_URL ?? 'https://admin-api.dev.event const wsUrl = process.env.VITE_WS_URL ?? 'wss://admin-ws.dev.eventhub.local'; const appVersion = process.env.VITE_APP_VERSION ?? '0.0'; +const appBuild = process.env.VITE_APP_BUILD ?? '0'; const gitSha = process.env.VITE_GIT_SHA ?? 'dev'; const builtAt = process.env.VITE_BUILT_AT ?? ''; @@ -14,6 +15,7 @@ export default defineConfig({ plugins: [react(), tailwindcss()], define: { 'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion), + 'import.meta.env.VITE_APP_BUILD': JSON.stringify(appBuild), 'import.meta.env.VITE_GIT_SHA': JSON.stringify(gitSha), 'import.meta.env.VITE_BUILT_AT': JSON.stringify(builtAt), },