mirror of https://github.com/jetkvm/kvm.git
Compare commits
15 Commits
27b7c5557b
...
8c032918d7
| Author | SHA1 | Date |
|---|---|---|
|
|
8c032918d7 | |
|
|
e186d3b674 | |
|
|
8c88f72ba4 | |
|
|
931413acd9 | |
|
|
550ff2e108 | |
|
|
cc36d83154 | |
|
|
bce968000c | |
|
|
0a299bbe18 | |
|
|
ae197c530b | |
|
|
e63c7fce0d | |
|
|
9e8ada32b3 | |
|
|
2447e8fff2 | |
|
|
a69f7c9c50 | |
|
|
019c73cd61 | |
|
|
325de4a33a |
2
hw.go
2
hw.go
|
|
@ -39,7 +39,7 @@ func readOtpEntropy() ([]byte, error) { //nolint:unused
|
|||
return content[0x17:0x1C], nil
|
||||
}
|
||||
|
||||
func hwReboot(force bool, postRebootAction *ota.PostRebootAction, delay time.Duration) error {
|
||||
func hwReboot(force bool, postRebootAction *ota.PostRebootAction, delay time.Duration) error { //nolint:unused
|
||||
logger.Info().Msgf("Reboot requested, rebooting in %d seconds...", delay)
|
||||
|
||||
writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package ota
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrVersionNotFound is returned when the specified version is not found
|
||||
ErrVersionNotFound = errors.New("specified version not found")
|
||||
)
|
||||
|
|
@ -3,7 +3,6 @@ package ota
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
|
@ -24,14 +23,12 @@ func (s *State) GetReleaseAPIEndpoint() string {
|
|||
}
|
||||
|
||||
// getUpdateURL returns the update URL for the given parameters
|
||||
func (s *State) getUpdateURL(params UpdateParams) (string, error, bool) {
|
||||
func (s *State) getUpdateURL(params UpdateParams) (string, error) {
|
||||
updateURL, err := url.Parse(s.releaseAPIEndpoint)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing update metadata URL: %w", err), false
|
||||
return "", fmt.Errorf("error parsing update metadata URL: %w", err)
|
||||
}
|
||||
|
||||
isCustomVersion := false
|
||||
|
||||
appTargetVersion := s.GetTargetVersion("app")
|
||||
if appTargetVersion != "" && params.AppTargetVersion == "" {
|
||||
params.AppTargetVersion = appTargetVersion
|
||||
|
|
@ -46,21 +43,19 @@ func (s *State) getUpdateURL(params UpdateParams) (string, error, bool) {
|
|||
query.Set("prerelease", fmt.Sprintf("%v", params.IncludePreRelease))
|
||||
if params.AppTargetVersion != "" {
|
||||
query.Set("appVersion", params.AppTargetVersion)
|
||||
isCustomVersion = true
|
||||
}
|
||||
if params.SystemTargetVersion != "" {
|
||||
query.Set("systemVersion", params.SystemTargetVersion)
|
||||
isCustomVersion = true
|
||||
}
|
||||
updateURL.RawQuery = query.Encode()
|
||||
|
||||
return updateURL.String(), nil, isCustomVersion
|
||||
return updateURL.String(), nil
|
||||
}
|
||||
|
||||
func (s *State) fetchUpdateMetadata(ctx context.Context, params UpdateParams) (*UpdateMetadata, error) {
|
||||
metadata := &UpdateMetadata{}
|
||||
|
||||
url, err, isCustomVersion := s.getUpdateURL(params)
|
||||
url, err := s.getUpdateURL(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting update URL: %w", err)
|
||||
}
|
||||
|
|
@ -82,10 +77,6 @@ func (s *State) fetchUpdateMetadata(ctx context.Context, params UpdateParams) (*
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if isCustomVersion && resp.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrVersionNotFound
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
|
@ -198,7 +189,7 @@ func (s *State) doUpdate(ctx context.Context, params UpdateParams) error {
|
|||
|
||||
postRebootAction := &PostRebootAction{
|
||||
HealthCheck: "/device/status",
|
||||
RedirectTo: redirectUrl,
|
||||
RedirectUrl: redirectUrl,
|
||||
}
|
||||
|
||||
if err := s.reboot(true, postRebootAction, 10*time.Second); err != nil {
|
||||
|
|
@ -239,29 +230,10 @@ func (s *State) getUpdateStatus(
|
|||
systemUpdate = ¤tSystemUpdate
|
||||
}
|
||||
|
||||
err = s.doGetUpdateStatus(ctx, params, appUpdate, systemUpdate)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
s.componentUpdateStatuses["app"] = *appUpdate
|
||||
s.componentUpdateStatuses["system"] = *systemUpdate
|
||||
|
||||
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(
|
||||
ctx context.Context,
|
||||
params UpdateParams,
|
||||
appUpdate *componentUpdateStatus,
|
||||
systemUpdate *componentUpdateStatus,
|
||||
) error {
|
||||
// Get local versions
|
||||
systemVersionLocal, appVersionLocal, err := s.getLocalVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting local version: %w", err)
|
||||
return nil, nil, fmt.Errorf("error getting local version: %w", err)
|
||||
}
|
||||
appUpdate.localVersion = appVersionLocal.String()
|
||||
systemUpdate.localVersion = systemVersionLocal.String()
|
||||
|
|
@ -269,12 +241,8 @@ func (s *State) doGetUpdateStatus(
|
|||
// Get remote metadata
|
||||
remoteMetadata, err := s.fetchUpdateMetadata(ctx, params)
|
||||
if err != nil {
|
||||
if err == ErrVersionNotFound || errors.Unwrap(err) == ErrVersionNotFound {
|
||||
err = ErrVersionNotFound
|
||||
} else {
|
||||
err = fmt.Errorf("error checking for updates: %w", err)
|
||||
}
|
||||
return err
|
||||
err = fmt.Errorf("error checking for updates: %w", err)
|
||||
return
|
||||
}
|
||||
appUpdate.url = remoteMetadata.AppURL
|
||||
appUpdate.hash = remoteMetadata.AppHash
|
||||
|
|
@ -288,7 +256,7 @@ func (s *State) doGetUpdateStatus(
|
|||
systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error parsing remote system version: %w", err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
systemUpdate.available = systemVersionRemote.GreaterThan(systemVersionLocal)
|
||||
systemUpdate.downgradeAvailable = systemVersionRemote.LessThan(systemVersionLocal)
|
||||
|
|
@ -296,7 +264,7 @@ func (s *State) doGetUpdateStatus(
|
|||
appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion)
|
||||
return err
|
||||
return
|
||||
}
|
||||
appUpdate.available = appVersionRemote.GreaterThan(appVersionLocal)
|
||||
appUpdate.downgradeAvailable = appVersionRemote.LessThan(appVersionLocal)
|
||||
|
|
@ -312,17 +280,18 @@ func (s *State) doGetUpdateStatus(
|
|||
appUpdate.available = false
|
||||
}
|
||||
|
||||
return nil
|
||||
s.componentUpdateStatuses["app"] = *appUpdate
|
||||
s.componentUpdateStatuses["system"] = *systemUpdate
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
_, _, err := s.getUpdateStatus(ctx, params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting update status: %w", err)
|
||||
}
|
||||
|
||||
return toUpdateStatus(appUpdate, systemUpdate, ""), nil
|
||||
return s.ToUpdateStatus(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ type UpdateStatus struct {
|
|||
// It is used to redirect the user to a specific page after a reboot
|
||||
type PostRebootAction struct {
|
||||
HealthCheck string `json:"healthCheck"` // The health check URL to call after the reboot
|
||||
RedirectTo string `json:"redirectTo"` // The URL to redirect to after the reboot
|
||||
RedirectUrl string `json:"redirectUrl"` // The URL to redirect to after the reboot
|
||||
}
|
||||
|
||||
// componentUpdateStatus represents the status of a component update
|
||||
|
|
@ -156,7 +156,18 @@ func (s *State) GetTargetVersion(component string) string {
|
|||
return componentUpdate.targetVersion
|
||||
}
|
||||
|
||||
func toUpdateStatus(appUpdate *componentUpdateStatus, systemUpdate *componentUpdateStatus, error string) *UpdateStatus {
|
||||
// ToUpdateStatus converts the State to the UpdateStatus
|
||||
func (s *State) ToUpdateStatus() *UpdateStatus {
|
||||
appUpdate, ok := s.componentUpdateStatuses["app"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
systemUpdate, ok := s.componentUpdateStatuses["system"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UpdateStatus{
|
||||
Local: &LocalMetadata{
|
||||
AppVersion: appUpdate.localVersion,
|
||||
|
|
@ -174,25 +185,10 @@ func toUpdateStatus(appUpdate *componentUpdateStatus, systemUpdate *componentUpd
|
|||
SystemDowngradeAvailable: systemUpdate.downgradeAvailable,
|
||||
AppUpdateAvailable: appUpdate.available,
|
||||
AppDowngradeAvailable: appUpdate.downgradeAvailable,
|
||||
Error: error,
|
||||
Error: s.error,
|
||||
}
|
||||
}
|
||||
|
||||
// ToUpdateStatus converts the State to the UpdateStatus
|
||||
func (s *State) ToUpdateStatus() *UpdateStatus {
|
||||
appUpdate, ok := s.componentUpdateStatuses["app"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
systemUpdate, ok := s.componentUpdateStatuses["system"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return toUpdateStatus(&appUpdate, &systemUpdate, s.error)
|
||||
}
|
||||
|
||||
// IsUpdatePending returns true if an update is pending
|
||||
func (s *State) IsUpdatePending() bool {
|
||||
return s.updating
|
||||
|
|
|
|||
|
|
@ -1151,10 +1151,9 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
||||
"getLocalVersion": {Func: rpcGetLocalVersion},
|
||||
"getUpdateStatus": {Func: rpcGetUpdateStatus},
|
||||
"checkUpdateComponents": {Func: rpcCheckUpdateComponents, Params: []string{"params", "includePreRelease"}},
|
||||
"getUpdateStatusChannel": {Func: rpcGetUpdateStatusChannel},
|
||||
"tryUpdate": {Func: rpcTryUpdate},
|
||||
"tryUpdateComponents": {Func: rpcTryUpdateComponents, Params: []string{"params", "includePreRelease", "resetConfig"}},
|
||||
"tryUpdateComponents": {Func: rpcTryUpdateComponents, Params: []string{"components", "includePreRelease", "checkOnly", "resetConfig"}},
|
||||
"cancelDowngrade": {Func: rpcCancelDowngrade},
|
||||
"getDevModeState": {Func: rpcGetDevModeState},
|
||||
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
|
||||
|
|
|
|||
42
ota.go
42
ota.go
|
|
@ -134,56 +134,42 @@ func rpcGetLocalVersion() (*ota.LocalMetadata, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
type updateParams struct {
|
||||
// ComponentName represents the name of a component
|
||||
type tryUpdateComponents struct {
|
||||
AppTargetVersion string `json:"app"`
|
||||
SystemTargetVersion string `json:"system"`
|
||||
Components string `json:"components,omitempty"` // components is a comma-separated list of components to update
|
||||
}
|
||||
|
||||
func rpcTryUpdate() error {
|
||||
return rpcTryUpdateComponents(updateParams{
|
||||
return rpcTryUpdateComponents(tryUpdateComponents{
|
||||
AppTargetVersion: "",
|
||||
SystemTargetVersion: "",
|
||||
}, config.IncludePreRelease, false)
|
||||
}, config.IncludePreRelease, false, false)
|
||||
}
|
||||
|
||||
// rpcCheckUpdateComponents checks the update status for the given components
|
||||
func rpcCheckUpdateComponents(params updateParams, includePreRelease bool) (*ota.UpdateStatus, error) {
|
||||
updateParams := ota.UpdateParams{
|
||||
DeviceID: GetDeviceID(),
|
||||
IncludePreRelease: includePreRelease,
|
||||
AppTargetVersion: params.AppTargetVersion,
|
||||
SystemTargetVersion: params.SystemTargetVersion,
|
||||
}
|
||||
if params.Components != "" {
|
||||
updateParams.Components = strings.Split(params.Components, ",")
|
||||
}
|
||||
info, err := otaState.GetUpdateStatus(context.Background(), updateParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check update: %w", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func rpcTryUpdateComponents(params updateParams, includePreRelease bool, resetConfig bool) error {
|
||||
func rpcTryUpdateComponents(components tryUpdateComponents, includePreRelease bool, checkOnly bool, resetConfig bool) error {
|
||||
updateParams := ota.UpdateParams{
|
||||
DeviceID: GetDeviceID(),
|
||||
IncludePreRelease: includePreRelease,
|
||||
CheckOnly: checkOnly,
|
||||
ResetConfig: resetConfig,
|
||||
}
|
||||
|
||||
updateParams.AppTargetVersion = params.AppTargetVersion
|
||||
if err := otaState.SetTargetVersion("app", params.AppTargetVersion); err != nil {
|
||||
logger.Info().Interface("components", components).Msg("components")
|
||||
|
||||
updateParams.AppTargetVersion = components.AppTargetVersion
|
||||
if err := otaState.SetTargetVersion("app", components.AppTargetVersion); err != nil {
|
||||
return fmt.Errorf("failed to set app target version: %w", err)
|
||||
}
|
||||
|
||||
updateParams.SystemTargetVersion = params.SystemTargetVersion
|
||||
if err := otaState.SetTargetVersion("system", params.SystemTargetVersion); err != nil {
|
||||
updateParams.SystemTargetVersion = components.SystemTargetVersion
|
||||
if err := otaState.SetTargetVersion("system", components.SystemTargetVersion); err != nil {
|
||||
return fmt.Errorf("failed to set system target version: %w", err)
|
||||
}
|
||||
|
||||
if params.Components != "" {
|
||||
updateParams.Components = strings.Split(params.Components, ",")
|
||||
if components.Components != "" {
|
||||
updateParams.Components = strings.Split(components.Components, ",")
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@
|
|||
"advanced_error_update_ssh_key": "Failed to update SSH key: {error}",
|
||||
"advanced_error_usb_emulation_disable": "Failed to disable USB emulation: {error}",
|
||||
"advanced_error_usb_emulation_enable": "Failed to enable USB emulation: {error}",
|
||||
"advanced_error_version_update": "Failed to initiate version update: {error}",
|
||||
"advanced_loopback_only_description": "Restrict web interface access to localhost only (127.0.0.1)",
|
||||
"advanced_loopback_only_title": "Loopback-Only Mode",
|
||||
"advanced_loopback_warning_before": "Before enabling this feature, make sure you have either:",
|
||||
|
|
@ -101,19 +100,6 @@
|
|||
"advanced_update_ssh_key_button": "Update SSH Key",
|
||||
"advanced_usb_emulation_description": "Control the USB emulation state",
|
||||
"advanced_usb_emulation_title": "USB Emulation",
|
||||
"advanced_version_update_app_label": "App Version",
|
||||
"advanced_version_update_button": "Update to Version",
|
||||
"advanced_version_update_description": "Install a specific version from GitHub releases",
|
||||
"advanced_version_update_github_link": "JetKVM releases page",
|
||||
"advanced_version_update_helper": "Find available versions on the",
|
||||
"advanced_version_update_reset_config_description": "Reset configuration after the update",
|
||||
"advanced_version_update_reset_config_label": "Reset configuration",
|
||||
"advanced_version_update_system_label": "System Version",
|
||||
"advanced_version_update_target_app": "App only",
|
||||
"advanced_version_update_target_both": "Both App and System",
|
||||
"advanced_version_update_target_label": "What to update",
|
||||
"advanced_version_update_target_system": "System only",
|
||||
"advanced_version_update_title": "Update to Specific Version",
|
||||
"already_adopted_new_owner": "If you're the new owner, please ask the previous owner to de-register the device from their account in the cloud dashboard. If you believe this is an error, contact our support team for assistance.",
|
||||
"already_adopted_other_user": "This device is currently registered to another user in our cloud dashboard.",
|
||||
"already_adopted_return_to_dashboard": "Return to Dashboard",
|
||||
|
|
@ -255,8 +241,8 @@
|
|||
"general_auto_update_description": "Automatically update the device to the latest version",
|
||||
"general_auto_update_error": "Failed to set auto-update: {error}",
|
||||
"general_auto_update_title": "Auto Update",
|
||||
"general_check_for_stable_updates": "Downgrade",
|
||||
"general_check_for_updates": "Check for Updates",
|
||||
"general_check_for_stable_updates": "Downgrade",
|
||||
"general_page_description": "Configure device settings and update preferences",
|
||||
"general_reboot_description": "Do you want to proceed with rebooting the system?",
|
||||
"general_reboot_device": "Reboot Device",
|
||||
|
|
@ -276,13 +262,9 @@
|
|||
"general_update_checking_title": "Checking for updates…",
|
||||
"general_update_completed_description": "Your device has been successfully updated to the latest version. Enjoy the new features and improvements!",
|
||||
"general_update_completed_title": "Update Completed Successfully",
|
||||
"general_update_downgrade_available_description": "A downgrade is available to revert to a previous version.",
|
||||
"general_update_downgrade_available_title": "Downgrade Available",
|
||||
"general_update_downgrade_button": "Downgrade Now",
|
||||
"general_update_error_description": "An error occurred while updating your device. Please try again later.",
|
||||
"general_update_error_details": "Error details: {errorMessage}",
|
||||
"general_update_error_title": "Update Error",
|
||||
"general_update_keep_current_button": "Keep Current Version",
|
||||
"general_update_later_button": "Do it later",
|
||||
"general_update_now_button": "Update Now",
|
||||
"general_update_rebooting": "Rebooting to complete the update…",
|
||||
|
|
@ -916,5 +898,23 @@
|
|||
"wake_on_lan_invalid_mac": "Invalid MAC address",
|
||||
"wake_on_lan_magic_sent_success": "Magic Packet sent successfully",
|
||||
"welcome_to_jetkvm": "Welcome to JetKVM",
|
||||
"welcome_to_jetkvm_description": "Control any computer remotely"
|
||||
"welcome_to_jetkvm_description": "Control any computer remotely",
|
||||
"advanced_version_update_app_label": "App Version",
|
||||
"advanced_version_update_button": "Update to Version",
|
||||
"advanced_version_update_description": "Install a specific version from GitHub releases",
|
||||
"advanced_version_update_github_link": "JetKVM releases page",
|
||||
"advanced_version_update_helper": "Find available versions on the",
|
||||
"advanced_version_update_system_label": "System Version",
|
||||
"advanced_version_update_target_app": "App only",
|
||||
"advanced_version_update_target_both": "Both App and System",
|
||||
"advanced_version_update_target_label": "What to update",
|
||||
"advanced_version_update_target_system": "System only",
|
||||
"advanced_version_update_title": "Update to Specific Version",
|
||||
"advanced_error_version_update": "Failed to initiate version update: {error}",
|
||||
"general_update_downgrade_available_description": "A downgrade is available to revert to a previous version.",
|
||||
"general_update_downgrade_available_title": "Downgrade Available",
|
||||
"general_update_downgrade_button": "Downgrade Now",
|
||||
"general_update_keep_current_button": "Keep Current Version",
|
||||
"advanced_version_update_reset_config_description": "Reset configuration after the update",
|
||||
"advanced_version_update_reset_config_label": "Reset configuration"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useCallback } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import semver from "semver";
|
||||
|
||||
import { useDeviceStore } from "@/hooks/stores";
|
||||
import { JsonRpcError, RpcMethodNotFound } from "@/hooks/useJsonRpc";
|
||||
|
|
@ -29,44 +30,51 @@ export function useVersion() {
|
|||
setSystemVersion,
|
||||
} = useDeviceStore();
|
||||
|
||||
const getVersionInfo = useCallback(async () => {
|
||||
try {
|
||||
const result = await getUpdateStatus();
|
||||
setAppVersion(result.local.appVersion);
|
||||
setSystemVersion(result.local.systemVersion);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const jsonRpcError = error as JsonRpcError;
|
||||
notifications.error(m.updates_failed_check({ error: jsonRpcError.message }));
|
||||
throw jsonRpcError;
|
||||
}
|
||||
}, [setAppVersion, setSystemVersion]);
|
||||
if (result.error) {
|
||||
notifications.error(m.updates_failed_check({ error: String(result.error) }));
|
||||
reject(new Error("Failed to check for updates"));
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [send, setAppVersion, setSystemVersion]);
|
||||
|
||||
const getLocalVersion = useCallback(async () => {
|
||||
try {
|
||||
const result = await getLocalVersionRpc();
|
||||
setAppVersion(result.appVersion);
|
||||
setSystemVersion(result.systemVersion);
|
||||
return result;
|
||||
} catch (error: unknown) {
|
||||
const jsonRpcError = error as JsonRpcError;
|
||||
const isOnDevVersion = useMemo(() => {
|
||||
if (appVersion && semver.prerelease(appVersion)) return true;
|
||||
if (systemVersion && semver.prerelease(systemVersion)) return true;
|
||||
return false;
|
||||
}, [appVersion, systemVersion]);
|
||||
|
||||
if (jsonRpcError.code === RpcMethodNotFound) {
|
||||
console.error("Failed to get local version, using legacy remote version");
|
||||
const result = await getVersionInfo();
|
||||
return result.local;
|
||||
}
|
||||
const getLocalVersion = useCallback(() => {
|
||||
return new Promise<VersionInfo>((resolve, reject) => {
|
||||
send("getLocalVersion", {}, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
console.log(resp.error)
|
||||
if (resp.error.code === RpcMethodNotFound) {
|
||||
console.warn("Failed to get device version, using legacy version");
|
||||
return getVersionInfo().then(result => resolve(result.local)).catch(reject);
|
||||
}
|
||||
console.error("Failed to get device version N", resp.error);
|
||||
notifications.error(m.updates_failed_get_device_version({ error: String(resp.error) }));
|
||||
reject(new Error("Failed to get device version"));
|
||||
} else {
|
||||
const result = resp.result as VersionInfo;
|
||||
|
||||
console.error("Failed to get device version", jsonRpcError);
|
||||
notifications.error(m.updates_failed_get_device_version({ error: jsonRpcError.message }));
|
||||
throw jsonRpcError;
|
||||
}
|
||||
}, [setAppVersion, setSystemVersion, getVersionInfo]);
|
||||
setAppVersion(result.appVersion);
|
||||
setSystemVersion(result.systemVersion);
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [send, setAppVersion, setSystemVersion, getVersionInfo]);
|
||||
|
||||
return {
|
||||
getVersionInfo,
|
||||
getLocalVersion,
|
||||
appVersion,
|
||||
systemVersion,
|
||||
isOnDevVersion,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useSettingsStore } from "@hooks/stores";
|
||||
import { JsonRpcError, JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import { useDeviceUiNavigation } from "@hooks/useAppNavigation";
|
||||
import { Button } from "@components/Button";
|
||||
import Checkbox, { CheckboxWithLabel } from "@components/Checkbox";
|
||||
|
|
@ -17,8 +17,6 @@ import { isOnDevice } from "@/main";
|
|||
import notifications from "@/notifications";
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { sleep } from "@/utils";
|
||||
import { checkUpdateComponents } from "@/utils/jsonrpc";
|
||||
import { SystemVersionInfo } from "@hooks/useVersion";
|
||||
|
||||
export default function SettingsAdvancedRoute() {
|
||||
const { send } = useJsonRpc();
|
||||
|
|
@ -35,7 +33,7 @@ export default function SettingsAdvancedRoute() {
|
|||
const [systemVersion, setSystemVersion] = useState<string>("");
|
||||
const [resetConfig, setResetConfig] = useState(false);
|
||||
const [versionChangeAcknowledged, setVersionChangeAcknowledged] = useState(false);
|
||||
const [versionUpdateLoading, setVersionUpdateLoading] = useState(false);
|
||||
|
||||
const settings = useSettingsStore();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -185,57 +183,34 @@ export default function SettingsAdvancedRoute() {
|
|||
setShowLoopbackWarning(false);
|
||||
}, [applyLoopbackOnlyMode, setShowLoopbackWarning]);
|
||||
|
||||
const handleVersionUpdateError = useCallback((error?: JsonRpcError) => {
|
||||
notifications.error(
|
||||
m.advanced_error_version_update({
|
||||
error: error?.data ?? error?.message ?? m.unknown_error()
|
||||
}),
|
||||
{ duration: 1000 * 15 } // 15 seconds
|
||||
);
|
||||
setVersionUpdateLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleVersionUpdate = useCallback(async () => {
|
||||
const components = updateTarget === "both" ? ["app", "system"] : [updateTarget];
|
||||
let versionInfo: SystemVersionInfo | undefined;
|
||||
try {
|
||||
// we do not need to set it to false if check succeeds,
|
||||
// because it will be redirected to the update page later
|
||||
setVersionUpdateLoading(true);
|
||||
versionInfo = await checkUpdateComponents({
|
||||
components: components.join(","),
|
||||
const handleVersionUpdate = useCallback(() => {
|
||||
const params = {
|
||||
components: {
|
||||
app: appVersion,
|
||||
system: systemVersion,
|
||||
}, devChannel);
|
||||
console.log("versionInfo", versionInfo);
|
||||
} catch (error: unknown) {
|
||||
const jsonRpcError = error as JsonRpcError;
|
||||
handleVersionUpdateError(jsonRpcError);
|
||||
return ;
|
||||
}
|
||||
},
|
||||
includePreRelease: devChannel,
|
||||
checkOnly: true,
|
||||
// no need to reset config for a check only update
|
||||
resetConfig: false,
|
||||
};
|
||||
|
||||
if (!versionInfo) {
|
||||
handleVersionUpdateError();
|
||||
return;
|
||||
}
|
||||
send("tryUpdateComponents", params, (resp: JsonRpcResponse) => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
m.advanced_error_version_update({ error: resp.error.data || m.unknown_error() })
|
||||
);
|
||||
return;
|
||||
}
|
||||
const pageParams = new URLSearchParams();
|
||||
pageParams.set("downgrade", "true");
|
||||
pageParams.set("resetConfig", resetConfig.toString());
|
||||
pageParams.set("components", updateTarget === "both" ? "app,system" : updateTarget);
|
||||
|
||||
const pageParams = new URLSearchParams();
|
||||
pageParams.set("downgrade", "true");
|
||||
if (components.includes("app") && versionInfo.remote?.appVersion && versionInfo.appDowngradeAvailable) {
|
||||
pageParams.set("app", versionInfo.remote?.appVersion);
|
||||
}
|
||||
if (components.includes("system") && versionInfo.remote?.systemVersion && versionInfo.systemDowngradeAvailable) {
|
||||
pageParams.set("system", versionInfo.remote?.systemVersion);
|
||||
}
|
||||
pageParams.set("resetConfig", resetConfig.toString());
|
||||
|
||||
// Navigate to update page
|
||||
navigateTo(`/settings/general/update?${pageParams.toString()}`);
|
||||
}, [
|
||||
updateTarget, appVersion, systemVersion, devChannel,
|
||||
navigateTo, resetConfig, handleVersionUpdateError,
|
||||
setVersionUpdateLoading
|
||||
]);
|
||||
// Navigate to update page
|
||||
navigateTo(`/settings/general/update?${pageParams.toString()}`);
|
||||
});
|
||||
}, [updateTarget, appVersion, systemVersion, devChannel, send, navigateTo, resetConfig]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -399,10 +374,8 @@ export default function SettingsAdvancedRoute() {
|
|||
(updateTarget === "app" && !appVersion) ||
|
||||
(updateTarget === "system" && !systemVersion) ||
|
||||
(updateTarget === "both" && (!appVersion || !systemVersion)) ||
|
||||
!versionChangeAcknowledged ||
|
||||
versionUpdateLoading
|
||||
!versionChangeAcknowledged
|
||||
}
|
||||
loading={versionUpdateLoading}
|
||||
onClick={handleVersionUpdate}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,44 +23,26 @@ export default function SettingsGeneralUpdateRoute() {
|
|||
const { send } = useJsonRpc();
|
||||
|
||||
const downgrade = useMemo(() => searchParams.get("downgrade") === "true", [searchParams]);
|
||||
const customAppVersion = useMemo(() => searchParams.get("app") || "", [searchParams]);
|
||||
const customSystemVersion = useMemo(() => searchParams.get("system") || "", [searchParams]);
|
||||
const updateComponents = useMemo(() => searchParams.get("components") || "", [searchParams]);
|
||||
const resetConfig = useMemo(() => searchParams.get("resetConfig") === "true", [searchParams]);
|
||||
|
||||
const onClose = useCallback(async () => {
|
||||
navigate(".."); // back to the devices.$id.settings page
|
||||
// Add 1s delay between navigation and calling reload() to prevent reload from interrupting the navigation.
|
||||
await sleep(1000);
|
||||
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
|
||||
}, [navigate]);
|
||||
|
||||
const onConfirmUpdate = useCallback(() => {
|
||||
send("tryUpdate", {});
|
||||
setModalView("updating");
|
||||
}, [send, setModalView]);
|
||||
|
||||
const onConfirmDowngrade = useCallback(() => {
|
||||
const components = [];
|
||||
if (customSystemVersion) {
|
||||
components.push("system");
|
||||
}
|
||||
if (customAppVersion) {
|
||||
components.push("app");
|
||||
}
|
||||
|
||||
const onConfirmDowngrade = useCallback((system?: string, app?: string) => {
|
||||
send("tryUpdateComponents", {
|
||||
params: {
|
||||
components: components.join(","),
|
||||
app: customAppVersion,
|
||||
system: customSystemVersion,
|
||||
components: {
|
||||
system, app,
|
||||
components: updateComponents
|
||||
},
|
||||
includePreRelease: false,
|
||||
resetConfig,
|
||||
}, (resp) => {
|
||||
if ("error" in resp) return;
|
||||
setModalView("updating");
|
||||
includePreRelease: true,
|
||||
checkOnly: false,
|
||||
resetConfig: resetConfig,
|
||||
});
|
||||
}, [send, setModalView, customAppVersion, customSystemVersion, resetConfig]);
|
||||
setModalView("updating");
|
||||
}, [send, setModalView, updateComponents, resetConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (otaState.updating) {
|
||||
|
|
@ -75,12 +57,10 @@ export default function SettingsGeneralUpdateRoute() {
|
|||
}, [otaState.error, otaState.updating, setModalView, updateSuccess]);
|
||||
|
||||
return <Dialog
|
||||
onClose={onClose}
|
||||
onConfirmUpdate={onConfirmUpdate}
|
||||
onConfirmDowngrade={onConfirmDowngrade}
|
||||
downgrade={downgrade}
|
||||
customAppVersion={customAppVersion}
|
||||
customSystemVersion={customSystemVersion}
|
||||
onClose={() => navigate("..")}
|
||||
onConfirmUpdate={onConfirmUpdate}
|
||||
onConfirmDowngrade={onConfirmDowngrade}
|
||||
downgrade={downgrade}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
@ -89,15 +69,11 @@ export function Dialog({
|
|||
onConfirmUpdate,
|
||||
onConfirmDowngrade,
|
||||
downgrade,
|
||||
customAppVersion,
|
||||
customSystemVersion,
|
||||
}: Readonly<{
|
||||
downgrade: boolean;
|
||||
onClose: () => void;
|
||||
onConfirmUpdate: () => void;
|
||||
onConfirmDowngrade: () => void;
|
||||
customAppVersion?: string;
|
||||
customSystemVersion?: string;
|
||||
}>) {
|
||||
const { navigateTo } = useDeviceUiNavigation();
|
||||
|
||||
|
|
@ -109,7 +85,8 @@ export function Dialog({
|
|||
(versionInfo: SystemVersionInfo) => {
|
||||
const hasUpdate =
|
||||
versionInfo?.systemUpdateAvailable || versionInfo?.appUpdateAvailable;
|
||||
const hasDowngrade = customSystemVersion !== undefined || customAppVersion !== undefined;
|
||||
const hasDowngrade =
|
||||
versionInfo?.systemDowngradeAvailable || versionInfo?.appDowngradeAvailable;
|
||||
|
||||
setVersionInfo(versionInfo);
|
||||
|
||||
|
|
@ -121,7 +98,7 @@ export function Dialog({
|
|||
setModalView("upToDate");
|
||||
}
|
||||
},
|
||||
[setModalView, downgrade, customAppVersion, customSystemVersion],
|
||||
[setModalView, downgrade],
|
||||
);
|
||||
|
||||
const onCancelDowngrade = useCallback(() => {
|
||||
|
|
@ -153,10 +130,9 @@ export function Dialog({
|
|||
)}
|
||||
{modalView === "updateDowngradeAvailable" && (
|
||||
<UpdateDowngradeAvailableState
|
||||
appVersion={customAppVersion}
|
||||
systemVersion={customSystemVersion}
|
||||
onConfirmDowngrade={onConfirmDowngrade}
|
||||
onCancelDowngrade={onCancelDowngrade}
|
||||
versionInfo={versionInfo!}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -472,19 +448,20 @@ function UpdateAvailableState({
|
|||
}
|
||||
|
||||
function UpdateDowngradeAvailableState({
|
||||
appVersion,
|
||||
systemVersion,
|
||||
versionInfo,
|
||||
onConfirmDowngrade,
|
||||
onCancelDowngrade,
|
||||
}: {
|
||||
appVersion?: string;
|
||||
systemVersion?: string;
|
||||
onConfirmDowngrade: () => void;
|
||||
versionInfo: SystemVersionInfo;
|
||||
onConfirmDowngrade: (system?: string, app?: string) => void;
|
||||
onCancelDowngrade: () => void;
|
||||
}) {
|
||||
const confirmDowngrade = useCallback(() => {
|
||||
onConfirmDowngrade();
|
||||
}, [onConfirmDowngrade]);
|
||||
onConfirmDowngrade(
|
||||
versionInfo?.remote?.systemVersion || undefined,
|
||||
versionInfo?.remote?.appVersion || undefined,
|
||||
);
|
||||
}, [versionInfo, onConfirmDowngrade]);
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-start space-y-4 text-left">
|
||||
<div className="text-left">
|
||||
|
|
@ -495,15 +472,15 @@ function UpdateDowngradeAvailableState({
|
|||
{m.general_update_downgrade_available_description()}
|
||||
</p>
|
||||
<p className="mb-4 text-sm text-slate-600 dark:text-slate-300">
|
||||
{systemVersion ? (
|
||||
{versionInfo?.systemDowngradeAvailable ? (
|
||||
<>
|
||||
<span className="font-semibold">{m.general_update_system_type()}</span>: {systemVersion}
|
||||
<span className="font-semibold">{m.general_update_system_type()}</span>: {versionInfo?.remote?.systemVersion}
|
||||
<br />
|
||||
</>
|
||||
) : null}
|
||||
{appVersion ? (
|
||||
{versionInfo?.appDowngradeAvailable ? (
|
||||
<>
|
||||
<span className="font-semibold">{m.general_update_application_type()}</span>: {appVersion}
|
||||
<span className="font-semibold">{m.general_update_application_type()}</span>: {versionInfo?.remote?.appVersion}
|
||||
</>
|
||||
) : null}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -220,9 +220,7 @@ export interface SystemVersionInfo {
|
|||
local: VersionInfo;
|
||||
remote?: VersionInfo;
|
||||
systemUpdateAvailable: boolean;
|
||||
systemDowngradeAvailable: boolean;
|
||||
appUpdateAvailable: boolean;
|
||||
appDowngradeAvailable: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
|
|
@ -244,21 +242,3 @@ export async function getLocalVersion() {
|
|||
if (response.error) throw response.error;
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export interface updateParams {
|
||||
app?: string;
|
||||
system?: string;
|
||||
components?: string;
|
||||
}
|
||||
|
||||
export async function checkUpdateComponents(params: updateParams, includePreRelease: boolean) {
|
||||
const response = await callJsonRpc<SystemVersionInfo>({
|
||||
method: "checkUpdateComponents",
|
||||
params: {
|
||||
params,
|
||||
includePreRelease,
|
||||
},
|
||||
});
|
||||
if (response.error) throw response.error;
|
||||
return response.result;
|
||||
}
|
||||
4
web.go
4
web.go
|
|
@ -814,7 +814,7 @@ func handleSendWOLMagicPacket(c *gin.Context) {
|
|||
inputMacAddr := c.Param("mac-addr")
|
||||
macAddr, err := net.ParseMAC(inputMacAddr)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Str("inputMacAddr", inputMacAddr).Msg("Invalid MAC address provided")
|
||||
logger.Warn().Err(err).Str("sendWol", inputMacAddr).Msg("Invalid mac address provided")
|
||||
c.String(http.StatusBadRequest, "Invalid mac address provided")
|
||||
return
|
||||
}
|
||||
|
|
@ -822,7 +822,7 @@ func handleSendWOLMagicPacket(c *gin.Context) {
|
|||
macAddrString := macAddr.String()
|
||||
err = rpcSendWOLMagicPacket(macAddrString)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Str("macAddrString", macAddrString).Msg("Failed to send WOL magic packet")
|
||||
logger.Warn().Err(err).Str("sendWOL", macAddrString).Msg("Failed to send WOL magic packet")
|
||||
c.String(http.StatusInternalServerError, "Failed to send WOL to %s: %v", macAddrString, err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue