1- import { useEffect , useMemo , useState } from "react" ;
1+ import { useEffect , useMemo , useRef , useState } from "react" ;
22import { formatUptime } from "../formatUptime" ;
33import { IconAlert , IconExternal , IconInfo , IconRefresh , IconX } from "../icons" ;
44import { useI18n , Trans } from "../i18n" ;
@@ -98,6 +98,7 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
9898 const [ updateChannel , setUpdateChannel ] = useState < UpdateChannel > ( "latest" ) ;
9999 const [ updateRestart , setUpdateRestart ] = useState ( true ) ;
100100 const [ updateLoading , setUpdateLoading ] = useState ( false ) ;
101+ const updateRetryRef = useRef ( 0 ) ;
101102 const [ updateCheck , setUpdateCheck ] = useState < UpdateCheckData | null > ( null ) ;
102103 const [ updateError , setUpdateError ] = useState < string | null > ( null ) ;
103104 const [ updateJob , setUpdateJob ] = useState < UpdateJob | null > ( null ) ;
@@ -317,14 +318,25 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
317318 const data = await res . json ( ) as UpdateCheckData | { error ?: string } ;
318319 if ( ! res . ok ) throw new Error ( "error" in data && data . error ? data . error : "update check failed" ) ;
319320 setUpdateCheck ( data as UpdateCheckData ) ;
321+ updateRetryRef . current = 0 ;
320322 } catch ( err ) {
321323 setUpdateError ( err instanceof Error ? err . message : String ( err ) ) ;
322324 } finally {
323325 setUpdateLoading ( false ) ;
326+
327+ setUpdateCheck ( prev => {
328+ if ( ! prev || prev . reason !== "latest_unavailable" ) return prev ;
329+ const max = 2 ;
330+ if ( updateRetryRef . current >= max ) return prev ;
331+ updateRetryRef . current += 1 ;
332+ setTimeout ( ( ) => { void fetchUpdateCheck ( channel ) ; } , 800 * updateRetryRef . current ) ;
333+ return prev ;
334+ } ) ;
324335 }
325336 } ;
326337
327338 const openUpdateDialog = ( ) => {
339+ updateRetryRef . current = 0 ;
328340 const channel = defaultUpdateChannel ( health ?. version ) ;
329341 setUpdateChannel ( channel ) ;
330342 setUpdateRestart ( true ) ;
@@ -333,6 +345,7 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
333345 } ;
334346
335347 const changeUpdateChannel = ( channel : UpdateChannel ) => {
348+ updateRetryRef . current = 0 ;
336349 setUpdateChannel ( channel ) ;
337350 fetchUpdateCheck ( channel ) ;
338351 } ;
@@ -692,7 +705,18 @@ export default function Dashboard({ apiBase }: { apiBase: string }) {
692705 < div className = "notice-warn" role = "status" > < IconAlert /> { t ( "dash.updateSource" ) } </ div >
693706 ) }
694707 { updateCheck . reason === "latest_unavailable" && (
695- < div className = "notice-warn" role = "status" > < IconAlert /> { t ( "dash.updateUnavailable" ) } </ div >
708+ < div className = "notice-warn" role = "status" >
709+ < IconAlert /> { t ( "dash.updateUnavailable" ) }
710+ < button
711+ type = "button"
712+ className = "btn btn-ghost btn-sm"
713+ disabled = { updateLoading }
714+ onClick = { ( ) => { void fetchUpdateCheck ( updateChannel ) ; } }
715+ style = { { marginLeft : 12 } }
716+ >
717+ < IconRefresh /> { t ( "dash.updateRetry" ) }
718+ </ button >
719+ </ div >
696720 ) }
697721 { updateCheck . canUpdate && (
698722 < div className = "spread update-restart" >
0 commit comments