import { Alert, Button, message } from 'antd' import { useQuery, useMutation } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useState } from 'react' import apiClient, { isEnvelope } from '../api/client' interface PackageVersions { [key: string]: string } // hasUpdate compares an *_installed value against its *_available // counterpart. Returns the package base name + both versions when an // update is pending; null otherwise. We pick the first package that // has a real upgrade — matching enconf's "show one upgrade hint at // a time" UX. function pickUpdate(v: PackageVersions): { pkg: string; installed: string; available: string } | null { for (const key of Object.keys(v)) { if (!key.endsWith('_installed')) continue const pkg = key.replace('_installed', '') const installed = v[key] const available = v[`${pkg}_available`] if (installed && available && installed !== available) { return { pkg, installed, available } } } return null } export default function UpdateBanner() { const { t } = useTranslation() const [applying, setApplying] = useState(false) const { data } = useQuery({ queryKey: ['system', 'package-versions'], queryFn: async () => { const r = await apiClient.get('/system/package-versions') if (isEnvelope(r.data)) return r.data.data as PackageVersions return {} as PackageVersions }, refetchInterval: 5 * 60 * 1000, // 5 min poll matches enconf cadence }) const upgrade = useMutation({ mutationFn: async () => { await apiClient.post('/system/upgrade') }, onSuccess: () => { setApplying(true) message.success(t('update.started')) }, }) if (!data) return null const u = pickUpdate(data) if (!u) return null return ( upgrade.mutate()}> {applying ? t('update.applying') : t('update.applyNow')} } /> ) }