fix(web): show semver on monitoring from legacy /v1/version
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Failing after 35s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped

На проде API отдаёт только api_version и git_sha=unknown без поля version;
карточка показывала SHA вместо semver. Общий хелпер version-info.ts:
version → api_version, placeholder SHA не в заголовке.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-05-20 11:13:15 +07:00
co-authored by Cursor
parent 700d415422
commit 13b4c20148
3 changed files with 71 additions and 30 deletions
+49
View File
@@ -0,0 +1,49 @@
/** Ответ GET /v1/version (см. docs/openapi.yaml VersionInfo). */
export type VersionInfo = {
version?: string;
api_version?: string;
git_sha?: string;
build_time?: string;
};
const PLACEHOLDER_SHA = new Set(['', 'unknown']);
function nonEmptyString(value: unknown): string {
if (typeof value === 'string') {
const s = value.trim();
return s;
}
if (value == null) return '';
return String(value).trim();
}
/** Semver или legacy api_version; пустая строка, если полей нет. */
export function resolveVersionString(info: VersionInfo | null | undefined): string {
if (!info) return '';
return nonEmptyString(info.version) || nonEmptyString(info.api_version);
}
/** Короткий git SHA; пусто для placeholder «unknown». */
export function resolveGitSha(info: VersionInfo | null | undefined): string {
if (!info) return '';
const sha = nonEmptyString(info.git_sha);
if (!sha || PLACEHOLDER_SHA.has(sha.toLowerCase())) return '';
return sha;
}
/** Заголовок карточки «Версия» на /monitoring. */
export function formatVersionHeadline(info: VersionInfo | null | undefined): string {
if (!info) return '—';
const ver = resolveVersionString(info);
const sha = resolveGitSha(info);
if (ver && sha) return `${ver} (${sha.slice(0, 12)})`;
if (ver) return ver;
if (sha) return sha.slice(0, 12);
return '—';
}
/** Подпись в сайдбаре (v1.2.3). */
export function formatVersionSidebarLabel(info: VersionInfo | null | undefined): string | null {
const ver = resolveVersionString(info);
return ver ? `v${ver}` : null;
}
+5 -10
View File
@@ -1,11 +1,10 @@
<script lang="ts">
import { onMount } from 'svelte';
import { apiJSON } from '$lib/api/client.js';
type VersionInfo = {
version?: string;
api_version?: string;
};
import {
formatVersionSidebarLabel,
type VersionInfo
} from '$lib/api/version-info.js';
let label = $state<string | null>(null);
@@ -13,11 +12,7 @@
void (async () => {
try {
const info = await apiJSON<VersionInfo>('/v1/version');
const ver =
(typeof info.version === 'string' && info.version) ||
(typeof info.api_version === 'string' && info.api_version) ||
'';
label = ver ? `v${ver}` : null;
label = formatVersionSidebarLabel(info);
} catch {
label = null;
}
+17 -20
View File
@@ -2,6 +2,11 @@
import { onMount } from 'svelte';
import { apiJSON, apiFetch } from '$lib/api/client.js';
import type { BirdStatus, JobsResponse } from '$lib/api/types.js';
import {
formatVersionHeadline,
resolveVersionString,
type VersionInfo
} from '$lib/api/version-info.js';
import { Badge } from '$lib/components/ui/badge/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import {
@@ -21,12 +26,6 @@
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
type ReadyStatus = { status: string; checks?: Record<string, unknown> };
type VersionInfo = {
version?: string;
git_sha?: string;
build_time?: string;
[k: string]: unknown;
};
type JobsKpi = { running: number; failed: number; total: number };
let health = $state<{ ok: boolean; status?: string; error?: string } | null>(null);
@@ -98,16 +97,16 @@
return `${bird.bgp_established}/${bird.bgp_sessions_total}`;
});
const versionText = $derived.by(() => {
if (!version) return '—';
const ver =
(typeof version.version === 'string' && version.version) ||
(typeof version.api_version === 'string' && version.api_version) ||
'';
const sha =
typeof version.git_sha === 'string' && version.git_sha ? version.git_sha : 'unknown';
if (ver) return `${ver} (${sha.slice(0, 12)})`;
return sha.slice(0, 12);
const versionText = $derived(formatVersionHeadline(version));
const versionFooter = $derived.by(() => {
if (!version) return '';
const parts: string[] = [];
const ver = resolveVersionString(version);
if (ver) parts.push(`version: ${ver}`);
if (version.git_sha) parts.push(`git_sha: ${version.git_sha}`);
if (version.build_time) parts.push(`build: ${version.build_time}`);
return parts.join(' · ');
});
async function load() {
@@ -263,10 +262,8 @@
</div>
{#if versionError}
<p class="mt-2 text-xs text-destructive">{versionError}</p>
{:else if version}
<p class="mt-2 truncate text-xs text-muted-foreground">
api_version: {String(version.api_version ?? 'unknown')}
</p>
{:else if versionFooter}
<p class="mt-2 truncate text-xs text-muted-foreground">{versionFooter}</p>
{/if}
</CardContent>
</Card>