Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions app/src/api/handlers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,29 @@ pub fn get_recent_logs(params: RecentLogsParams) -> Result<Value> {
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<String> {
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<Vec<String>> {
Ok(vec![
"UTC".to_string(),
Expand Down
1 change: 1 addition & 0 deletions app/src/api/registry_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;
Expand Down
1 change: 1 addition & 0 deletions app/tests/rpc_methods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ getStreamQualityFactor
getTLSState
getTailscaleControlURL
getTailscaleStatus
getTimezone
getTimezones
getUSBState
getUpdateStatus
Expand Down
34 changes: 31 additions & 3 deletions crates/rkvm-web/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
}
Expand Down Expand Up @@ -494,6 +510,12 @@ pub fn Settings(
prop:value=move || new_pw.get()
on:input=move |ev| new_pw.set(event_target_value(&ev))
/>
<input
type="password"
placeholder="Repeat new password"
prop:value=move || confirm_pw.get()
on:input=move |ev| confirm_pw.set(event_target_value(&ev))
/>
<button on:click=submit_password>
{move || if no_password.get() { "Set password" } else { "Update password" }}
</button>
Expand Down Expand Up @@ -536,7 +558,13 @@ pub fn Settings(
<div class="muted">{move || update_state.get()}</div>
<div class="row">
<button on:click=check_update>"Check"</button>
<button on:click=apply_update>"Update now"</button>
<button
on:click=apply_update
disabled=move || !update_available.get()
title="Run Check first; enabled when an update is available"
>
"Update now"
</button>
</div>
</section>

Expand Down
19 changes: 16 additions & 3 deletions crates/rkvm-web/src/settings_advanced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub fn AdvancedSettings(open: RwSignal<bool>, rpc: Rpc) -> impl IntoView {
let serial_cmd = RwSignal::new(String::new());
let serial_history = RwSignal::new(Vec::<String>::new());
let timezones = RwSignal::new(Vec::<String>::new());
let timezone = RwSignal::new(String::new());
let macros = RwSignal::new(Vec::<MacroItem>::new());
let edit_id = RwSignal::new(Option::<String>::None);
let edit_name = RwSignal::new(String::new());
Expand Down Expand Up @@ -94,9 +95,18 @@ pub fn AdvancedSettings(open: RwSignal<bool>, 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(&current) {
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));
}
Expand Down Expand Up @@ -336,7 +346,9 @@ pub fn AdvancedSettings(open: RwSignal<bool>, rpc: Rpc) -> impl IntoView {

<section>
<h3>"Timezone"</h3>
<select>
// The device has no RPC to change its zone yet, so show the current
// one without pretending the select does something.
<select disabled=true prop:value=move || timezone.get()>
{move || {
timezones
.get()
Expand All @@ -345,6 +357,7 @@ pub fn AdvancedSettings(open: RwSignal<bool>, rpc: Rpc) -> impl IntoView {
.collect_view()
}}
</select>
<div class="muted">"Current device time zone. Changing it is not supported yet."</div>
</section>

<section>
Expand Down Expand Up @@ -378,7 +391,7 @@ pub fn AdvancedSettings(open: RwSignal<bool>, rpc: Rpc) -> impl IntoView {
/>
<textarea
rows="4"
placeholder="One step per line: modifiers | keys | delayMs&#10;e.g. ControlLeft,AltLeft | Delete | 50"
placeholder="One step per line: modifiers | keys | delayMs\ne.g. ControlLeft,AltLeft | Delete | 50"
prop:value=move || edit_steps.get()
on:input=move |ev| edit_steps.set(event_target_value(&ev))
></textarea>
Expand Down
Loading