feat: version checking

This commit is contained in:
diced 2025-05-08 21:20:47 -07:00
parent 485f106a65
commit 4a5d01c663
No known key found for this signature in database
GPG key ID: 436B2B0FA0DCA354
4 changed files with 252 additions and 18 deletions

View file

@ -48,6 +48,7 @@ import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import ConfigProvider from './ConfigProvider';
import VersionBadge from './VersionBadge';
type NavLinks = {
label: string;
@ -357,23 +358,27 @@ export default function Layout({ children, config }: { children: React.ReactNode
}
})}
<Divider mt='auto' />
<div style={{ marginTop: 'auto' }}>
<VersionBadge />
<ScrollArea mah='auto'>
<Box>
{config.website.externalLinks.map(({ name, url }, i) => (
<NavLink
key={i}
label={name}
leftSection={<IconExternalLink size='1rem' />}
variant='light'
component={Link}
href={url}
target='_blank'
/>
))}
</Box>
</ScrollArea>
<Divider />
<ScrollArea mah='auto'>
<Box>
{config.website.externalLinks.map(({ name, url }, i) => (
<NavLink
key={i}
label={name}
leftSection={<IconExternalLink size='1rem' />}
variant='light'
component={Link}
href={url}
target='_blank'
/>
))}
</Box>
</ScrollArea>
</div>
</AppShell.Navbar>
<AppShell.Main>

View file

@ -0,0 +1,173 @@
import useVersion from '@/lib/hooks/useVersion';
import {
Anchor,
Badge,
Button,
Flex,
Indicator,
Modal,
Paper,
Stack,
Text,
Title,
Tooltip,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
function DataDisplay({ items }: { items: { label: string; value: string; href?: string }[] }) {
return (
<Paper withBorder p='sm'>
<Stack gap='xs'>
{items.map((item, index) => (
<Flex justify='space-between' align='center' style={{ width: '100%' }} key={index}>
<Text c='dimmed' fw='bolder' style={{ flex: 1 }}>
{item.label}
</Text>
{item.href ? (
<Anchor href={item.href} target='_blank'>
{item.value}
</Anchor>
) : (
<Text>{item.value}</Text>
)}
</Flex>
))}
</Stack>
</Paper>
);
}
function VersionButton({ text, children, href }: { href: string; text: string; children: React.ReactNode }) {
return (
<Button
component='a'
href={href}
target='_blank'
variant='filled'
fullWidth
color='blue'
size='sm'
mt='xs'
leftSection={
<Text size='sm' fw='bolder'>
{text}
</Text>
}
>
{children}
</Button>
);
}
export default function VersionBadge() {
const { version, isLoading } = useVersion();
const [opened, { open, close }] = useDisclosure(false);
if (isLoading) return null;
if (!version) return null;
return (
<>
<Modal title='Zipline Version' opened={opened} onClose={close} size='lg'>
{version.isLatest && <Text>Running the latest version of Zipline.</Text>}
{version.isUpstream && (
<Text>
You are running an <b>unstable</b> version of Zipline. Upstream versions are not fully tested and
may contain bugs.
</Text>
)}
{!version.isLatest && !version.isUpstream && version.isRelease && (
<Text>
You are running an <b>outdated</b> version of Zipline. It is recommended to update to the{' '}
<Anchor href={version.latest?.url!}>latest version</Anchor>.
</Text>
)}
<Indicator
processing
position='middle-end'
inline
offset={-15}
color='red'
disabled={version.isLatest}
>
<Title order={3} my='sm'>
Current Version
</Title>
</Indicator>
<DataDisplay
items={[
{
label: 'Version',
value: version.version?.tag!,
href: `https://github.com/diced/zipline/releases/${version.version?.tag}`,
},
{
label: 'Commit',
value: version.version?.sha!,
href: `https://github.com/diced/zipline/commit/${version.version?.sha}`,
},
{ label: 'Upstream?', value: version.isUpstream ? 'Yes' : 'No' },
]}
/>
{!version.isLatest && version.isUpstream && (
<>
<Title order={3} mt='sm'>
Latest Commit Available
</Title>
<Text c='dimmed' size='sm' mb='sm'>
This is only visible when running an upstream version.
</Text>
<DataDisplay
items={[
{
label: 'Commit',
value: version.latest?.commit?.sha!.slice(0, 7)!,
href: `https://github.com/diced/zipline/commit/${version.latest?.commit?.sha}`,
},
{
label: 'Available to update',
value: version.latest?.commit?.pull ? 'Yes' : 'No',
},
]}
/>
</>
)}
{!version.isLatest && version.isRelease && (
<>
<Title order={3} mt='sm'>
{version.latest?.tag} is available
</Title>
<VersionButton text='Changelogs' href={version.latest?.url!}>
{version.latest?.tag}
</VersionButton>
<VersionButton text='Update' href='https://zipline.diced.sh/docs/get-started/docker#updating'>
{version.latest?.tag}
</VersionButton>
</>
)}
</Modal>
<Tooltip label='Click to view more version information'>
<Badge
onClick={open}
style={{ cursor: 'pointer', textTransform: 'unset' }}
mx='sm'
my='xs'
color={version.isLatest ? 'green' : 'red'}
variant='dot'
size='lg'
radius='md'
>
{version.version?.tag}
</Badge>
</Tooltip>
</>
);
}

23
src/lib/hooks/useVersion.ts Executable file
View file

@ -0,0 +1,23 @@
import useSWR from 'swr';
import { Response } from '../api/response';
const f = async () => {
const res = await fetch('/api/version', {
cache: 'force-cache',
});
if (!res.ok) throw new Error('Failed to fetch version');
const r = await res.json();
return r;
};
export default function useVersion() {
const { isLoading, data } = useSWR<Response['/api/version'], Error>('/api/version', f, {
refreshInterval: undefined,
revalidateOnFocus: false,
revalidateIfStale: false,
refreshWhenOffline: false,
refreshWhenHidden: false,
revalidateOnReconnect: false,
});
return { version: data?.data, isLoading };
}

View file

@ -3,16 +3,49 @@ import fastifyPlugin from 'fastify-plugin';
import { getVersion } from '@/lib/version';
export type ApiVersionResponse = {
version: string;
details: ReturnType<typeof getVersion>;
data: VersionAPI;
};
interface VersionAPI {
isUpstream?: boolean;
isRelease?: boolean;
isLatest?: boolean;
version?: {
tag: string;
sha: string;
url: string;
};
latest?: {
tag: string;
url: string;
commit?: {
sha: string;
url: string;
pull: boolean;
};
};
}
export const PATH = '/api/version';
export default fastifyPlugin(
(server, _, done) => {
server.get(PATH, { preHandler: [userMiddleware] }, async (_, res) => {
const details = getVersion();
const params = new URLSearchParams([['details', JSON.stringify(details)]]);
return res.send(details);
const resp = await fetch(`https://zipline-version.diced.sh/?${params.toString()}`);
if (!resp.ok) {
return res.internalServerError('failed to fetch version details: ' + await resp.text());
}
const data: VersionAPI = await resp.json();
return res.send({
data,
details,
});
});
done();