* fix(logging): reset troubleshooting log level after reboot
Restore WARN on boot so temporary troubleshooting verbosity cannot silently fill device storage before rolling logs land. Update the settings copy to describe the reboot reset, add an e2e regression test for the INFO-to-WARN behavior, and make the i18n resort helper accept lint-staged file arguments.
* fix(i18n): avoid formatter churn in locale files
Keep localization message JSON on the repo's existing resort_messages.py formatting so translation-only changes do not explode into full-file diffs. Remove JSON from the generic oxfmt lint-staged rule and restage the locale files in their canonical format.
* feat(ui): add log level selector in Troubleshooting Mode
Add a UI dropdown in Advanced > Troubleshooting Mode that lets users
set the system log verbosity (Error, Warning, Info, Debug, Trace).
Changes take effect immediately without restart via new
getDefaultLogLevel/setDefaultLogLevel JSON-RPC endpoints.
Also downgrades the noisy wakeup_on_write permission denied warning
from Warn to Debug level, and removes the INFO→WARN config migration
so users can actually select INFO.
Localized for all 14 languages.
* chore(ui): disable no-floating-promises in oxlint
The rule is not actionable for the current codebase; turn it off explicitly.
* refactor(ui): drop void prefixes on JSON-RPC send in advanced settings
no-floating-promises is disabled in oxlint; match the rest of the codebase.
* fix(ui): localize log level dropdown and fix optimistic update
- Replace hardcoded English dropdown labels with localized m.*() calls
- Replace hardcoded error string with m.advanced_error_set_log_level()
- Optimistically update dropdown on change and revert on RPC failure
- Add 6 new i18n keys across all 14 locales
* chore: add remote-agent to .gitignore and auto-sort i18n in pre-commit
- Ignore the compiled e2e/remote-agent/remote-agent binary
- Add lint-staged rule to run i18n:resort on message JSON changes
* copy(ui): improve log level setting description
Apply outcome-oriented copy: explain what the setting does for the
user and when to change it, rather than restating the control's
mechanics. Updated across all 14 locales.
* fix(logging): scope loggers not rebuilt when config level matches base default
UpdateLogLevel compared the new config level against the base default
(ErrorLevel) instead of the previous config level. When switching from
WARN back to ERROR, the comparison was equal so scope loggers kept
their old WarnLevel filter — WRN messages continued appearing despite
the user selecting Error.
Compare against the previous defaultLogLevelFromConfig instead.
* test(logging): add RPC probe for log level filtering
Add a dedicated emitTestLog JSON-RPC method and a focused e2e spec that
verifies live TRACE/DEBUG/INFO/WARN/ERROR filtering against last.log.
* chore(ui): update .gitignore to exclude screenshot.png file
Remove the detach window feature and replace it with a simpler `?embed`
query parameter that hides the header bar and status bar using the same
settings levers. Embed mode latches into session state so it persists
across in-app navigation.
- Delete useDetachedWindow hook and window tracking logic
- Add `isEmbedMode` to UI store, latched from `?embed` query param
- Embed mode forces hideHeaderBar and hideStatusBar via same code path
as the existing appearance settings
- Replace detach/close buttons with fullscreen split button containing
"Compact Window" option that opens embed view in new window
- Hide settings button and show close button in embed mode
- Simplify useAppNavigation by removing query param threading
- Add action_bar_compact_window i18n key to all 14 locales
* feat(video): tune encoder for better quality and faster recovery
- Reduce GOP from 60 to 30 for faster keyframe recovery on screen changes
- Set u32MinBitRate to half the target to prevent static-screen bitrate collapse
- Reduce u32StatTime from 3s to 2s for tighter rate control adaptation
- Raise minimum bitrate floor from 100 to 200 kbps
* feat(ui): show live bitrate in debug info bar
Poll WebRTC inbound-rtp stats every second and display the current
receive bitrate next to the codec indicator when debug mode is enabled.
* feat(ui): add loading spinner to video quality dropdown
Disable the select and show a spinner while fetching or applying the
stream quality factor, matching the existing EDID selector pattern.
* fix(i18n): remove redundant prefixes from video settings titles
Strip "Video"/"Stream" prefixes from settings headings that are already
under the Video settings page: Stream Quality → Quality, Video Codec →
Codec, Video Enhancement → Enhancement.
* feat(video): add H.265 codec support with auto-negotiation
Add H.265 (HEVC) encoding support to the RV1106 hardware encoder alongside
existing H.264. The codec is negotiated per-WebRTC session based on browser
capabilities.
- Add codec preference setting (Auto/H.265/H.264) to config, RPC, and UI
- Auto mode inspects the browser's SDP offer and prefers H.265 when supported,
with graceful fallback to H.264 for browsers without H.265 (e.g. Firefox)
- Move WebRTC video track creation from newSession() to ExchangeOffer() so
the codec can be resolved after seeing the browser's offer
- Set encoder codec type in onFirstSessionConnected() before VideoStart()
- Show active codec in the status bar when troubleshooting mode is enabled
- Remove quality factor >1.0 ceiling from ctrl.c to allow bitrate testing
- Fix Go wrapper to check return value from C quality factor setter
- Add e2e tests: video quality bitrate measurement, codec negotiation,
codec preference persistence, and a quality factor sweep benchmark
- Add visual noise helpers (remote host terminal) to e2e test infrastructure
* chore(e2e): remove video quality benchmark tests and helpers
Remove video-quality-sweep and video-quality spec files — these are
benchmarking tools, not regression tests. Also removes the visual noise
helpers and hardcoded developer SSH address from helpers.ts.
* feat(video): bump bitrate cap to 4000 kbps and tighten VBR ceiling
- Increase base_bitrate_high from 2000 to 4000 kbps, giving users
better image quality at every quality factor setting.
- Tighten VBR max_bitrate from 2x to 1.5x target, reducing encoder
overshoot while still allowing headroom for dynamic content.
- Add frames dropped, decode time, freeze count to WebRTC test hooks
for pipeline health monitoring.
- Move bitrate sweep benchmark to ui/benchmarks/ with its own
playwright config, separate from the e2e test suite.
Sweep results (visual noise, H.264, 1080p):
factor=0.1: 3082 kbps, 60fps, 0 dropped, 2.9ms decode
factor=0.5: 6357 kbps, 60fps, 0 dropped, 3.6ms decode
factor=1.0: 9445 kbps, 59fps, 0 dropped, 4.3ms decode
* feat(network): add custom NTP/HTTP time sync configuration UI
Closes#516, #645, #59
The backend supports custom NTP servers, HTTP URLs, source ordering,
parallel queries, and fallback control for time synchronization, but the
frontend only exposes three presets (NTP only, NTP and HTTP, HTTP only).
Users who need to specify their own NTP server — the core ask in all
three linked issues — have no way to do so through the UI.
Add a "Custom" option to the time sync dropdown. When selected, a card
appears with input fields for NTP servers and HTTP URLs, following the
same list-with-add/remove pattern used by the static IPv4 DNS fields.
This is a simplified alternative to #1102 which exposed every backend
field (source ordering, parallel queries, disable fallback) as direct
UI controls. That PR stalled for 3 months due to complexity concerns
and UX debate. This PR ships the functionality users actually requested
— custom NTP servers — with a minimal UI surface:
#1102: 753 additions, 15 files, new Combobox modifications
This: ~120 additions, 18 files (13 are localization)
The advanced fields (TimeSyncOrdering, TimeSyncParallel,
TimeSyncDisableFallback) retain their backend defaults and can be
surfaced in a follow-up if there is demand.
Backend changes:
confparser.go — add hostname_or_ipv4_or_ipv6 validation type so NTP
server fields accept hostnames like pool.ntp.org, not just raw IPs.
config.go — change TimeSyncNTPServers validation from ipv4_or_ipv6
to hostname_or_ipv4_or_ipv6.
Frontend changes:
CustomTimeSyncCard.tsx — new component with NTP server list and HTTP
URL list, field validation, add/remove controls.
stores.ts — add optional time_sync_ordering, time_sync_ntp_servers,
time_sync_http_urls, time_sync_disable_fallback, time_sync_parallel
to NetworkSettings interface.
network settings page — uncomment Custom option, render card when
time_sync_mode is custom.
Translations added for all 13 supported languages.
* fix(timesync): address review feedback on custom NTP UI
1. filterNTPServers: pass hostnames through instead of dropping
them. net.ParseIP() returns nil for hostnames like
pool.ntp.org, causing them to be silently skipped. The NTP
library handles DNS resolution itself, so hostnames are valid
entries.
2. getSyncMode: when TimeSyncMode is "custom", default the
ordering to [ntp_user_provided, http_user_provided, ntp_dhcp,
ntp, http] so user-provided servers are actually queried. The
previous hardcoded default never included *_user_provided
entries, rendering custom servers unreachable.
3. Stale config pointer: add SetNetworkConfig() on TimeSync and
call it from rpcSetNetworkSettings after config.NetworkConfig
is replaced. Without this, TimeSync holds a stale pointer and
ignores runtime config changes until restart.
4. DNS vacuous truth: guard .every() calls on ipv4/ipv6 DNS
dirty arrays with .length > 0 checks. [].every() returns true
in JS, causing empty DNS arrays to falsely appear in the
confirmation dialog.
Signed-off-by: Alex Howells <alex@howells.me>
* fix(timesync): ensure custom mode uses user-provided servers and re-syncs on settings change
Move TimeSyncOrdering override before the mode switch so "custom" mode
always sets the correct ordering with ntp_user_provided first, preventing
stale ordering values from overriding it. Trigger an immediate time sync
when network settings are saved so users don't have to wait for the
hourly cycle or reboot.
---------
Signed-off-by: Alex Howells <alex@howells.me>
Co-authored-by: Adam Shiervani <adam@jetkvm.com>
* fix: add hold-to-force-off hint for ATX Power button (#1040)
* i18n: add atx_power_control_hold_hint translations for all languages
* fix: restore i18n files to proper format, only add atx_power_control_hold_hint key
Previous commits accidentally changed indentation (2-space → 4-space) and
removed 102 keys from all locale files. This restores the original formatting
and content, adding only the new atx_power_control_hold_hint translation.
* fix: add hide text toggle to paste modal (#694)
* fix: move paste modal hide/show text toggle to top-right above input (#694)
* fix: inline hide/show toggle into input labels and fix lint warnings
* i18n: add paste modal hide/show text translations for all locales
* fix: use CSS text-security instead of password input to preserve newlines
* i18n: translate scroll invert strings for all locales
* fix: hide invalid character details when text is hidden in paste modal
* feat(ui): add invert scroll direction toggle and fix Vite 8 CJS interop
Add scroll direction invert setting for macOS Natural Scrolling support.
Fix react-use-websocket CJS default export not resolving under Rolldown.
Fixes#640
* fix(ui): invert horizontal scroll when invertScroll is enabled
macOS Natural Scrolling inverts both axes at the OS level. The previous
change only corrected vertical scroll, leaving horizontal broken.
Add scroll direction invert setting for macOS Natural Scrolling support.
Fix react-use-websocket CJS default export not resolving under Rolldown.
Fixes#640
* fix: add custom broadcast IP option to Wake-on-LAN (#1238)
Add support for specifying a custom subnet broadcast IP when sending
WOL magic packets, enabling wake across different subnets.
Backend:
- Add broadcastIP optional parameter to rpcSendWOLMagicPacket
- Add OptionalParams support to RPCHandler for params with zero defaults
- Pass broadcastIP query param through HTTP handler
UI:
- Add broadcast address dropdown (Auto/Custom) to WOL dialog
- Show subnet broadcast IP input when Custom is selected
- Pass broadcastIP to RPC call when custom mode is active
* fix: move broadcast address field to add form only, default to Auto (#1238)
* fix(ui): simplify WoL broadcast dropdown and indent custom field
- Rename "Auto (global broadcast)" to "Auto" in the broadcast address
dropdown
- Wrap the custom subnet IP input in a nested indent with left border,
matching the settings page pattern (NestedSettingsGroup style)
* fix(i18n): use localization system for WoL broadcast address labels
Replace hardcoded English strings with m.xxx() calls in the broadcast
address UI and add the 4 new keys to all 14 locale files.
* fix: add USB serial console toggle to hardware settings (#726)
* fix: add USB CDC-ACM serial console gadget function (#726)
Add serial_console.go with acm.usb0 gadget config item following the
mass_storage pattern. Add SerialConsole bool to Devices struct and wire
it through config.go enable check and jsonrpc.go setUsbDeviceState.
The existing UI toggle in UsbDeviceSetting.tsx (with localization
messages) now calls through to the backend correctly.
When enabled, the KVM device creates /dev/ttyGS0 and the target host
sees a CDC-ACM serial device (/dev/ttyACM*). When disabled, the
symlink is removed from the USB gadget config and the host no longer
enumerates the ACM interface.
* fix: add CDC-ACM Console terminal UI for USB serial gadget (#726)
* fix: merge terminal buttons into split button and rename CDC-ACM to USB Serial Console (#726)
Combine KVM Terminal and USB Serial Console into a split button when both
are present, make USB serial console state reactive via zustand store so
the action bar updates without a page refresh, and fix the split button
chevron not respecting the disabled state.
* fix: add toggles to show/hide header bar and status bar in Appearance settings (#1333)
* fix: invert panel visibility toggles to hide header/status bars (#1333)
* fix: implement factory reset replacing config-only reset (#529)
- Add rpcFactoryReset that removes all user data (config, images,
TLS certs, SSH keys, serial settings, crash dumps) and reboots
- Remove rpcResetConfig RPC handler (keep internal resetConfig for
OTA and native event use)
- Replace Reset Config UI with Factory Reset button (danger theme)
and confirmation dialog in Settings > Advanced
- Update localization: add factory reset keys, remove reset config keys
- Add E2E test verifying factory reset UI and dialog copy
- Update ra-all factory reset test to restore SSH keys after reset
* fix: ensure factory reset reboots even when path removal fails
The early return on error exited rpcFactoryReset before reaching the
goroutine that triggers hwReboot, leaving the device partially wiped
with no reboot. Log the warning instead and always fall through to
the reboot.
* fix: remove hardcoded screenshot path from factory reset e2e test
* fix: replace hardcoded English strings with i18n message functions (#1162)
Replace two hardcoded English strings that bypassed the Paraglide i18n system:
- MacroStepCard.tsx: regex-derived "Left"/"Right" modifier labels now use
m.macro_modifier_left() and m.macro_modifier_right() message functions
- devices.$id.settings.advanced.tsx: version change acknowledgment checkbox
label now uses m.advanced_version_change_acknowledged_label()
Added new i18n keys to all locale JSON files and recompiled paraglide output.
* fix(i18n): translate untranslated keys across all languages
Translate `advanced_version_change_acknowledged_label`, `macro_modifier_left`,
and `macro_modifier_right` which were left as English in all non-English locales.
* fix(ui): fix prettier formatting in MacroStepCard
---------
Co-authored-by: AI Agent <ai@coder-ai.local>
* feat(tailscale): add custom control URL configuration & handling w/ tests
- Introduced TailscaleControlURL in the Config struct to allow configuration of the Tailscale control server.
- Added RPC handlers for getting and setting the Tailscale control URL.
- Updated TailscaleStatus to include controlURL, ensuring it reflects the configured or default value.
- Enhanced parsing and normalization of the control URL to enforce valid formats.
- Updated TailscaleCard component to manage and display the control server URL, allowing users to save changes.
* docs: update README to include optional Tailscale networking feature
Added a new section highlighting the built-in Tailscale status and control-server configuration, including support for custom Headscale-compatible endpoints.
* docs: Extend DEVELOPMENT.md with Tailscale control server details
* fix(tailscale): enhance error handling in rpcSetTailscaleControlURL
- Updated the rpcSetTailscaleControlURL function to revert the TailscaleControlURL to its previous value if saving or applying the new URL fails.
- Added a new test to ensure that the configuration is not saved when the apply command fails, verifying that the previous URL remains intact.
- Adjusted existing tests to validate the order of operations during the URL setting process.
Related to Review: https://github.com/jetkvm/kvm/pull/1312#pullrequestreview-3984856379
* refactor(tailscale): simplify control URL application logic and enhance error handling
- Renamed the test function to better reflect its purpose and updated the test cases to ensure correct command execution.
- Removed fallback logic for applying the Tailscale control URL, streamlining the error handling to return a clear error message when the "set" command fails.
- Added a new test to verify behavior when the "set" command fails, ensuring proper error reporting.
Related to Review https://github.com/jetkvm/kvm/pull/1312#pullrequestreview-3984856379
* refactor(tailscale): update control server configuration in UI & documentation
- Updated the TailscaleCard component to allow users to select between default and custom control server URLs.
- Improved state management for control server URL input based on the selected mode.
- Revised DEVELOPMENT.md to clarify control server application logic and error handling.
- Removed outdated example JSON-RPC payloads for clarity.
Related to Review: https://github.com/jetkvm/kvm/pull/1312#discussion_r2980284611
* refactor(ui): use built-in components and i18n for TailscaleCard
- Replace raw <select> with SelectMenuBasic component
- Use SettingsItem with new SM size for control server setting
- Use NestedSettingsGroup for indented custom URL input
- Add tailscale_* i18n keys to all 14 locale files with translations
- Add size prop (SM/MD) to SettingsItem for compact contexts
---------
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* MQTT support
* feat(mqtt): redesign MQTT settings UI with improved UX
Restructure the MQTT settings page for clarity and usability:
UI Structure:
- Organize settings into logical sections (Auth, Home Assistant, Advanced)
- Use progressive disclosure for port (Auto/Custom) and base topic (Default/Custom)
- Move connection status badge into page header
- Conditionally show HDD debounce only when ATX extension is active
- Add inline validation for required broker field
Connection & Error Handling:
- Add test-then-save flow: Save & Reconnect validates connectivity before persisting
- Add standalone Test Connection button for dry-run validation
- Add testMqttConnection RPC with 5s timeout (no retry, no side effects)
- Surface friendly i18n-ready error messages for common failures (auth, timeout, TLS, DNS)
- Track last connection error on MQTTManager for status reporting
Copy:
- Rewrite all descriptions for clarity and brevity
- Use benefit-oriented, active-voice microcopy throughout
---------
Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
* [FEATURE] Added copy text to clipboard from kvm using OCR
* fix: make OCR overlay cover full viewport
Change the semi-transparent background from absolute to fixed
positioning so it extends to the viewport edges, covering the
letterbox/pillarbox areas outside the video container.
* refactor: use Card component for OCR selection size indicator
Replace the manually-styled div with the Card component for the
selection size indicator, consistent with the project's component
library.
* feat: use ConfirmDialog for OCR processing indicator
* refactor: use ConfirmDialog for OCR result panel
* fix: make OCR result textarea readonly with text pre-selected
* refactor: use Cancel and Copy CTAs for OCR dialogs
* chore: machine translate OCR localization keys
* fix: address OCR copy-paste review feedback
- Fix cursor-pointer showing on disabled button in ConfirmDialog
- Change CTA from "Copy" to "Copy text" for clarity
- Fix selection breaking when mouse leaves viewport during drag
- Add children prop to ConfirmDialog; use description for helper text
- Fix clipboard.writeText failing in insecure contexts with fallback
* fix: address remaining OCR review feedback
- Merge processing and result into a single ConfirmDialog so the modal
doesn't flicker when OCR completes quickly
- Add dark:text-white to the selection size pill Card
- Remove duplicate toast in the execCommand fallback (the copy event
listener already handles it)
- Sequence dialog close → unmount so HeadlessUI's leave transition plays
* fix: prevent OCR dialog content flash during close animation
When closing the OCR result dialog, the title briefly flashed back to
"Recognizing text..." during the exit animation. This happened because
closeOverlay reset status to "idle" to close the dialog, but the
dialog content ternaries also depended on status, causing them to
evaluate incorrectly during the 200ms HeadlessUI leave transition.
Fix by introducing an isClosing flag that controls the dialog's open
prop independently, leaving status unchanged so dialog content remains
stable during the exit animation.
The SSH public key field in Settings > Advanced has no indication that a
key is required for SSH to work. Users enable developer mode, skip the
key field, and then cannot connect via SSH. The existing helper text only
mentions the default user is root.
Add an amber warning below the SSH key field that appears when the field
is empty: "A public key is required for SSH access. Without one, you
will not be able to connect." Uses the same text-amber-600 style as the
developer mode warning icon on the same page. The warning disappears
once a key is entered.
The save button remains enabled with an empty key so users can
intentionally clear their key to revoke SSH access.
Translations added for all 13 supported languages: da, de, en, es, fr,
it, ja, nb, pt, ru, sv, zh, zh-tw.
Fixes#1051
Go's bcrypt.GenerateFromPassword rejects passwords over 72 bytes with
an error. The code calls it without length checking in handleSetup,
handleCreatePassword, and handleUpdatePassword. The error surfaces as
"Failed to hash password" with no indication of the actual cause.
Users report being unable to set passwords during initial setup.
Add MaxPasswordLength = 72 alongside the existing MinPasswordLength = 8.
Check it in all three backend handlers before calling GenerateFromPassword,
returning a clear error message. Add matching MAX_PASSWORD_LENGTH
validation in the frontend (settings local-auth dialog and welcome
password flow) with localized error messages in all 13 languages.
Fixes#60
Add complete Welsh translation for all 963 UI strings. Technical
acronyms (KVM, HDMI, USB, DHCP, SSH, etc.) kept in English per standard
Welsh computing convention. Common Welsh computing terms used throughout:
cyfrinair (password), gosodiadau (settings), dyfais (device), rhwydwaith
(network), bysellfwrdd (keyboard), llygoden (mouse), sgrin (screen).
Register cy locale in both locales and languageTags arrays in the inlang
settings file.
* Create new extension "Serial Buttons"
* Add backend to send custom commands
* Add order buttons and response field
* Merge extensions "Serial Console" and "Serial Buttons"
* Update backend to combine serial console and custom buttons
* Update backend, implement pause function in terminal
* Improve normalization
* Minor serial helper improvements
* Update serial console part
* Small bug fixes
* Add localization
* Fix linting error
* Fix UI linting issues
* Fix Go lint issues
* Differentiate between User and System commands
* Set minimum chars and ratelimit on pw
* Implement onboarding flow for USB device tests by adding helper functions to handle welcome state and login. Enhance test setup to ensure device is ready before running tests.
* Refactor password handling by removing rate limiting checks from update and delete password functions. Update UI error handling to remove rate limit messages. Clean up related test cases for rate limiting.
* Refactor welcome flow and password handling in tests. Consolidate password setup functions and enhance local authentication mode checks. Update test cases to streamline onboarding and password management processes.
* Update log file paths in DEVELOPMENT.md
* Add password validation messages for multiple languages
- Added error message for passwords that are too short (minimum 8 characters).
- Included rate limiting error message for too many failed attempts.
- Updated localization files for Danish, German, Spanish, French, Italian, Japanese, Norwegian, Portuguese, Swedish, and both Simplified and Traditional Chinese.
* Update noder version in DEVELOPMENT.md
* Fix typo in log file path in DEVELOPMENT.md
* Refactor device onboarding and authentication flow in tests
- Renamed `ensureWelcomeState` to `resetDeviceToWelcome` for clarity.
- Consolidated password handling in welcome flow tests, replacing deprecated functions.
- Updated test setup to ensure device is in noPassword mode before running tests.
- Removed unused functions and cleaned up related test cases for better maintainability.
* Refactor mouse round-trip tests to streamline cursor movement verification
* Add missing languages and updated translations of languages
Japanese and Portuguese were not added to the translations.
Japanese locale strings need to be in Japanese.
Portuguese locale strings need to be in Portuguese.
Chinese (Traditional) locale strings need to be in Chinese (Traditional).
Always use (bokmål) for "book language" Norwegian.
Resorted using `npm run i18n:resort`
* Update the documentation to guide addition.
Needed to document the steps for adding the other languages.
* Correct the Chinese locale options
They should be in the native language of the containing file.
* Correct path for settings.json
Sorted language codes (leaving English at top as default)
Address review comments.
* Added update instructions for updating the translation menu.
Japanese was never added.
* Removed requirement to update utils.ts
Added a generic helper function that maps localized messages to their inlang/paraglide formatting function.
* Add documentation on end-to-end testing.
* Slight tweak of documentation
* Replace Japanese spelling out bokmål