Compare commits

..

3 Commits

Author SHA1 Message Date
Marc Brooks ffb5bb544b
Switch to throwing redirect for welcome or login-local needed
Also cleaned up the loader function error-state returns
2025-10-29 12:09:02 -05:00
Marc Brooks 8e9e3e242f
PR feedback, keep trying to connect even if failed 2025-10-28 11:17:11 -05:00
Marc Brooks 48f5cb3437
Better reporting of and process for OTA updating
Add ability to request a reload to LinkButton and Link
Added exponential backoff to reconnection
Made the number of reconnect attempts settable
Doesn't attempt a reconnection if we intentionally disconnect
Make sure the fire-and-forget for TURN activity doesn't result in unhandled promise rejection.
Fix comment
Added force page reload to the onClose events of update/reboot
Ensure we get the correct UI version.
Fixed comment about the system update progress
Removed duplicate code between main and devices.$id
The isOnDevice and checkAuth can be leveraged from devices.$id.tsx
Extend retries to 2000 (which is 20000 seconds after backing off)
2025-10-27 19:10:14 -05:00
10 changed files with 42 additions and 100 deletions

View File

@ -177,8 +177,7 @@ func getDefaultConfig() Config {
_ = confparser.SetDefaultsAndValidate(c)
return c
}(),
DefaultLogLevel: "INFO",
VideoQualityFactor: 1.0,
DefaultLogLevel: "INFO",
}
}

View File

