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 <cursoragent@cursor.com>
This commit is contained in:
2026-07-17 21:48:25 +03:00
parent eb37476bdb
commit 8753e84a03
9 changed files with 66 additions and 6 deletions
+7 -1
View File
@@ -50,12 +50,18 @@ jobs:
- name: Bake build identity - name: Bake build identity
id: build_meta id: build_meta
env:
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
GITEA_TOKEN: ${{ secrets.REGISTRY_PASSWORD }}
run: | run: |
set -euo pipefail 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}" GIT_SHA="${GITHUB_SHA:0:12}"
BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" 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_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
echo "VITE_APP_BUILD=${APP_BUILD}" >> "$GITHUB_ENV"
echo "VITE_GIT_SHA=${GIT_SHA}" >> "$GITHUB_ENV" echo "VITE_GIT_SHA=${GIT_SHA}" >> "$GITHUB_ENV"
echo "VITE_BUILT_AT=${BUILT_AT}" >> "$GITHUB_ENV" echo "VITE_BUILT_AT=${BUILT_AT}" >> "$GITHUB_ENV"
+1
View File
@@ -84,6 +84,7 @@ export async function installAdminApiMocks(page: Page, options: MockOptions = {}
status: 'ok', status: 'ok',
service: 'eventhub', service: 'eventhub',
version: '0.1', version: '0.1',
build: 1,
git_sha: 'e2etestsha000', git_sha: 'e2etestsha000',
built_at: '2026-07-14T00:00:00Z', built_at: '2026-07-14T00:00:00Z',
}); });
+40
View File
@@ -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}"
+1
View File
@@ -4,6 +4,7 @@ export type AdminHealthInfo = {
status: string; status: string;
service?: string; service?: string;
version?: string; version?: string;
build?: number;
git_sha?: string; git_sha?: string;
built_at?: string; built_at?: string;
}; };
+12 -3
View File
@@ -57,9 +57,13 @@ import { useCommandPaletteHotkey } from '@/hooks/useCommandPaletteHotkey';
import { HeaderNodeMetrics } from '@/components/HeaderNodeMetrics'; import { HeaderNodeMetrics } from '@/components/HeaderNodeMetrics';
import { healthApi } from '@/api/healthApi'; 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; 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<string, React.ComponentType<{ className?: string }>> = { const navIconByKey: Record<string, React.ComponentType<{ className?: string }>> = {
@@ -125,6 +129,7 @@ const ControlCenterLayout: React.FC = () => {
const adminVersionLabel = formatBuildLabel( const adminVersionLabel = formatBuildLabel(
import.meta.env.VITE_APP_VERSION || '0.0', import.meta.env.VITE_APP_VERSION || '0.0',
import.meta.env.VITE_APP_BUILD || '0',
import.meta.env.VITE_GIT_SHA || 'dev' import.meta.env.VITE_GIT_SHA || 'dev'
); );
@@ -135,7 +140,11 @@ const ControlCenterLayout: React.FC = () => {
.then((info) => { .then((info) => {
if (cancelled) return; if (cancelled) return;
setApiVersionLabel( setApiVersionLabel(
formatBuildLabel(info.version || '0.0', info.git_sha || 'unknown') formatBuildLabel(
info.version || '0.0',
info.build,
info.git_sha || 'unknown'
)
); );
}) })
.catch(() => { .catch(() => {
+1
View File
@@ -32,6 +32,7 @@ function buildContext(extra?: Record<string, unknown>): Record<string, unknown>
route: typeof window !== 'undefined' ? window.location.pathname : undefined, route: typeof window !== 'undefined' ? window.location.pathname : undefined,
user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined, user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
build: import.meta.env.VITE_APP_VERSION || '0.0', 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', git_sha: import.meta.env.VITE_GIT_SHA || 'dev',
...extra, ...extra,
}; };
+1 -1
View File
@@ -195,7 +195,7 @@
"nodeMemory": "Memory: {{used}} / {{total}} MB ({{percent}}%)", "nodeMemory": "Memory: {{used}} / {{total}} MB ({{percent}}%)",
"nodeWas": "was {{value}}%", "nodeWas": "was {{value}}%",
"nodeAria": "{{node}}: CPU {{cpu}}%, memory {{memory}}%", "nodeAria": "{{node}}: CPU {{cpu}}%, memory {{memory}}%",
"adminVersion": "Admin {{version}}", "adminVersion": "Admin UI {{version}}",
"apiVersion": "API {{version}}", "apiVersion": "API {{version}}",
"apiVersionUnavailable": "API — unavailable" "apiVersionUnavailable": "API — unavailable"
}, },
+1 -1
View File
@@ -195,7 +195,7 @@
"nodeMemory": "Память: {{used}} / {{total}} МБ ({{percent}}%)", "nodeMemory": "Память: {{used}} / {{total}} МБ ({{percent}}%)",
"nodeWas": "было {{value}}%", "nodeWas": "было {{value}}%",
"nodeAria": "{{node}}: CPU {{cpu}}%, память {{memory}}%", "nodeAria": "{{node}}: CPU {{cpu}}%, память {{memory}}%",
"adminVersion": "Admin {{version}}", "adminVersion": "Admin UI {{version}}",
"apiVersion": "API {{version}}", "apiVersion": "API {{version}}",
"apiVersionUnavailable": "API — нет данных" "apiVersionUnavailable": "API — нет данных"
}, },
+2
View File
@@ -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 wsUrl = process.env.VITE_WS_URL ?? 'wss://admin-ws.dev.eventhub.local';
const appVersion = process.env.VITE_APP_VERSION ?? '0.0'; 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 gitSha = process.env.VITE_GIT_SHA ?? 'dev';
const builtAt = process.env.VITE_BUILT_AT ?? ''; const builtAt = process.env.VITE_BUILT_AT ?? '';
@@ -14,6 +15,7 @@ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
define: { define: {
'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion), '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_GIT_SHA': JSON.stringify(gitSha),
'import.meta.env.VITE_BUILT_AT': JSON.stringify(builtAt), 'import.meta.env.VITE_BUILT_AT': JSON.stringify(builtAt),
}, },