diff --git a/app/src/api/handlers/system.rs b/app/src/api/handlers/system.rs index fcbec79..de201a7 100644 --- a/app/src/api/handlers/system.rs +++ b/app/src/api/handlers/system.rs @@ -155,6 +155,29 @@ pub fn get_recent_logs(params: RecentLogsParams) -> Result { Ok(serde_json::to_value(entries)?) } +/// The device's current time zone: `TZ`, then `/etc/timezone`, then the +/// `/etc/localtime` symlink target; a device with none of them runs on UTC. +pub fn get_timezone() -> Result { + if let Ok(tz) = std::env::var("TZ") { + let tz = tz.trim().trim_start_matches(':'); + if !tz.is_empty() { + return Ok(tz.to_string()); + } + } + if let Ok(tz) = std::fs::read_to_string("/etc/timezone") { + let tz = tz.trim(); + if !tz.is_empty() { + return Ok(tz.to_string()); + } + } + if let Ok(target) = std::fs::read_link("/etc/localtime") + && let Some((_, zone)) = target.to_string_lossy().split_once("zoneinfo/") + { + return Ok(zone.to_string()); + } + Ok("UTC".to_string()) +} + pub fn get_timezones() -> Result> { Ok(vec![ "UTC".to_string(), diff --git a/app/src/api/registry_builder.rs b/app/src/api/registry_builder.rs index b1b491c..1e2fa91 100644 --- a/app/src/api/registry_builder.rs +++ b/app/src/api/registry_builder.rs @@ -455,6 +455,7 @@ pub fn create_default_registry() -> RpcRegistry { registry.register_typed("setDefaultLogLevel", system::set_default_log_level); registry.register_typed("emitTestLog", system::emit_test_log); registry.register_typed("getRecentLogs", system::get_recent_logs); + registry.register_no_params("getTimezone", system::get_timezone); registry.register_async("getTimezones", |_params| { Box::pin(async move { let result = system::get_timezones()?; diff --git a/app/tests/rpc_methods.txt b/app/tests/rpc_methods.txt index 03238d1..e06c637 100644 --- a/app/tests/rpc_methods.txt +++ b/app/tests/rpc_methods.txt @@ -52,6 +52,7 @@ getStreamQualityFactor getTLSState getTailscaleControlURL getTailscaleStatus +getTimezone getTimezones getUSBState getUpdateStatus diff --git a/crates/rkvm-web/src/settings.rs b/crates/rkvm-web/src/settings.rs index 4658085..b5287ef 100644 --- a/crates/rkvm-web/src/settings.rs +++ b/crates/rkvm-web/src/settings.rs @@ -47,6 +47,9 @@ pub fn Settings( let network = RwSignal::new(String::new()); let update_state = RwSignal::new(String::new()); + // Only offered after Check reports an update. + let update_available = RwSignal::new(false); + let confirm_pw = RwSignal::new(String::new()); let usb = RwSignal::new(UsbDevices::default()); let edid = RwSignal::new(String::new()); let wol_mac = RwSignal::new(String::new()); @@ -181,7 +184,10 @@ pub fn Settings( }), Box::pin(async move { if let Ok(v) = client.call("getVideoSleepMode", Value::Null).await { - video_sleep.set(v.get("duration").and_then(Value::as_i64).unwrap_or(-1)); + // The device keeps the last duration while disabled. + let enabled = v.get("enabled").and_then(Value::as_bool).unwrap_or(false); + let duration = v.get("duration").and_then(Value::as_i64).unwrap_or(-1); + video_sleep.set(if enabled { duration } else { -1 }); } }), Box::pin(async move { @@ -211,8 +217,9 @@ pub fn Settings( }); let do_mount = move |_| { - let url = mount_url.get(); + let url = mount_url.get().trim().to_string(); if url.is_empty() { + mount_state.set("enter an image URL first".to_string()); return; } let mode = mount_mode.get(); @@ -278,6 +285,7 @@ pub fn Settings( let app = v.get("appUpdateAvailable").and_then(Value::as_bool).unwrap_or(false); let sys = v.get("systemUpdateAvailable").and_then(Value::as_bool).unwrap_or(false); + update_available.set(app || sys); update_state.set(if app || sys { "update available".to_string() } else { @@ -289,6 +297,9 @@ pub fn Settings( }); }; let apply_update = move |_| { + if !update_available.get_untracked() { + return; + } let Some(client) = rpc.get_value() else { return }; update_state.set("updating…".to_string()); spawn_local(async move { @@ -387,6 +398,10 @@ pub fn Settings( pw_msg.set("New password too short".to_string()); return; } + if n != confirm_pw.get() { + pw_msg.set("The two new passwords do not match".to_string()); + return; + } let first = no_password.get_untracked(); spawn_local(async move { let result = if first { @@ -400,6 +415,7 @@ pub fn Settings( pw_msg.set(if first { "Password set" } else { "Password updated" }.to_string()); old_pw.set(String::new()); new_pw.set(String::new()); + confirm_pw.set(String::new()); } Err(e) => pw_msg.set(e), } @@ -494,6 +510,12 @@ pub fn Settings( prop:value=move || new_pw.get() on:input=move |ev| new_pw.set(event_target_value(&ev)) /> + @@ -536,7 +558,13 @@ pub fn Settings(
{move || update_state.get()}
- +
diff --git a/crates/rkvm-web/src/settings_advanced.rs b/crates/rkvm-web/src/settings_advanced.rs index 9c5c22a..9d62ba9 100644 --- a/crates/rkvm-web/src/settings_advanced.rs +++ b/crates/rkvm-web/src/settings_advanced.rs @@ -25,6 +25,7 @@ pub fn AdvancedSettings(open: RwSignal, rpc: Rpc) -> impl IntoView { let serial_cmd = RwSignal::new(String::new()); let serial_history = RwSignal::new(Vec::::new()); let timezones = RwSignal::new(Vec::::new()); + let timezone = RwSignal::new(String::new()); let macros = RwSignal::new(Vec::::new()); let edit_id = RwSignal::new(Option::::None); let edit_name = RwSignal::new(String::new()); @@ -94,9 +95,18 @@ pub fn AdvancedSettings(open: RwSignal, rpc: Rpc) -> impl IntoView { if let Ok(v) = c.call("getSerialCommandHistory", Value::Null).await { serial_history.set(json_string_list(&v)); } + let current = match c.call("getTimezone", Value::Null).await { + Ok(v) => as_string(&v), + Err(_) => String::new(), + }; if let Ok(v) = c.call("getTimezones", Value::Null).await { - timezones.set(json_string_list(&v)); + let mut list = json_string_list(&v); + if !current.is_empty() && !list.contains(¤t) { + list.insert(0, current.clone()); + } + timezones.set(list); } + timezone.set(current); if let Ok(v) = c.call("getKeyboardMacros", Value::Null).await { macros.set(MacroItem::list_from(&v)); } @@ -336,7 +346,9 @@ pub fn AdvancedSettings(open: RwSignal, rpc: Rpc) -> impl IntoView {

"Timezone"

- {move || { timezones .get() @@ -345,6 +357,7 @@ pub fn AdvancedSettings(open: RwSignal, rpc: Rpc) -> impl IntoView { .collect_view() }} +
"Current device time zone. Changing it is not supported yet."
@@ -378,7 +391,7 @@ pub fn AdvancedSettings(open: RwSignal, rpc: Rpc) -> impl IntoView { />