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
+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')}