feat(admin): VERSION+sha в UI, bake CI и auto stage по sha-*
CI / test (push) Successful in 6m45s
CI / push-image (push) Failing after 4m38s
CI / ci-done (push) Has been cancelled

This commit is contained in:
2026-07-14 21:25:35 +03:00
parent b7d572ea8e
commit 2a32465050
9 changed files with 128 additions and 27 deletions
+16
View File
@@ -0,0 +1,16 @@
import apiClient from './client';
export type AdminHealthInfo = {
status: string;
service?: string;
version?: string;
git_sha?: string;
built_at?: string;
};
export const healthApi = {
getAdminHealth: async (): Promise<AdminHealthInfo> => {
const { data } = await apiClient.get<AdminHealthInfo>('/v1/admin/health');
return data;
},
};
+39
View File
@@ -52,6 +52,12 @@ import { ErrorBoundary } from '@/components/ErrorBoundary';
import { CommandPalette } from '@/components/CommandPalette';
import { useCommandPaletteHotkey } from '@/hooks/useCommandPaletteHotkey';
import { HeaderNodeMetrics } from '@/components/HeaderNodeMetrics';
import { healthApi } from '@/api/healthApi';
function formatBuildLabel(version: string, sha: string): string {
const shortSha = sha && sha !== 'unknown' && sha !== 'dev' ? sha.slice(0, 12) : sha;
return `${version} (${shortSha})`;
}
const navIconByKey: Record<string, React.ComponentType<{ className?: string }>> = {
'/dashboard': LayoutDashboard,
@@ -102,10 +108,34 @@ const ControlCenterLayout: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
const [paletteOpen, setPaletteOpen] = useState(false);
const [density, setDensity] = useState<TableDensity>(() => getTableDensity());
const [apiVersionLabel, setApiVersionLabel] = useState<string | null>(null);
const openPalette = useCallback(() => setPaletteOpen(true), []);
useCommandPaletteHotkey(openPalette);
const adminVersionLabel = formatBuildLabel(
import.meta.env.VITE_APP_VERSION || '0.0',
import.meta.env.VITE_GIT_SHA || 'dev'
);
useEffect(() => {
let cancelled = false;
healthApi
.getAdminHealth()
.then((info) => {
if (cancelled) return;
setApiVersionLabel(
formatBuildLabel(info.version || '0.0', info.git_sha || 'unknown')
);
})
.catch(() => {
if (!cancelled) setApiVersionLabel(null);
});
return () => {
cancelled = true;
};
}, []);
const selectedKey = getMenuSelectedKey(location.pathname);
const groups = useMemo(() => filterNavGroupsByRole(NAV_GROUPS, user?.role), [user?.role]);
@@ -317,6 +347,15 @@ const ControlCenterLayout: React.FC = () => {
{t('common.langEn')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel className="space-y-0.5 text-xs font-normal text-muted-foreground">
<div>{t('layout.adminVersion', { version: adminVersionLabel })}</div>
<div>
{apiVersionLabel
? t('layout.apiVersion', { version: apiVersionLabel })
: t('layout.apiVersionUnavailable')}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout} className="text-destructive focus:text-destructive">
<LogOut className="mr-2 h-4 w-4" />
{t('layout.logout')}
+4 -1
View File
@@ -192,7 +192,10 @@
"nodeCpu": "CPU: {{value}}%",
"nodeMemory": "Memory: {{used}} / {{total}} MB ({{percent}}%)",
"nodeWas": "was {{value}}%",
"nodeAria": "{{node}}: CPU {{cpu}}%, memory {{memory}}%"
"nodeAria": "{{node}}: CPU {{cpu}}%, memory {{memory}}%",
"adminVersion": "Admin {{version}}",
"apiVersion": "API {{version}}",
"apiVersionUnavailable": "API — unavailable"
},
"explore": {
"viewAria": "List view",
+4 -1
View File
@@ -192,7 +192,10 @@
"nodeCpu": "CPU: {{value}}%",
"nodeMemory": "Память: {{used}} / {{total}} МБ ({{percent}}%)",
"nodeWas": "было {{value}}%",
"nodeAria": "{{node}}: CPU {{cpu}}%, память {{memory}}%"
"nodeAria": "{{node}}: CPU {{cpu}}%, память {{memory}}%",
"adminVersion": "Admin {{version}}",
"apiVersion": "API {{version}}",
"apiVersionUnavailable": "API — нет данных"
},
"explore": {
"viewAria": "Вид списка",