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
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:
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { apiJSON } from '$lib/api/client.js';
|
import { apiJSON } from '$lib/api/client.js';
|
||||||
|
import {
|
||||||
type VersionInfo = {
|
formatVersionSidebarLabel,
|
||||||
version?: string;
|
type VersionInfo
|
||||||
api_version?: string;
|
} from '$lib/api/version-info.js';
|
||||||
};
|
|
||||||
|
|
||||||
let label = $state<string | null>(null);
|
let label = $state<string | null>(null);
|
||||||
|
|
||||||
@@ -13,11 +12,7 @@
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const info = await apiJSON<VersionInfo>('/v1/version');
|
const info = await apiJSON<VersionInfo>('/v1/version');
|
||||||
const ver =
|
label = formatVersionSidebarLabel(info);
|
||||||
(typeof info.version === 'string' && info.version) ||
|
|
||||||
(typeof info.api_version === 'string' && info.api_version) ||
|
|
||||||
'';
|
|
||||||
label = ver ? `v${ver}` : null;
|
|
||||||
} catch {
|
} catch {
|
||||||
label = null;
|
label = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { apiJSON, apiFetch } from '$lib/api/client.js';
|
import { apiJSON, apiFetch } from '$lib/api/client.js';
|
||||||
import type { BirdStatus, JobsResponse } from '$lib/api/types.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 { Badge } from '$lib/components/ui/badge/index.js';
|
||||||
import { Button } from '$lib/components/ui/button/index.js';
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -21,12 +26,6 @@
|
|||||||
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
||||||
|
|
||||||
type ReadyStatus = { status: string; checks?: Record<string, unknown> };
|
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 };
|
type JobsKpi = { running: number; failed: number; total: number };
|
||||||
|
|
||||||
let health = $state<{ ok: boolean; status?: string; error?: string } | null>(null);
|
let health = $state<{ ok: boolean; status?: string; error?: string } | null>(null);
|
||||||
@@ -98,16 +97,16 @@
|
|||||||
return `${bird.bgp_established}/${bird.bgp_sessions_total}`;
|
return `${bird.bgp_established}/${bird.bgp_sessions_total}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const versionText = $derived.by(() => {
|
const versionText = $derived(formatVersionHeadline(version));
|
||||||
if (!version) return '—';
|
|
||||||
const ver =
|
const versionFooter = $derived.by(() => {
|
||||||
(typeof version.version === 'string' && version.version) ||
|
if (!version) return '';
|
||||||
(typeof version.api_version === 'string' && version.api_version) ||
|
const parts: string[] = [];
|
||||||
'';
|
const ver = resolveVersionString(version);
|
||||||
const sha =
|
if (ver) parts.push(`version: ${ver}`);
|
||||||
typeof version.git_sha === 'string' && version.git_sha ? version.git_sha : 'unknown';
|
if (version.git_sha) parts.push(`git_sha: ${version.git_sha}`);
|
||||||
if (ver) return `${ver} (${sha.slice(0, 12)})`;
|
if (version.build_time) parts.push(`build: ${version.build_time}`);
|
||||||
return sha.slice(0, 12);
|
return parts.join(' · ');
|
||||||
});
|
});
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
@@ -263,10 +262,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if versionError}
|
{#if versionError}
|
||||||
<p class="mt-2 text-xs text-destructive">{versionError}</p>
|
<p class="mt-2 text-xs text-destructive">{versionError}</p>
|
||||||
{:else if version}
|
{:else if versionFooter}
|
||||||
<p class="mt-2 truncate text-xs text-muted-foreground">
|
<p class="mt-2 truncate text-xs text-muted-foreground">{versionFooter}</p>
|
||||||
api_version: {String(version.api_version ?? 'unknown')}
|
|
||||||
</p>
|
|
||||||
{/if}
|
{/if}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user