From ba76d5bbc9de0a5102f69ea886d47374d2c99b4a Mon Sep 17 00:00:00 2001 From: Siyuan Date: Fri, 7 Nov 2025 16:01:09 +0000 Subject: [PATCH] refactor(ui): simplify update dialog --- internal/ota/ota.go | 49 +++--- ota.go | 4 +- .../routes/devices.$id.settings.advanced.tsx | 6 +- .../devices.$id.settings.general.update.tsx | 148 +++++++----------- ui/src/utils/jsonrpc.ts | 4 +- 5 files changed, 91 insertions(+), 120 deletions(-) diff --git a/internal/ota/ota.go b/internal/ota/ota.go index 0459c3f2..e23010f5 100644 --- a/internal/ota/ota.go +++ b/internal/ota/ota.go @@ -220,6 +220,8 @@ type UpdateParams struct { ResetConfig bool `json:"resetConfig"` } +// getUpdateStatus gets the update status for the given components +// and updates the componentUpdateStatuses map func (s *State) getUpdateStatus( ctx context.Context, params UpdateParams, @@ -239,7 +241,7 @@ func (s *State) getUpdateStatus( systemUpdate = ¤tSystemUpdate } - err = s.doGetUpdateStatus(ctx, params, appUpdate, systemUpdate) + err = s.checkUpdateStatus(ctx, params, appUpdate, systemUpdate) if err != nil { return nil, nil, err } @@ -250,21 +252,20 @@ func (s *State) getUpdateStatus( return appUpdate, systemUpdate, nil } -// doGetUpdateStatus is the internal function that gets the update status -// it WON'T change the state of the OTA state -func (s *State) doGetUpdateStatus( +// checkUpdateStatus checks the update status for the given components +func (s *State) checkUpdateStatus( ctx context.Context, params UpdateParams, - appUpdate *componentUpdateStatus, - systemUpdate *componentUpdateStatus, + appUpdateStatus *componentUpdateStatus, + systemUpdateStatus *componentUpdateStatus, ) error { // Get local versions systemVersionLocal, appVersionLocal, err := s.getLocalVersion() if err != nil { return fmt.Errorf("error getting local version: %w", err) } - appUpdate.localVersion = appVersionLocal.String() - systemUpdate.localVersion = systemVersionLocal.String() + appUpdateStatus.localVersion = appVersionLocal.String() + systemUpdateStatus.localVersion = systemVersionLocal.String() // Get remote metadata remoteMetadata, err := s.fetchUpdateMetadata(ctx, params) @@ -276,13 +277,13 @@ func (s *State) doGetUpdateStatus( } return err } - appUpdate.url = remoteMetadata.AppURL - appUpdate.hash = remoteMetadata.AppHash - appUpdate.version = remoteMetadata.AppVersion + appUpdateStatus.url = remoteMetadata.AppURL + appUpdateStatus.hash = remoteMetadata.AppHash + appUpdateStatus.version = remoteMetadata.AppVersion - systemUpdate.url = remoteMetadata.SystemURL - systemUpdate.hash = remoteMetadata.SystemHash - systemUpdate.version = remoteMetadata.SystemVersion + systemUpdateStatus.url = remoteMetadata.SystemURL + systemUpdateStatus.hash = remoteMetadata.SystemHash + systemUpdateStatus.version = remoteMetadata.SystemVersion // Get remote versions systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion) @@ -290,26 +291,26 @@ func (s *State) doGetUpdateStatus( err = fmt.Errorf("error parsing remote system version: %w", err) return err } - systemUpdate.available = systemVersionRemote.GreaterThan(systemVersionLocal) - systemUpdate.downgradeAvailable = systemVersionRemote.LessThan(systemVersionLocal) + systemUpdateStatus.available = systemVersionRemote.GreaterThan(systemVersionLocal) + systemUpdateStatus.downgradeAvailable = systemVersionRemote.LessThan(systemVersionLocal) appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion) if err != nil { err = fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion) return err } - appUpdate.available = appVersionRemote.GreaterThan(appVersionLocal) - appUpdate.downgradeAvailable = appVersionRemote.LessThan(appVersionLocal) + appUpdateStatus.available = appVersionRemote.GreaterThan(appVersionLocal) + appUpdateStatus.downgradeAvailable = appVersionRemote.LessThan(appVersionLocal) // Handle pre-release updates isRemoteSystemPreRelease := systemVersionRemote.Prerelease() != "" isRemoteAppPreRelease := appVersionRemote.Prerelease() != "" if isRemoteSystemPreRelease && !params.IncludePreRelease { - systemUpdate.available = false + systemUpdateStatus.available = false } if isRemoteAppPreRelease && !params.IncludePreRelease { - appUpdate.available = false + appUpdateStatus.available = false } return nil @@ -317,12 +318,12 @@ func (s *State) doGetUpdateStatus( // GetUpdateStatus returns the current update status (for backwards compatibility) func (s *State) GetUpdateStatus(ctx context.Context, params UpdateParams) (*UpdateStatus, error) { - appUpdate := &componentUpdateStatus{} - systemUpdate := &componentUpdateStatus{} - err := s.doGetUpdateStatus(ctx, params, appUpdate, systemUpdate) + appUpdateStatus := componentUpdateStatus{} + systemUpdateStatus := componentUpdateStatus{} + err := s.checkUpdateStatus(ctx, params, &appUpdateStatus, &systemUpdateStatus) if err != nil { return nil, fmt.Errorf("error getting update status: %w", err) } - return toUpdateStatus(appUpdate, systemUpdate, ""), nil + return toUpdateStatus(&appUpdateStatus, &systemUpdateStatus, ""), nil } diff --git a/ota.go b/ota.go index b9d454d6..1ebd54e3 100644 --- a/ota.go +++ b/ota.go @@ -135,8 +135,8 @@ func rpcGetLocalVersion() (*ota.LocalMetadata, error) { } type updateParams struct { - AppTargetVersion string `json:"app"` - SystemTargetVersion string `json:"system"` + AppTargetVersion string `json:"appTargetVersion"` + SystemTargetVersion string `json:"systemTargetVersion"` Components string `json:"components,omitempty"` // components is a comma-separated list of components to update } diff --git a/ui/src/routes/devices.$id.settings.advanced.tsx b/ui/src/routes/devices.$id.settings.advanced.tsx index 45b9d705..b84d8ed7 100644 --- a/ui/src/routes/devices.$id.settings.advanced.tsx +++ b/ui/src/routes/devices.$id.settings.advanced.tsx @@ -204,9 +204,8 @@ export default function SettingsAdvancedRoute() { setVersionUpdateLoading(true); versionInfo = await checkUpdateComponents({ components: components.join(","), - // TODO: Rename to appTargetVersion and systemTargetVersion - app: appVersion, - system: systemVersion, + appTargetVersion: appVersion, + systemTargetVersion: systemVersion, }, devChannel); console.log("versionInfo", versionInfo); } catch (error: unknown) { @@ -216,7 +215,6 @@ export default function SettingsAdvancedRoute() { } const pageParams = new URLSearchParams(); - pageParams.set("downgrade", "true"); if (components.includes("app") && versionInfo.remote?.appVersion && versionInfo.appDowngradeAvailable) { pageParams.set("app", versionInfo.remote?.appVersion); } diff --git a/ui/src/routes/devices.$id.settings.general.update.tsx b/ui/src/routes/devices.$id.settings.general.update.tsx index 6376945b..20a026cc 100644 --- a/ui/src/routes/devices.$id.settings.general.update.tsx +++ b/ui/src/routes/devices.$id.settings.general.update.tsx @@ -11,7 +11,7 @@ import LoadingSpinner from "@components/LoadingSpinner"; import UpdatingStatusCard, { type UpdatePart } from "@components/UpdatingStatusCard"; import { m } from "@localizations/messages.js"; import { sleep } from "@/utils"; -import { SystemVersionInfo } from "@/utils/jsonrpc"; +import { checkUpdateComponents, SystemVersionInfo, updateParams } from "@/utils/jsonrpc"; export default function SettingsGeneralUpdateRoute() { const navigate = useNavigate(); @@ -38,20 +38,16 @@ export default function SettingsGeneralUpdateRoute() { setModalView("updating"); }, [send, setModalView]); - const onConfirmDowngrade = useCallback(() => { + const onConfirmCustomUpdate = useCallback((appTargetVersion?: string, systemTargetVersion?: string) => { const components = []; - if (customSystemVersion) { - components.push("system"); - } - if (customAppVersion) { - components.push("app"); - } + if (appTargetVersion) components.push("system"); + if (systemTargetVersion) components.push("app"); send("tryUpdateComponents", { params: { components: components.join(","), - app: customAppVersion, - system: customSystemVersion, + appTargetVersion, + systemTargetVersion, }, includePreRelease: false, resetConfig, @@ -59,7 +55,7 @@ export default function SettingsGeneralUpdateRoute() { if ("error" in resp) return; setModalView("updating"); }); - }, [send, setModalView, customAppVersion, customSystemVersion, resetConfig]); + }, [send, setModalView, resetConfig]); useEffect(() => { if (otaState.updating) { @@ -76,7 +72,7 @@ export default function SettingsGeneralUpdateRoute() { return ; @@ -85,13 +81,13 @@ export default function SettingsGeneralUpdateRoute() { export function Dialog({ onClose, onConfirmUpdate, - onConfirmDowngrade, + onConfirmCustomUpdate: onConfirmCustomUpdateCallback, customAppVersion, customSystemVersion, }: Readonly<{ onClose: () => void; onConfirmUpdate: () => void; - onConfirmDowngrade: () => void; + onConfirmCustomUpdate: (appVersion?: string, systemVersion?: string) => void; customAppVersion?: string; customSystemVersion?: string; }>) { @@ -99,32 +95,30 @@ export function Dialog({ const [versionInfo, setVersionInfo] = useState(null); const { modalView, setModalView, otaState } = useUpdateStore(); - const { send } = useJsonRpc(); + const forceCustomUpdate = customSystemVersion !== undefined || customAppVersion !== undefined; + const onConfirmCustomUpdate = useCallback(() => { + onConfirmCustomUpdateCallback( + customAppVersion !== undefined ? customAppVersion : versionInfo?.remote?.appVersion, + customSystemVersion !== undefined ? customSystemVersion : versionInfo?.remote?.systemVersion, + ); + }, [onConfirmCustomUpdateCallback, customAppVersion, customSystemVersion, versionInfo]); const onFinishedLoading = useCallback( (versionInfo: SystemVersionInfo) => { const hasUpdate = versionInfo?.systemUpdateAvailable || versionInfo?.appUpdateAvailable; - const forceCustomUpdate = customSystemVersion !== undefined || customAppVersion !== undefined; setVersionInfo(versionInfo); - if (forceCustomUpdate) { - setModalView("confirmCustomUpdate"); - } else if (hasUpdate) { + if (hasUpdate || forceCustomUpdate) { setModalView("updateAvailable"); } else { setModalView("upToDate"); } }, - [setModalView, customAppVersion, customSystemVersion], + [setModalView, forceCustomUpdate], ); - const onCancelDowngrade = useCallback(() => { - send("cancelDowngrade", {}); - onClose(); - }, [onClose, send]); - return (
@@ -137,24 +131,22 @@ export function Dialog({ )} {modalView === "loading" && ( - + )} {modalView === "updateAvailable" && ( )} - {modalView === "confirmCustomUpdate" && ( - - )} {modalView === "updating" && ( void; onCancelCheck: () => void; + customAppVersion?: string; + customSystemVersion?: string; }) { const [progressWidth, setProgressWidth] = useState("0%"); const abortControllerRef = useRef(null); @@ -190,6 +186,23 @@ function LoadingState({ const { setModalView } = useUpdateStore(); const progressBarRef = useRef(null); + + const checkUpdate = useCallback(async () => { + if (!customAppVersion && !customSystemVersion) { + return await getVersionInfo(); + } + const params : updateParams = { + components: "", + appTargetVersion: customAppVersion, + systemTargetVersion: customSystemVersion, + }; + if (customAppVersion) params.components += ",app"; + if (customSystemVersion) params.components += ",system"; + params.components = params.components?.replace(/^,+/, ""); + + return await checkUpdateComponents(params, false); + }, [customAppVersion, customSystemVersion, getVersionInfo]); + useEffect(() => { abortControllerRef.current = new AbortController(); const signal = abortControllerRef.current.signal; @@ -199,8 +212,7 @@ function LoadingState({ setProgressWidth("100%"); }, 0); - // TODO: CHECK FOR QUERY PARAMS - getVersionInfo() + checkUpdate() .then(async versionInfo => { // Add a small delay to ensure it's not just flickering await sleep(600); @@ -222,7 +234,7 @@ function LoadingState({ clearTimeout(animationTimer); abortControllerRef.current?.abort(); }; - }, [getVersionInfo, onFinished, setModalView]); + }, [checkUpdate, onFinished, setModalView]); return (
@@ -430,11 +442,13 @@ function SystemUpToDateState({ function UpdateAvailableState({ versionInfo, - onConfirmUpdate, + forceCustomUpdate, + onConfirm, onClose, }: { versionInfo: SystemVersionInfo; - onConfirmUpdate: () => void; + forceCustomUpdate: boolean; + onConfirm: () => void; onClose: () => void; }) { return ( @@ -444,23 +458,23 @@ function UpdateAvailableState({ {m.general_update_available_title()}

- {m.general_update_available_description()} + {forceCustomUpdate ? m.general_update_downgrade_available_description() : m.general_update_available_description()}

- {versionInfo?.systemUpdateAvailable ? ( + {(forceCustomUpdate ? versionInfo?.systemDowngradeAvailable : versionInfo?.systemUpdateAvailable) ? ( <> - {m.general_update_system_type()}: {versionInfo?.remote?.systemVersion} + {m.general_update_system_type()}: {versionInfo?.local?.systemVersion} {versionInfo?.remote?.systemVersion}
) : null} - {versionInfo?.appUpdateAvailable ? ( + {(forceCustomUpdate ? versionInfo?.appDowngradeAvailable : versionInfo?.appUpdateAvailable) ? ( <> - {m.general_update_application_type()}: {versionInfo?.remote?.appVersion} + {m.general_update_application_type()}: {versionInfo?.local?.appVersion} {versionInfo?.remote?.appVersion} ) : null}

-
@@ -468,48 +482,6 @@ function UpdateAvailableState({ ); } -function ConfirmCustomUpdate({ - appVersion, - systemVersion, - onConfirm, - onCancel, -}: { - appVersion?: string; - systemVersion?: string; - onConfirm: () => void; - onCancel: () => void; -}) { - return ( -
-
-

- {m.general_update_downgrade_available_title()} -

-

- {m.general_update_downgrade_available_description()} -

-

- {systemVersion ? ( - <> - {m.general_update_system_type()}: {systemVersion} -
- - ) : null} - {appVersion ? ( - <> - {m.general_update_application_type()}: {appVersion} - - ) : null} -

-
-
-
-
- ); -} - function UpdateCompletedState({ onClose }: { onClose: () => void }) { return (
diff --git a/ui/src/utils/jsonrpc.ts b/ui/src/utils/jsonrpc.ts index 68ef0d47..b219a30e 100644 --- a/ui/src/utils/jsonrpc.ts +++ b/ui/src/utils/jsonrpc.ts @@ -246,8 +246,8 @@ export async function getLocalVersion() { } export interface updateParams { - app?: string; - system?: string; + appTargetVersion?: string; + systemTargetVersion?: string; components?: string; }