mirror of https://github.com/jetkvm/kvm.git
Compare commits
2 Commits
8df0a49755
...
ca1c36b84e
| Author | SHA1 | Date |
|---|---|---|
|
|
ca1c36b84e | |
|
|
d3b0f1bebc |
32
hw.go
32
hw.go
|
|
@ -3,6 +3,7 @@ package kvm
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -36,6 +37,37 @@ func readOtpEntropy() ([]byte, error) { //nolint:unused
|
|||
return content[0x17:0x1C], nil
|
||||
}
|
||||
|
||||
func hwReboot(force bool, postRebootAction *PostRebootAction, delay time.Duration) error {
|
||||
logger.Info().Msgf("Reboot requested, rebooting in %d seconds...", delay)
|
||||
|
||||
writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
|
||||
time.Sleep(1 * time.Second) // Wait for the JSONRPCEvent to be sent
|
||||
|
||||
nativeInstance.SwitchToScreenIfDifferent("rebooting_screen")
|
||||
time.Sleep(delay - (1 * time.Second)) // wait requested extra settle time
|
||||
|
||||
args := []string{}
|
||||
if force {
|
||||
args = append(args, "-f")
|
||||
}
|
||||
|
||||
cmd := exec.Command("reboot", args...)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to reboot")
|
||||
switchToMainScreen()
|
||||
return fmt.Errorf("failed to reboot: %w", err)
|
||||
}
|
||||
|
||||
// If the reboot command is successful, exit the program after 5 seconds
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var deviceID string
|
||||
var deviceIDOnce sync.Once
|
||||
|
||||
|
|
|
|||
30
jsonrpc.go
30
jsonrpc.go
|
|
@ -173,34 +173,8 @@ func rpcGetDeviceID() (string, error) {
|
|||
}
|
||||
|
||||
func rpcReboot(force bool) error {
|
||||
logger.Info().Msg("Got reboot request from JSONRPC, rebooting...")
|
||||
|
||||
writeJSONRPCEvent("willReboot", nil, currentSession)
|
||||
|
||||
// Wait for the JSONRPCEvent to be sent
|
||||
time.Sleep(1 * time.Second)
|
||||
nativeInstance.SwitchToScreenIfDifferent("rebooting_screen")
|
||||
|
||||
args := []string{}
|
||||
if force {
|
||||
args = append(args, "-f")
|
||||
}
|
||||
|
||||
cmd := exec.Command("reboot", args...)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to reboot")
|
||||
switchToMainScreen()
|
||||
return fmt.Errorf("failed to reboot: %w", err)
|
||||
}
|
||||
|
||||
// If the reboot command is successful, exit the program after 5 seconds
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
return nil
|
||||
logger.Info().Msg("Got reboot request via RPC")
|
||||
return hwReboot(force, nil, 0)
|
||||
}
|
||||
|
||||
var streamFactor = 1.0
|
||||
|
|
|
|||
|
|
@ -37,14 +37,17 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
|
|||
nativeLogger.Trace().Str("event", event).Msg("rpc event received")
|
||||
switch event {
|
||||
case "resetConfig":
|
||||
nativeLogger.Info().Msg("Reset configuration request via native rpc event")
|
||||
err := rpcResetConfig()
|
||||
if err != nil {
|
||||
nativeLogger.Warn().Err(err).Msg("error resetting config")
|
||||
}
|
||||
_ = rpcReboot(true)
|
||||
case "reboot":
|
||||
nativeLogger.Info().Msg("Reboot request via native rpc event")
|
||||
_ = rpcReboot(true)
|
||||
case "toggleDHCPClient":
|
||||
nativeLogger.Info().Msg("Toggle DHCP request via native rpc event")
|
||||
_ = rpcToggleDHCPClient()
|
||||
default:
|
||||
nativeLogger.Warn().Str("event", event).Msg("unknown rpc event received")
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
|
|||
|
||||
oldIPv4Mode := oldConfig.IPv4Mode.String
|
||||
newIPv4Mode := newConfig.IPv4Mode.String
|
||||
|
||||
// IPv4 mode change requires reboot
|
||||
if newIPv4Mode != oldIPv4Mode {
|
||||
rebootRequired = true
|
||||
|
|
@ -284,7 +285,8 @@ func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, er
|
|||
}
|
||||
|
||||
if rebootRequired {
|
||||
if err := rpcReboot(false); err != nil {
|
||||
l.Info().Msg("Rebooting due to network changes")
|
||||
if err := hwReboot(true, postRebootAction, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
ota.go
24
ota.go
|
|
@ -487,25 +487,15 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
}
|
||||
|
||||
if rebootNeeded {
|
||||
scopedLogger.Info().Msg("System Rebooting in 10s")
|
||||
scopedLogger.Info().Msg("System Rebooting due to OTA update")
|
||||
|
||||
// TODO: Future enhancement - send postRebootAction to redirect to release notes
|
||||
// Example:
|
||||
// postRebootAction := &PostRebootAction{
|
||||
// HealthCheck: "[..]/device/status",
|
||||
// RedirectUrl: "[..]/settings/general/update?version=X.Y.Z",
|
||||
// }
|
||||
// writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
|
||||
postRebootAction := &PostRebootAction{
|
||||
HealthCheck: "/device/status",
|
||||
RedirectUrl: "/settings/general/update?version=" + remote.SystemVersion,
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
cmd := exec.Command("reboot")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
otaState.Error = fmt.Sprintf("Failed to start reboot: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Failed to start reboot")
|
||||
return fmt.Errorf("failed to start reboot: %w", err)
|
||||
} else {
|
||||
os.Exit(0)
|
||||
if err := hwReboot(true, postRebootAction, 10*time.Second); err != nil {
|
||||
return fmt.Errorf("error requesting reboot: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Downloader {update_type} opdatering…",
|
||||
"general_update_status_fetching": "Henter opdateringsoplysninger…",
|
||||
"general_update_status_installing": "Installation af {update_type} opdatering…",
|
||||
"general_update_status_progress": "{part} fremskridt",
|
||||
"general_update_status_verifying": "Bekræfter {update_type} opdatering…",
|
||||
"general_update_system_type": "System",
|
||||
"general_update_system_update_title": "Linux-systemopdatering",
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
"access_no_device_id": "Keine Geräte-ID verfügbar",
|
||||
"access_private_key_description": "Aus Sicherheitsgründen wird es nach dem Speichern nicht angezeigt.",
|
||||
"access_private_key_label": "Privater Schlüssel",
|
||||
"access_provider_custom": "Brauch",
|
||||
"access_provider_custom": "Benutzerdefiniert",
|
||||
"access_provider_jetkvm": "JetKVM Cloud",
|
||||
"access_remote_description": "Verwalten Sie den Modus des Fernzugriffs auf das Gerät",
|
||||
"access_security_encryption": "Ende-zu-Ende-Verschlüsselung mit WebRTC (DTLS und SRTP)",
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"access_title": "Zugang",
|
||||
"access_tls_certificate_description": "Fügen Sie unten Ihr TLS-Zertifikat ein. Geben Sie bei Zertifikatsketten die gesamte Kette an (Blatt-, Zwischen- und Stammzertifikate).",
|
||||
"access_tls_certificate_title": "TLS-Zertifikat",
|
||||
"access_tls_custom": "Brauch",
|
||||
"access_tls_custom": "Benutzerdefiniert",
|
||||
"access_tls_disabled": "Deaktiviert",
|
||||
"access_tls_self_signed": "Selbstsigniert",
|
||||
"access_tls_updated": "TLS-Einstellungen erfolgreich aktualisiert",
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
"appearance_page_description": "Passen Sie das Erscheinungsbild Ihrer JetKVM-Schnittstelle an",
|
||||
"appearance_theme": "Thema",
|
||||
"appearance_theme_dark": "Dunkel",
|
||||
"appearance_theme_light": "Licht",
|
||||
"appearance_theme_light": "Hell",
|
||||
"appearance_theme_system": "System",
|
||||
"appearance_title": "Aussehen",
|
||||
"attach": "Befestigen",
|
||||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Das Update {update_type} wird heruntergeladen…",
|
||||
"general_update_status_fetching": "Update-Informationen werden abgerufen …",
|
||||
"general_update_status_installing": "Das Update {update_type} wird installiert…",
|
||||
"general_update_status_progress": "{part} Fortschritt",
|
||||
"general_update_status_verifying": "Überprüfung des Updates {update_type} …",
|
||||
"general_update_system_type": "System",
|
||||
"general_update_system_update_title": "Linux-Systemupdate",
|
||||
|
|
@ -412,7 +413,7 @@
|
|||
"locale_de": "Deutsch",
|
||||
"locale_en": "Englisch",
|
||||
"locale_es": "Spanisch",
|
||||
"locale_fr": "Deutsch",
|
||||
"locale_fr": "Französisch",
|
||||
"locale_it": "Italienisch",
|
||||
"locale_nb": "Norwegisch (bokmål)",
|
||||
"locale_sv": "Schwedisch",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Downloading {update_type} update…",
|
||||
"general_update_status_fetching": "Fetching update information…",
|
||||
"general_update_status_installing": "Installing {update_type} update…",
|
||||
"general_update_status_progress": "{part} progress",
|
||||
"general_update_status_verifying": "Verifying {update_type} update…",
|
||||
"general_update_system_type": "System",
|
||||
"general_update_system_update_title": "Linux System Update",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Descargando actualización {update_type} …",
|
||||
"general_update_status_fetching": "Obteniendo información de actualización…",
|
||||
"general_update_status_installing": "Instalando {update_type} actualización…",
|
||||
"general_update_status_progress": "{part} progreso",
|
||||
"general_update_status_verifying": "Verificando la actualización {update_type} …",
|
||||
"general_update_system_type": "Sistema",
|
||||
"general_update_system_update_title": "Actualización del sistema Linux",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Téléchargement de la mise à jour {update_type} …",
|
||||
"general_update_status_fetching": "Récupération des informations de mise à jour…",
|
||||
"general_update_status_installing": "Installation de la mise à jour {update_type} …",
|
||||
"general_update_status_progress": "{part} progression",
|
||||
"general_update_status_verifying": "Vérification de la mise à jour de {update_type} …",
|
||||
"general_update_system_type": "Système",
|
||||
"general_update_system_update_title": "Mise à jour du système Linux",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Scaricamento dell'aggiornamento {update_type} …",
|
||||
"general_update_status_fetching": "Recupero delle informazioni di aggiornamento in corso…",
|
||||
"general_update_status_installing": "Installazione dell'aggiornamento {update_type} …",
|
||||
"general_update_status_progress": "{part} progresso",
|
||||
"general_update_status_verifying": "Verifica dell'aggiornamento {update_type} …",
|
||||
"general_update_system_type": "Sistema",
|
||||
"general_update_system_update_title": "Aggiornamento del sistema Linux",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Laster ned {update_type} oppdatering…",
|
||||
"general_update_status_fetching": "Henter oppdateringsinformasjon …",
|
||||
"general_update_status_installing": "Installerer {update_type} oppdatering…",
|
||||
"general_update_status_progress": "{part} fremgang",
|
||||
"general_update_status_verifying": "Verifiserer {update_type} oppdatering…",
|
||||
"general_update_system_type": "System",
|
||||
"general_update_system_update_title": "Linux-systemoppdatering",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "Laddar ner {update_type} uppdatering…",
|
||||
"general_update_status_fetching": "Hämtar uppdateringsinformation…",
|
||||
"general_update_status_installing": "Installerar {update_type} uppdatering…",
|
||||
"general_update_status_progress": "{part} framsteg",
|
||||
"general_update_status_verifying": "Verifierar {update_type} uppdatering…",
|
||||
"general_update_system_type": "System",
|
||||
"general_update_system_update_title": "Linux-systemuppdatering",
|
||||
|
|
|
|||
|
|
@ -271,6 +271,7 @@
|
|||
"general_update_status_downloading": "正在下载{update_type}更新…",
|
||||
"general_update_status_fetching": "正在获取更新信息...",
|
||||
"general_update_status_installing": "正在安装{update_type}更新...",
|
||||
"general_update_status_progress": "{part}进度",
|
||||
"general_update_status_verifying": "验证{update_type}更新…",
|
||||
"general_update_system_type": "系统",
|
||||
"general_update_system_update_title": "Linux 系统更新",
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@
|
|||
"@types/react-dom": "^19.2.2",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/validator": "^13.15.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
||||
"@typescript-eslint/parser": "^8.46.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.38.0",
|
||||
|
|
@ -2387,7 +2387,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
|
|
@ -2397,7 +2396,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz",
|
||||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
|
|
@ -2423,17 +2421,17 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz",
|
||||
"integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
|
||||
"integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.46.1",
|
||||
"@typescript-eslint/type-utils": "8.46.1",
|
||||
"@typescript-eslint/utils": "8.46.1",
|
||||
"@typescript-eslint/visitor-keys": "8.46.1",
|
||||
"@typescript-eslint/scope-manager": "8.46.2",
|
||||
"@typescript-eslint/type-utils": "8.46.2",
|
||||
"@typescript-eslint/utils": "8.46.2",
|
||||
"@typescript-eslint/visitor-keys": "8.46.2",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
|
|
@ -2447,7 +2445,7 @@
|
|||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.46.1",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
|
|
@ -2463,17 +2461,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz",
|
||||
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz",
|
||||
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/typescript-estree": "8.46.1",
|
||||
"@typescript-eslint/visitor-keys": "8.46.1",
|
||||
"@typescript-eslint/scope-manager": "8.46.2",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"@typescript-eslint/typescript-estree": "8.46.2",
|
||||
"@typescript-eslint/visitor-keys": "8.46.2",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -2489,14 +2486,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz",
|
||||
"integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz",
|
||||
"integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.46.1",
|
||||
"@typescript-eslint/types": "^8.46.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.46.2",
|
||||
"@typescript-eslint/types": "^8.46.2",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -2511,14 +2508,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz",
|
||||
"integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz",
|
||||
"integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/visitor-keys": "8.46.1"
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"@typescript-eslint/visitor-keys": "8.46.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
|
@ -2529,9 +2526,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz",
|
||||
"integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz",
|
||||
"integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -2546,15 +2543,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz",
|
||||
"integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz",
|
||||
"integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/typescript-estree": "8.46.1",
|
||||
"@typescript-eslint/utils": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"@typescript-eslint/typescript-estree": "8.46.2",
|
||||
"@typescript-eslint/utils": "8.46.2",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
|
|
@ -2571,9 +2568,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz",
|
||||
"integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz",
|
||||
"integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -2585,16 +2582,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz",
|
||||
"integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz",
|
||||
"integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.46.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/visitor-keys": "8.46.1",
|
||||
"@typescript-eslint/project-service": "8.46.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.46.2",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"@typescript-eslint/visitor-keys": "8.46.2",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
|
|
@ -2640,16 +2637,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz",
|
||||
"integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz",
|
||||
"integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/typescript-estree": "8.46.1"
|
||||
"@typescript-eslint/scope-manager": "8.46.2",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"@typescript-eslint/typescript-estree": "8.46.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
|
@ -2664,13 +2661,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.46.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz",
|
||||
"integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==",
|
||||
"version": "8.46.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz",
|
||||
"integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.46.1",
|
||||
"@typescript-eslint/types": "8.46.2",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -2775,15 +2772,13 @@
|
|||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -3119,7 +3114,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
|
|
@ -3349,8 +3343,7 @@
|
|||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cva": {
|
||||
"version": "1.0.0-beta.4",
|
||||
|
|
@ -3946,7 +3939,6 @@
|
|||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -4007,7 +3999,6 @@
|
|||
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
|
|
@ -4081,7 +4072,6 @@
|
|||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
|
|
@ -5504,7 +5494,6 @@
|
|||
"integrity": "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
|
|
@ -6227,7 +6216,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
|
|
@ -6273,7 +6261,6 @@
|
|||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
|
@ -6430,7 +6417,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -6453,7 +6439,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
|
|
@ -6515,7 +6500,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
|
|
@ -6612,8 +6596,7 @@
|
|||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
|
|
@ -6673,12 +6656,12 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
|
|
@ -7203,8 +7186,7 @@
|
|||
"version": "4.1.15",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.15.tgz",
|
||||
"integrity": "sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
|
|
@ -7264,7 +7246,6 @@
|
|||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -7441,7 +7422,6 @@
|
|||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -7625,7 +7605,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz",
|
||||
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
|
@ -7737,7 +7716,6 @@
|
|||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
|
@ -7886,7 +7864,6 @@
|
|||
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
"i18n:validate": "inlang validate --project ./localization/jetKVM.UI.inlang",
|
||||
"i18n:compile": "paraglide-js compile --project ./localization/jetKVM.UI.inlang --outdir ./localization/paraglide",
|
||||
"i18n:machine-translate": "inlang machine translate --project ./localization/jetKVM.UI.inlang",
|
||||
"i18n:audit": "npm run i18n:find-dupes && npm i18n:find-excess && npm run i18n:find-unused",
|
||||
"i18n:audit": "npm run i18n:find-dupes && npm run i18n:find-excess && npm run i18n:find-unused",
|
||||
"i18n:find-excess": "python3 ./tools/find_excess_messages.py",
|
||||
"i18n:find-unused": "python3 ./tools/find_unused_messages.py",
|
||||
"i18n:find-dupes": "python3 ./tools/find_duplicate_translations.py"
|
||||
|
|
@ -77,8 +77,8 @@
|
|||
"@types/react-dom": "^19.2.2",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/validator": "^13.15.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
||||
"@typescript-eslint/parser": "^8.46.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||
"@typescript-eslint/parser": "^8.46.2",
|
||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.38.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid"; // adjust import if you use a different icon set
|
||||
|
||||
import LoadingSpinner from "@components/LoadingSpinner"; // adjust import path if needed
|
||||
import { m } from "@localizations/messages.js";
|
||||
|
||||
export interface UpdatePart {
|
||||
pending: boolean;
|
||||
status: string;
|
||||
progress: number;
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
export default function UpdatingStatusCard({
|
||||
label,
|
||||
part,
|
||||
}: {
|
||||
label: string;
|
||||
part: UpdatePart;
|
||||
}) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-black dark:text-white">{label}</p>
|
||||
{part.progress < 100 ? (
|
||||
<LoadingSpinner className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="h-2.5 w-full overflow-hidden rounded-full bg-slate-300 dark:bg-slate-600"
|
||||
role="progressbar"
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
aria-valuenow={Math.round(part.progress)}
|
||||
aria-label={m.general_update_status_progress({part: label})}
|
||||
>
|
||||
<div
|
||||
className="h-2.5 rounded-full bg-blue-700 transition-all duration-500 ease-linear dark:bg-blue-500"
|
||||
style={{ width: `${part.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||
<span>{part.status}</span>
|
||||
{part.progress < 100 ? <span>{`${Math.round(part.progress)}%`}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -476,6 +476,7 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
|
|||
// Device is available, redirect to the specified URL
|
||||
console.log('Device is available, redirecting to:', postRebootAction.redirectUrl);
|
||||
window.location.href = postRebootAction.redirectUrl;
|
||||
window.location.reload(true)
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore errors - they're expected while device is rebooting
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
|||
try {
|
||||
data = message.marshal();
|
||||
} catch (e) {
|
||||
console.error("Failed to send HID RPC message", e);
|
||||
console.error("Failed to marshal HID RPC message", e);
|
||||
}
|
||||
if (!data) return;
|
||||
|
||||
|
|
@ -223,13 +223,19 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
|||
setRpcHidProtocolVersion(null);
|
||||
};
|
||||
|
||||
const errorHandler = (e: Event) => {
|
||||
console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${e}`)
|
||||
};
|
||||
|
||||
rpcHidChannel.addEventListener("message", messageHandler);
|
||||
rpcHidChannel.addEventListener("close", closeHandler);
|
||||
rpcHidChannel.addEventListener("error", errorHandler);
|
||||
rpcHidChannel.addEventListener("open", openHandler);
|
||||
|
||||
return () => {
|
||||
rpcHidChannel.removeEventListener("message", messageHandler);
|
||||
rpcHidChannel.removeEventListener("close", closeHandler);
|
||||
rpcHidChannel.removeEventListener("error", errorHandler);
|
||||
rpcHidChannel.removeEventListener("open", openHandler);
|
||||
};
|
||||
}, [
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ export default function SettingsGeneralRoute() {
|
|||
const localeOptions = useMemo(() => {
|
||||
return ["", ...locales]
|
||||
.map((code) => {
|
||||
const [localizedName, nativeName] = map_locale_code_to_name(code);
|
||||
const [localizedName, nativeName] = map_locale_code_to_name(currentLocale, code);
|
||||
// don't repeat the name if it's the same in both locales (or blank)
|
||||
const label = nativeName && nativeName !== localizedName ? `${localizedName} - ${nativeName}` : localizedName;
|
||||
return { value: code, label: label }
|
||||
});
|
||||
}, []);
|
||||
}, [currentLocale]);
|
||||
|
||||
const handleLocaleChange = (newLocale: string) => {
|
||||
if (newLocale === currentLocale) return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { CheckCircleIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
import { useJsonRpc } from "@hooks/useJsonRpc";
|
||||
import { UpdateState, useUpdateStore } from "@hooks/stores";
|
||||
|
|
@ -9,6 +8,7 @@ import { SystemVersionInfo, useVersion } from "@hooks/useVersion";
|
|||
import { Button } from "@components/Button";
|
||||
import Card from "@components/Card";
|
||||
import LoadingSpinner from "@components/LoadingSpinner";
|
||||
import UpdatingStatusCard, { type UpdatePart} from "@components/UpdatingStatusCard";
|
||||
import { m } from "@localizations/messages.js";
|
||||
|
||||
export default function SettingsGeneralUpdateRoute() {
|
||||
|
|
@ -121,6 +121,7 @@ function LoadingState({
|
|||
const abortControllerRef = useRef<AbortController | null>(null);
|
||||
|
||||
const { getVersionInfo } = useVersion();
|
||||
const { setModalView } = useUpdateStore();
|
||||
|
||||
const progressBarRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
|
|
@ -128,6 +129,7 @@ function LoadingState({
|
|||
const signal = abortControllerRef.current.signal;
|
||||
|
||||
const animationTimer = setTimeout(() => {
|
||||
// we start the progress bar animation after a tiny delay to avoid react warnings
|
||||
setProgressWidth("100%");
|
||||
}, 0);
|
||||
|
||||
|
|
@ -144,6 +146,7 @@ function LoadingState({
|
|||
.catch(error => {
|
||||
if (!signal.aborted) {
|
||||
console.error("LoadingState: Error fetching version info", error);
|
||||
setModalView("error");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -151,7 +154,7 @@ function LoadingState({
|
|||
clearTimeout(animationTimer);
|
||||
abortControllerRef.current?.abort();
|
||||
};
|
||||
}, [getVersionInfo, onFinished]);
|
||||
}, [getVersionInfo, onFinished, setModalView]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-start space-y-4 text-left">
|
||||
|
|
@ -186,77 +189,94 @@ function UpdatingDeviceState({
|
|||
otaState: UpdateState["otaState"];
|
||||
onMinimizeUpgradeDialog: () => void;
|
||||
}) {
|
||||
const calculateOverallProgress = (type: "system" | "app") => {
|
||||
const downloadProgress = Math.round((otaState[`${type}DownloadProgress`] || 0) * 100);
|
||||
const updateProgress = Math.round((otaState[`${type}UpdateProgress`] || 0) * 100);
|
||||
const verificationProgress = Math.round(
|
||||
(otaState[`${type}VerificationProgress`] || 0) * 100,
|
||||
);
|
||||
interface ProgressSummary {
|
||||
system: UpdatePart;
|
||||
app: UpdatePart;
|
||||
areAllUpdatesComplete: boolean;
|
||||
};
|
||||
|
||||
if (!downloadProgress && !updateProgress && !verificationProgress) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`For ${type}:\n` +
|
||||
` Download Progress: ${downloadProgress}% (${otaState[`${type}DownloadProgress`]})\n` +
|
||||
` Update Progress: ${updateProgress}% (${otaState[`${type}UpdateProgress`]})\n` +
|
||||
` Verification Progress: ${verificationProgress}% (${otaState[`${type}VerificationProgress`]})`,
|
||||
);
|
||||
|
||||
if (type === "app") {
|
||||
// App: 65% download, 34% verification, 1% update(There is no "real" update for the app)
|
||||
return Math.min(
|
||||
downloadProgress * 0.55 + verificationProgress * 0.54 + updateProgress * 0.01,
|
||||
100,
|
||||
const progress = useMemo<ProgressSummary>(() => {
|
||||
const calculateOverallProgress = (type: "system" | "app") => {
|
||||
const downloadProgress = Math.round((otaState[`${type}DownloadProgress`] || 0) * 100);
|
||||
const updateProgress = Math.round((otaState[`${type}UpdateProgress`] || 0) * 100);
|
||||
const verificationProgress = Math.round(
|
||||
(otaState[`${type}VerificationProgress`] || 0) * 100,
|
||||
);
|
||||
|
||||
if (!downloadProgress && !updateProgress && !verificationProgress) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (type === "app") {
|
||||
// App: 55% download, 54% verification, 1% update(There is no "real" update for the app)
|
||||
return Math.round(Math.min(
|
||||
downloadProgress * 0.55 + verificationProgress * 0.54 + updateProgress * 0.01,
|
||||
100,
|
||||
));
|
||||
} else {
|
||||
// System: 10% download, 10% verification, 80% update
|
||||
return Math.round(Math.min(
|
||||
downloadProgress * 0.1 + verificationProgress * 0.1 + updateProgress * 0.8,
|
||||
100,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
const getUpdateStatus = (type: "system" | "app") => {
|
||||
const downloadFinishedAt = otaState[`${type}DownloadFinishedAt`];
|
||||
const verifiedAt = otaState[`${type}VerifiedAt`];
|
||||
const updatedAt = otaState[`${type}UpdatedAt`];
|
||||
|
||||
const update_type = () => (type === "system" ? m.general_update_system_type() : m.general_update_application_type());
|
||||
|
||||
if (!otaState.metadataFetchedAt) {
|
||||
return m.general_update_status_fetching();
|
||||
} else if (!downloadFinishedAt) {
|
||||
return m.general_update_status_downloading({ update_type: update_type() });
|
||||
} else if (!verifiedAt) {
|
||||
return m.general_update_status_verifying({ update_type: update_type() });
|
||||
} else if (!updatedAt) {
|
||||
return m.general_update_status_installing({ update_type: update_type() });
|
||||
} else {
|
||||
return m.general_update_status_awaiting_reboot();
|
||||
}
|
||||
};
|
||||
|
||||
const isUpdateComplete = (type: "system" | "app") => {
|
||||
return !!otaState[`${type}UpdatedAt`];
|
||||
};
|
||||
|
||||
const systemUpdatePending = otaState.systemUpdatePending
|
||||
const systemUpdateComplete = isUpdateComplete("system");
|
||||
|
||||
const appUpdatePending = otaState.appUpdatePending
|
||||
const appUpdateComplete = isUpdateComplete("app");
|
||||
|
||||
let areAllUpdatesComplete: boolean;
|
||||
if (!systemUpdatePending && !appUpdatePending) {
|
||||
areAllUpdatesComplete = false;
|
||||
} else if (systemUpdatePending && appUpdatePending) {
|
||||
areAllUpdatesComplete = systemUpdateComplete && appUpdateComplete;
|
||||
} else {
|
||||
// System: 10% download, 90% update
|
||||
return Math.min(
|
||||
downloadProgress * 0.1 + verificationProgress * 0.1 + updateProgress * 0.8,
|
||||
100,
|
||||
);
|
||||
areAllUpdatesComplete = systemUpdatePending ? systemUpdateComplete : appUpdateComplete;
|
||||
}
|
||||
};
|
||||
|
||||
const getUpdateStatus = (type: "system" | "app") => {
|
||||
const downloadFinishedAt = otaState[`${type}DownloadFinishedAt`];
|
||||
const verfiedAt = otaState[`${type}VerifiedAt`];
|
||||
const updatedAt = otaState[`${type}UpdatedAt`];
|
||||
|
||||
const update_type = () => (type === "system" ? m.general_update_system_type() : m.general_update_application_type());
|
||||
|
||||
if (!otaState.metadataFetchedAt) {
|
||||
return m.general_update_status_fetching();
|
||||
} else if (!downloadFinishedAt) {
|
||||
return m.general_update_status_downloading({ update_type });
|
||||
} else if (!verfiedAt) {
|
||||
return m.general_update_status_verifying({ update_type });
|
||||
} else if (!updatedAt) {
|
||||
return m.general_update_status_installing({ update_type });
|
||||
} else {
|
||||
return m.general_update_status_awaiting_reboot();
|
||||
}
|
||||
};
|
||||
|
||||
const isUpdateComplete = (type: "system" | "app") => {
|
||||
return !!otaState[`${type}UpdatedAt`];
|
||||
};
|
||||
|
||||
const areAllUpdatesComplete = () => {
|
||||
if (otaState.systemUpdatePending && otaState.appUpdatePending) {
|
||||
return isUpdateComplete("system") && isUpdateComplete("app");
|
||||
}
|
||||
return (
|
||||
(otaState.systemUpdatePending && isUpdateComplete("system")) ||
|
||||
(otaState.appUpdatePending && isUpdateComplete("app"))
|
||||
);
|
||||
};
|
||||
|
||||
const systemOverallProgress = calculateOverallProgress("system");
|
||||
const systemUpdateStatus = getUpdateStatus("system");
|
||||
const appOverallProgress = calculateOverallProgress("app");
|
||||
const appUpdateStatus = getUpdateStatus("app");
|
||||
return {
|
||||
system: {
|
||||
pending: systemUpdatePending,
|
||||
status: getUpdateStatus("system"),
|
||||
progress: calculateOverallProgress("system"),
|
||||
complete: systemUpdateComplete,
|
||||
},
|
||||
app: {
|
||||
pending: appUpdatePending,
|
||||
status: getUpdateStatus("app"),
|
||||
progress: calculateOverallProgress("app"),
|
||||
complete: appUpdateComplete,
|
||||
},
|
||||
areAllUpdatesComplete,
|
||||
};
|
||||
}, [otaState]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start justify-start space-y-4 text-left">
|
||||
|
|
@ -270,7 +290,7 @@ function UpdatingDeviceState({
|
|||
</p>
|
||||
</div>
|
||||
<Card className="space-y-4 p-4">
|
||||
{areAllUpdatesComplete() ? (
|
||||
{progress.areAllUpdatesComplete ? (
|
||||
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
||||
<LoadingSpinner className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||
|
|
@ -281,68 +301,22 @@ function UpdatingDeviceState({
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
{!(otaState.systemUpdatePending || otaState.appUpdatePending) && (
|
||||
{!(progress.system.pending || progress.app.pending) && (
|
||||
<div className="my-2 flex flex-col items-center space-y-2 text-center">
|
||||
<LoadingSpinner className="h-6 w-6 text-blue-700 dark:text-blue-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{otaState.systemUpdatePending && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-black dark:text-white">
|
||||
{m.general_update_system_update_title()}
|
||||
</p>
|
||||
{systemOverallProgress < 100 ? (
|
||||
<LoadingSpinner className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="h-2.5 w-full overflow-hidden rounded-full bg-slate-300 dark:bg-slate-600">
|
||||
<div
|
||||
className="h-2.5 rounded-full bg-blue-700 transition-all duration-500 ease-linear dark:bg-blue-500"
|
||||
style={{ width: `${systemOverallProgress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||
<span>{systemUpdateStatus}</span>{" "}
|
||||
{systemOverallProgress < 100
|
||||
? (<span>{`${systemOverallProgress}%`}</span>)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
{progress.system.pending && (
|
||||
<UpdatingStatusCard label={m.general_update_system_update_title()} part={progress.system} />
|
||||
)}
|
||||
{otaState.appUpdatePending && (
|
||||
<>
|
||||
{otaState.systemUpdatePending && (
|
||||
<hr className="dark:border-slate-600" />
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm font-semibold text-black dark:text-white">
|
||||
{m.general_update_app_update_title()}
|
||||
</p>
|
||||
{appOverallProgress < 100 ? (
|
||||
<LoadingSpinner className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
) : (
|
||||
<CheckCircleIcon className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="h-2.5 w-full overflow-hidden rounded-full bg-slate-300 dark:bg-slate-600">
|
||||
<div
|
||||
className="h-2.5 rounded-full bg-blue-700 transition-all duration-500 ease-linear dark:bg-blue-500"
|
||||
style={{ width: `${appOverallProgress}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-slate-600 dark:text-slate-300">
|
||||
<span>{appUpdateStatus}</span>{" "}
|
||||
{appOverallProgress < 100
|
||||
? (<span>{`${appOverallProgress}%`}</span>)
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
{progress.system.pending && progress.app.pending && (
|
||||
<hr className="dark:border-slate-600" />
|
||||
)}
|
||||
|
||||
{progress.app.pending && (
|
||||
<UpdatingStatusCard label={m.general_update_app_update_title()} part={progress.app} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -485,13 +485,15 @@ export default function KvmIdRoute() {
|
|||
|
||||
const rpcDataChannel = pc.createDataChannel("rpc");
|
||||
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
|
||||
rpcDataChannel.onerror = (e: Event) => console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
|
||||
rpcDataChannel.onerror = (ev: Event) => console.error(`Error on DataChannel '${rpcDataChannel.label}': ${ev}`);
|
||||
rpcDataChannel.onopen = () => {
|
||||
setRpcDataChannel(rpcDataChannel);
|
||||
};
|
||||
|
||||
const rpcHidChannel = pc.createDataChannel("hidrpc");
|
||||
rpcHidChannel.binaryType = "arraybuffer";
|
||||
rpcHidChannel.onclose = () => console.log("rpcHidChannel has closed");
|
||||
rpcHidChannel.onerror = (ev: Event) => console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${ev}`);
|
||||
rpcHidChannel.onopen = () => {
|
||||
setRpcHidChannel(rpcHidChannel);
|
||||
};
|
||||
|
|
@ -501,6 +503,8 @@ export default function KvmIdRoute() {
|
|||
maxRetransmits: 0,
|
||||
});
|
||||
rpcHidUnreliableChannel.binaryType = "arraybuffer";
|
||||
rpcHidUnreliableChannel.onclose = () => console.log("rpcHidUnreliableChannel has closed");
|
||||
rpcHidUnreliableChannel.onerror = (ev: Event) => console.error(`Error on rpcHidUnreliableChannel '${rpcHidUnreliableChannel.label}': ${ev}`);
|
||||
rpcHidUnreliableChannel.onopen = () => {
|
||||
setRpcHidUnreliableChannel(rpcHidUnreliableChannel);
|
||||
};
|
||||
|
|
@ -510,6 +514,8 @@ export default function KvmIdRoute() {
|
|||
maxRetransmits: 0,
|
||||
});
|
||||
rpcHidUnreliableNonOrderedChannel.binaryType = "arraybuffer";
|
||||
rpcHidUnreliableNonOrderedChannel.onclose = () => console.log("rpcHidUnreliableNonOrderedChannel has closed");
|
||||
rpcHidUnreliableNonOrderedChannel.onerror = (ev: Event) => console.error(`Error on rpcHidUnreliableNonOrderedChannel '${rpcHidUnreliableNonOrderedChannel.label}': ${ev}`);
|
||||
rpcHidUnreliableNonOrderedChannel.onopen = () => {
|
||||
setRpcHidUnreliableNonOrderedChannel(rpcHidUnreliableNonOrderedChannel);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { KeySequence } from "@hooks/stores";
|
||||
import { getLocale } from '@localizations/runtime.js';
|
||||
import { m } from "@localizations/messages.js";
|
||||
import { locales } from '@localizations/runtime.js';
|
||||
|
||||
export const formatters = {
|
||||
date: (date: Date, options?: Intl.DateTimeFormatOptions) =>
|
||||
|
|
@ -256,20 +257,22 @@ export function normalizeSortOrders(macros: KeySequence[]): KeySequence[] {
|
|||
}));
|
||||
};
|
||||
|
||||
export function map_locale_code_to_name(locale: string): [string, string] {
|
||||
type LocaleCode = typeof locales[number];
|
||||
|
||||
export function map_locale_code_to_name(currentLocale: LocaleCode, locale: string): [string, string] {
|
||||
// the first is the name in the current app locale (e.g. Inglese),
|
||||
// the second is the name in the language of the locale itself (e.g. English)
|
||||
switch (locale) {
|
||||
case '': return [m.locale_auto(), ""];
|
||||
case 'en': return [m.locale_en(), m.locale_en({}, { locale })];
|
||||
case 'da': return [m.locale_da(), m.locale_da({}, { locale })];
|
||||
case 'de': return [m.locale_de(), m.locale_de({}, { locale })];
|
||||
case 'es': return [m.locale_es(), m.locale_es({}, { locale })];
|
||||
case 'fr': return [m.locale_fr(), m.locale_fr({}, { locale })];
|
||||
case 'it': return [m.locale_it(), m.locale_it({}, { locale })];
|
||||
case 'nb': return [m.locale_nb(), m.locale_nb({}, { locale })];
|
||||
case 'sv': return [m.locale_sv(), m.locale_sv({}, { locale })];
|
||||
case 'zh': return [m.locale_zh(), m.locale_zh({}, { locale })];
|
||||
case 'en': return [m.locale_en({}, { locale: currentLocale }), m.locale_en({}, { locale })];
|
||||
case 'da': return [m.locale_da({}, { locale: currentLocale }), m.locale_da({}, { locale })];
|
||||
case 'de': return [m.locale_de({}, { locale: currentLocale }), m.locale_de({}, { locale })];
|
||||
case 'es': return [m.locale_es({}, { locale: currentLocale }), m.locale_es({}, { locale })];
|
||||
case 'fr': return [m.locale_fr({}, { locale: currentLocale }), m.locale_fr({}, { locale })];
|
||||
case 'it': return [m.locale_it({}, { locale: currentLocale }), m.locale_it({}, { locale })];
|
||||
case 'nb': return [m.locale_nb({}, { locale: currentLocale }), m.locale_nb({}, { locale })];
|
||||
case 'sv': return [m.locale_sv({}, { locale: currentLocale }), m.locale_sv({}, { locale })];
|
||||
case 'zh': return [m.locale_zh({}, { locale: currentLocale }), m.locale_zh({}, { locale })];
|
||||
default: return [locale, ""];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue