feat(admin): link version numbers in sidebar to GitHub release notes (#566)

Closes #566.

The admin sidebar showed the running frontend + backend versions as
plain text. Wraps each version (and the "update available" indicator)
in an anchor pointing at the corresponding GitHub release tag, opening
in a new tab so the admin session isn't disrupted.

A small githubReleaseUrl helper (extracted to its own module for
testability) does the version → URL mapping. Because release-please
tags every release as `vX.Y.Z[-beta.N]`, the version string already
carries the channel suffix and a pure template covers both stable and
beta without branching.

Three unit tests pin the URL template — stable, beta-with-suffix, and
a defensive check that the leading `v` isn't double-prefixed if a
caller accidentally passes a tag-shaped value.
This commit is contained in:
Paul Nothaft
2026-05-29 10:25:12 +02:00
parent 3ceeccd85a
commit d231623c59
3 changed files with 79 additions and 6 deletions
+35 -6
View File
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { Info, ArrowUpCircle } from 'lucide-react';
import { api } from '../../config/api';
import { githubReleaseUrl as releaseUrl } from '../../utils/githubReleaseUrl';
import packageJson from '../../../package.json';
// Frontend version from package.json
@@ -63,20 +64,48 @@ export const VersionInfo: React.FC = () => {
{channelBadge}
</div>
<div className="mt-1 space-y-0.5 text-xs text-neutral-500">
<div>Frontend: v{FRONTEND_VERSION}</div>
<div>
Frontend:{' '}
<a
href={releaseUrl(FRONTEND_VERSION)}
target="_blank"
rel="noopener noreferrer"
className="text-neutral-500 hover:text-neutral-700 hover:underline"
title={t('admin.viewReleaseNotes', 'View release notes on GitHub')}
>
v{FRONTEND_VERSION}
</a>
</div>
{versionInfo && (
<div>Backend: v{versionInfo.backend}</div>
<div>
Backend:{' '}
<a
href={releaseUrl(versionInfo.backend)}
target="_blank"
rel="noopener noreferrer"
className="text-neutral-500 hover:text-neutral-700 hover:underline"
title={t('admin.viewReleaseNotes', 'View release notes on GitHub')}
>
v{versionInfo.backend}
</a>
</div>
)}
</div>
{updateInfo?.enabled && updateInfo?.updateAvailable && (
<div className="mt-2 flex items-center gap-1 text-xs text-blue-600">
{updateInfo?.enabled && updateInfo?.updateAvailable && updateInfo.latest && (
<a
href={releaseUrl(updateInfo.latest.forChannel)}
target="_blank"
rel="noopener noreferrer"
className="mt-2 flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 hover:underline"
title={t('admin.viewReleaseNotes', 'View release notes on GitHub')}
>
<ArrowUpCircle className="w-3 h-3" />
<span>
{t('admin.updates.updateAvailableShort', 'v{{version}} available', {
version: updateInfo.latest?.forChannel
version: updateInfo.latest.forChannel
})}
</span>
</div>
</a>
)}
</div>
);