@ -305,11 +305,11 @@ func wakeDisplay(force bool, reason string) {
displayLogger.Warn().Err(err).Msg("failed to wake display")
}
if config.DisplayDimAfterSec != 0 && dimTicker != nil {
if config.DisplayDimAfterSec != 0 {
dimTicker.Reset(time.Duration(config.DisplayDimAfterSec) * time.Second)
}
if config.DisplayOffAfterSec != 0 && offTicker != nil {
if config.DisplayOffAfterSec != 0 {
offTicker.Reset(time.Duration(config.DisplayOffAfterSec) * time.Second)
}
backlightState = 0

View File

@ -306,7 +306,7 @@ int jetkvm_ui_add_flag(const char *obj_name, const char *flag_name) {
if (obj == NULL) {
return -1;
}
lv_obj_flag_t flag_val = str_to_lv_obj_flag(flag_name);
if (flag_val == 0)
{
@ -368,7 +368,7 @@ void jetkvm_video_stop() {
}
int jetkvm_video_set_quality_factor(float quality_factor) {
if (quality_factor <= 0 || quality_factor > 1) {
if (quality_factor < 0 || quality_factor > 1) {
return -1;
}
video_set_quality_factor(quality_factor);
@ -417,4 +417,4 @@ void jetkvm_crash() {
// let's call a function that will crash the program
int* p = 0;
*p = 0;
}
}

View File

@ -235,7 +235,7 @@ int video_init(float factor)
{
detect_sleep_mode();
if (factor <= 0 || factor > 1) {
if (factor < 0 || factor > 1) {
factor = 1.0f;
}
quality_factor = factor;

View File

@ -69,7 +69,7 @@ func NewNative(opts NativeOptions) *Native {
sleepModeSupported := isSleepModeSupported()
defaultQualityFactor := opts.DefaultQualityFactor
if defaultQualityFactor <= 0 || defaultQualityFactor > 1 {
if defaultQualityFactor < 0 || defaultQualityFactor > 1 {
defaultQualityFactor = 1.0
}

View File

@ -177,8 +177,10 @@ func rpcReboot(force bool) error {
return hwReboot(force, nil, 0)
}
var streamFactor = 1.0
func rpcGetStreamQualityFactor() (float64, error) {
return config.VideoQualityFactor, nil
return streamFactor, nil
}
func rpcSetStreamQualityFactor(factor float64) error {
@ -188,10 +190,7 @@ func rpcSetStreamQualityFactor(factor float64) error {
return err
}
config.VideoQualityFactor = factor
if err := SaveConfig(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
streamFactor = factor
return nil
}

View File

@ -111,7 +111,6 @@ type Client struct {
var (
defaultTimerDuration = 1 * time.Second
defaultLinkUpTimeout = 30 * time.Second
defaultDHCPTimeout = 5 * time.Second // DHCP request timeout (not link up timeout)
maxRenewalAttemptDuration = 2 * time.Hour
)
@ -126,11 +125,11 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
}
if cfg.Timeout == 0 {
cfg.Timeout = defaultDHCPTimeout
cfg.Timeout = defaultLinkUpTimeout
}
if cfg.Retries == 0 {
cfg.Retries = 4
cfg.Retries = 3
}
return &Client{
@ -154,15 +153,9 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
}, nil
}
func resetTimer(t *time.Timer, attempt int, l *zerolog.Logger) {
// Exponential backoff: 1s, 2s, 4s, 8s, max 8s
backoffAttempt := attempt
if backoffAttempt > 3 {
backoffAttempt = 3
}
delay := time.Duration(1<<backoffAttempt) * time.Second
l.Debug().Dur("delay", delay).Int("attempt", attempt).Msg("will retry later")
t.Reset(delay)
func resetTimer(t *time.Timer, l *zerolog.Logger) {
l.Debug().Dur("delay", defaultTimerDuration).Msg("will retry later")
t.Reset(defaultTimerDuration)
}
func getRenewalTime(lease *Lease) time.Duration {
@ -175,14 +168,12 @@ func getRenewalTime(lease *Lease) time.Duration {
func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
l := c.l.With().Str("interface", ifname).Int("family", family).Logger()
attempt := 0
for range t.C {
l.Info().Int("attempt", attempt).Msg("requesting lease")
l.Info().Msg("requesting lease")
if _, err := c.ensureInterfaceUp(ifname); err != nil {
l.Error().Err(err).Int("attempt", attempt).Msg("failed to ensure interface up")
resetTimer(t, attempt, c.l)
attempt++
l.Error().Err(err).Msg("failed to ensure interface up")
resetTimer(t, c.l)
continue
}
@ -197,14 +188,11 @@ func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
lease, err = c.requestLease6(ifname)
}
if err != nil {
l.Error().Err(err).Int("attempt", attempt).Msg("failed to request lease")
resetTimer(t, attempt, c.l)
attempt++
l.Error().Err(err).Msg("failed to request lease")
resetTimer(t, c.l)
continue
}
// Successfully obtained lease, reset attempt counter
attempt = 0
c.handleLeaseChange(lease)
nextRenewal := getRenewalTime(lease)

View File

@ -26,31 +26,6 @@ show_help() {
echo " $0 -r 192.168.0.17 -u admin"
}
# Function to check if device is pingable
check_ping() {
local host=$1
msg_info "▶ Checking if device is reachable at ${host}..."
if ! ping -c 3 -W 5 "${host}" > /dev/null 2>&1; then
msg_err "Error: Cannot reach device at ${host}"
msg_err "Please verify the IP address and network connectivity"
exit 1
fi
msg_info "✓ Device is reachable"
}
# Function to check if SSH is accessible
check_ssh() {
local user=$1
local host=$2
msg_info "▶ Checking SSH connectivity to ${user}@${host}..."
if ! ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=10 "${user}@${host}" "echo 'SSH connection successful'" > /dev/null 2>&1; then
msg_err "Error: Cannot establish SSH connection to ${user}@${host}"
msg_err "Please verify SSH access and credentials"
exit 1
fi
msg_info "✓ SSH connection successful"
}
# Default values
SCRIPT_PATH=$(realpath "$(dirname $(realpath "${BASH_SOURCE[0]}"))")
REMOTE_USER="root"
@ -138,10 +113,6 @@ if [ -z "$REMOTE_HOST" ]; then
exit 1
fi
# Check device connectivity before proceeding
check_ping "${REMOTE_HOST}"
check_ssh "${REMOTE_USER}" "${REMOTE_HOST}"
# check if the current CPU architecture is x86_64
if [ "$(uname -m)" != "x86_64" ]; then
msg_warn "Warning: This script is only supported on x86_64 architecture"
@ -160,7 +131,7 @@ if [[ "$SKIP_UI_BUILD" = true && ! -f "static/index.html" ]]; then
SKIP_UI_BUILD=false
fi
if [[ "$SKIP_UI_BUILD" = false && "$JETKVM_INSIDE_DOCKER" != 1 ]]; then
if [[ "$SKIP_UI_BUILD" = false && "$JETKVM_INSIDE_DOCKER" != 1 ]]; then
msg_info "▶ Building frontend"
make frontend SKIP_UI_BUILD=0
SKIP_UI_BUILD_RELEASE=1
@ -173,13 +144,13 @@ fi
if [ "$RUN_GO_TESTS" = true ]; then
msg_info "▶ Building go tests"
make build_dev_test
make build_dev_test
msg_info "▶ Copying device-tests.tar.gz to remote host"
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > /tmp/device-tests.tar.gz" < device-tests.tar.gz
msg_info "▶ Running go tests"
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" ash << 'EOF'
ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << 'EOF'
set -e
TMP_DIR=$(mktemp -d)
cd ${TMP_DIR}
@ -220,35 +191,35 @@ then
SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD} \
SKIP_UI_BUILD=${SKIP_UI_BUILD_RELEASE} \
ENABLE_SYNC_TRACE=${ENABLE_SYNC_TRACE}
# Copy the binary to the remote host as if we were the OTA updater.
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > /userdata/jetkvm/jetkvm_app.update" < bin/jetkvm_app
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > /userdata/jetkvm/jetkvm_app.update" < bin/jetkvm_app
# Reboot the device, the new app will be deployed by the startup process.
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "reboot"
ssh "${REMOTE_USER}@${REMOTE_HOST}" "reboot"
else
msg_info "▶ Building development binary"
do_make build_dev \
SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD} \
SKIP_UI_BUILD=${SKIP_UI_BUILD_RELEASE} \
ENABLE_SYNC_TRACE=${ENABLE_SYNC_TRACE}
# Kill any existing instances of the application
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true"
ssh "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true"
# Copy the binary to the remote host
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "cat > ${REMOTE_PATH}/jetkvm_app_debug" < bin/jetkvm_app
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > ${REMOTE_PATH}/jetkvm_app_debug" < bin/jetkvm_app
if [ "$RESET_USB_HID_DEVICE" = true ]; then
msg_info "▶ Resetting USB HID device"
msg_warn "The option has been deprecated and will be removed in a future version, as JetKVM will now reset USB gadget configuration when needed"
# Remove the old USB gadget configuration
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "rm -rf /sys/kernel/config/usb_gadget/jetkvm/configs/c.1/hid.usb*"
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" "ls /sys/class/udc > /sys/kernel/config/usb_gadget/jetkvm/UDC"
ssh "${REMOTE_USER}@${REMOTE_HOST}" "rm -rf /sys/kernel/config/usb_gadget/jetkvm/configs/c.1/hid.usb*"
ssh "${REMOTE_USER}@${REMOTE_HOST}" "ls /sys/class/udc > /sys/kernel/config/usb_gadget/jetkvm/UDC"
fi
# Deploy and run the application on the remote host
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF
ssh "${REMOTE_USER}@${REMOTE_HOST}" ash << EOF
set -e
# Set the library path to include the directory where librockit.so is located
@ -258,17 +229,6 @@ export LD_LIBRARY_PATH=/oem/usr/lib:\$LD_LIBRARY_PATH
killall jetkvm_app || true
killall jetkvm_app_debug || true
# Wait until both binaries are killed, max 10 seconds
i=1
while [ \$i -le 10 ]; do
echo "Waiting for jetkvm_app and jetkvm_app_debug to be killed, \$i/10 ..."
if ! pgrep -f "jetkvm_app" > /dev/null && ! pgrep -f "jetkvm_app_debug" > /dev/null; then
break
fi
sleep 1
i=\$((i + 1))
done
# Navigate to the directory where the binary will be stored
cd "${REMOTE_PATH}"

View File

@ -13,7 +13,6 @@ import { useRTCStore, PostRebootAction } from "@/hooks/stores";
import LogoBlue from "@/assets/logo-blue.svg";
import LogoWhite from "@/assets/logo-white.svg";
import { isOnDevice } from "@/main";
import { sleep } from "@/utils";
interface OverlayContentProps {
@ -482,11 +481,8 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
// - Protocol-relative URLs: resolved with current protocol
// - Fully qualified URLs: used as-is
const targetUrl = new URL(postRebootAction.redirectTo, window.location.origin);
clearInterval(intervalId); // Stop polling before redirect
window.location.href = targetUrl.href;
// Add 1s delay between setting location.href and calling reload() to prevent reload from interrupting the navigation.
await sleep(1000);
window.location.reload();
}
} catch (err) {

View File

@ -46,10 +46,10 @@ export default function SettingsHardwareRoute() {
}
setBacklightSettings(settings);
handleBacklightSettingsSave(settings);
handleBacklightSettingsSave();
};
const handleBacklightSettingsSave = (backlightSettings: BacklightSettings) => {
const handleBacklightSettingsSave = () => {
send("setBacklightSettings", { params: backlightSettings }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
@ -81,7 +81,7 @@ export default function SettingsHardwareRoute() {
const duration = enabled ? 90 : -1;
send("setVideoSleepMode", { duration }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.hardware_power_saving_failed_error({ error: resp.error.data || m.unknown_error() }));
notifications.error(m.hardware_power_saving_failed_error({ error: resp.error.data ||m.unknown_error() }));
setPowerSavingEnabled(!enabled); // Attempt to revert on error
return;
}