diff --git a/ui/eslint.config.cjs b/ui/eslint.config.cjs index 8c6d57d6..ad4338a3 100644 --- a/ui/eslint.config.cjs +++ b/ui/eslint.config.cjs @@ -9,8 +9,6 @@ const { fixupConfigRules, } = require("@eslint/compat"); -const tsParser = require("@typescript-eslint/parser"); -const reactRefresh = require("eslint-plugin-react-refresh"); const js = require("@eslint/js"); const { @@ -23,6 +21,9 @@ const compat = new FlatCompat({ allConfig: js.configs.all }); +const tsParser = require("@typescript-eslint/parser"); +const reactRefresh = require("eslint-plugin-react-refresh"); + module.exports = defineConfig([{ languageOptions: { globals: { diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index ade66285..a7bf672d 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -1,19 +1,146 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", - "jetkvm": "JetKVM", - "jetkvm_logo": "JetKVM Logo", + "action_bar_connection_stats": "Connection Stats", + "action_bar_exit_fullscreen": "Exit Fullscreen", + "action_bar_extension": "Extension", + "action_bar_fullscreen": "Fullscreen", + "action_bar_paste_text": "Paste text", + "action_bar_settings": "Settings", + "action_bar_virtual_keyboard": "Virtual Keyboard", + "action_bar_virtual_media": "Virtual Media", + "action_bar_wake_on_lan": "Wake on LAN", + "action_bar_web_terminal": "Web Terminal", + "already_adopted_new_owner": "If you're the new owner, please ask the previous owner to de-register the device from their account in the cloud dashboard. If you believe this is an error, contact our support team for assistance.", + "already_adopted_other_user": "This device is currently registered to another user in our cloud dashboard.", + "already_adopted_return_to_dashboard": "Return to Dashboard", + "already_adopted_title": "Device Already Registered", "attach": "Attach", + "atx_power_control_get_state_error": "Failed to get ATX power state: {error}", + "atx_power_control_hdd_led": "HDD LED", + "atx_power_control_long_power_button": "Long Press", + "atx_power_control_power_button": "Power", + "atx_power_control_power_led": "Power LED", + "atx_power_control_reset_button": "Reset", + "atx_power_control_send_action_error": "Failed to send ATX power action {action}: {error}", + "atx_power_control_short_power_button": "Short Press", + "auth_authentication_mode_error": "An error occurred while setting the authentication mode", + "auth_authentication_mode_invalid": "Invalid authentication mode", + "auth_authentication_mode": "Please select an authentication mode", + "auth_connect_to_cloud_action": "Log in & Connect device", + "auth_connect_to_cloud_description": "Unlock remote access and advanced features for your device", + "auth_connect_to_cloud": "Connect your JetKVM to the cloud", + "auth_header_cta_already_have_account": "Already have an account?", + "auth_header_cta_dont_have_account": "Don't have an account?", + "auth_header_cta_new_to_jetkvm": "New to JetKVM?", + "auth_login_action": "Log in", + "auth_login_description": "Log in to access and manage your devices securely", + "auth_login": "Log in to your JetKVM account", + "auth_mode_local_change_later": "You can always change your authentication method later in the settings.", + "auth_mode_local_description": "Select how you would like to secure your JetKVM device locally.", + "auth_mode_local_no_password_description": "Quick access without password authentication.", + "auth_mode_local_no_password": "No Password", + "auth_mode_local_password_confirm_description": "Confirm your password", + "auth_mode_local_password_confirm_label": "Confirm Password", + "auth_mode_local_password_description": "Secure your device with a password for added protection.", + "auth_mode_local_password_do_not_match": "Passwords do not match", + "auth_mode_local_password_failed_set": "Failed to set password: {error}", + "auth_mode_local_password_note_local": "All data remains on your local device.", + "auth_mode_local_password_note": "This password will be used to secure your device data and protect against unauthorized access.", + "auth_mode_local_password_set_button": "Set Password", + "auth_mode_local_password_set_description": "Create a strong password to secure your JetKVM device locally.", + "auth_mode_local_password_set_label": "Enter a password", + "auth_mode_local_password_set": "Set a Password", + "auth_mode_local_password": "Password", + "auth_mode_local": "Local Authentication Method", + "auth_signup_connect_to_cloud_action": "Signup & Connect device", + "auth_signup_create_account_action": "Create Account", + "auth_signup_create_account_description": "Create your account and start managing your devices with ease.", + "auth_signup_create_account": "Create your JetKVM account", + "back_to_devices": "Back to Devices", + "back": "Back", "cancel": "Cancel", "close": "Close", + "cloud_kvms_description": "Manage your cloud KVMs and connect to them securely.", + "cloud_kvms_no_devices_description": "You don't have any devices with enabled JetKVM Cloud yet.", + "cloud_kvms_no_devices": "No devices found", + "cloud_kvms": "Cloud KVMs", "confirm": "Confirm", "connect_to_kvm": "Connect to KVM", + "connecting_to_device": "Connecting to device…", + "connection_established": "Connection established", + "connection_stats_badge_jitter_buffer_avg_delay": "Jitter Buffer Avg. Delay", + "connection_stats_badge_jitter": "Jitter", + "connection_stats_connection_description": "The connection between the client and the JetKVM.", + "connection_stats_connection": "Connection", + "connection_stats_frames_per_second_description": "Number of inbound video frames displayed per second.", + "connection_stats_frames_per_second": "Frames per second", + "connection_stats_network_stability_description": "How steady the flow of inbound video packets is across the network.", + "connection_stats_network_stability": "Network Stability", + "connection_stats_packets_lost_description": "Count of lost inbound video RTP packets.", + "connection_stats_packets_lost": "Packets Lost", + "connection_stats_playback_delay_description": "Delay added by the jitter buffer to smooth playback when frames arrive unevenly.", + "connection_stats_playback_delay": "Playback Delay", + "connection_stats_round_trip_time_description": "Round-trip time for the active ICE candidate pair between peers.", + "connection_stats_round_trip_time": "Round-Trip Time", + "connection_stats_sidebar": "Connection Stats", + "connection_stats_video_description": "The video stream from the JetKVM to the client.", + "connection_stats_video": "Video", + "continue": "Continue", + "creating_peer_connection": "Creating peer connection...", + "dc_power_control_current_unit": "A", + "dc_power_control_current": "Current", + "dc_power_control_get_state_error": "Failed to get DC power state: {error}", + "dc_power_control_power_off_button": "Power Off", + "dc_power_control_power_off_state": "Power OFF", + "dc_power_control_power_on_button": "Power On", + "dc_power_control_power_on_state": "Power ON", + "dc_power_control_power_unit": "W", + "dc_power_control_power": "Power", + "dc_power_control_restore_last_state": "Last State", + "dc_power_control_restore_power_state": "Restore Power Loss", + "dc_power_control_set_power_state_error": "Failed to send DC power state to {enabled}: {error}", + "dc_power_control_set_restore_state_error": "Failed to send DC power restore state to {state}: {error}", + "dc_power_control_voltage_unit": "V", + "dc_power_control_voltage": "Voltage", "default": "Default", "delete": "Delete", + "deregister_button": "Deregister from Cloud", + "deregister_cloud_devices": "Cloud Devices", + "deregister_description": "This will remove the device from your cloud account and revoke remote access to it. Please note that local access will still be possible", + "deregister_error": "There was an error {status} deregistering your device. Please try again.", "deregister_from_cloud": "Deregister from cloud", + "deregister_headline": "Deregister {device} from your cloud account", "detach": "Detach", + "dhcp_lease_boot_file": "Boot File", + "dhcp_lease_boot_next_server": "Boot Next Server", + "dhcp_lease_boot_server_name": "Boot Server Name", + "dhcp_lease_broadcast": "Broadcast", + "dhcp_lease_domain": "Domain", + "dhcp_lease_gateway": "Gateway", + "dhcp_lease_header": "DHCP Lease Information", + "dhcp_lease_hostname": "Hostname", + "dhcp_lease_lease_expires": "Lease Expires", + "dhcp_lease_maximum_transfer_unit": "MTU", + "dhcp_lease_renew": "Renew DHCP Lease", + "dhcp_lease_time_to_live": "TTL", "dhcp_server": "DHCP Server", "dns_servers": "DNS Servers", + "establishing_secure_connection": "Establishing secure connection…", + "experimental": "Experimental", + "extension_popover_load_and_manage_extensions": "Load and manage your extensions", + "extension_popover_set_error_notification": "Failed to set active extension: {error}", + "extension_popover_unload_extension": "Unload Extension", + "extension_serial_console_description": "Access your serial console extension", + "extension_serial_console": "Serial Console", + "extensions_atx_power_control_description": "Control the power state of your machine via ATX power control.", + "extensions_atx_power_control": "ATX Power Control", + "extensions_dc_power_control_description": "Control your DC Power extension", + "extensions_dc_power_control": "DC Power Control", + "extensions_popover_extensions": "Extensions", + "gathering_ice_candidates": "Gathering ICE candidates...", + "getting_remote_session_description": "Getting remote session description attempt {attempt}", "hide": "Hide", + "ice_gathering_completed": "ICE Gathering completed", "info_caps_lock": "Caps Lock", "info_compose": "Compose", "info_hdmi_state": "HDMI State:", @@ -32,73 +159,186 @@ "info_usb_state": "USB State:", "info_video_size": "Video Size:", "input_disabled": "Input disabled", + "invalid_password": "Invalid password", "ip_address": "IP Address", + "ipv6_address_label": "Address", + "ipv6_addresses": "IPv6 Addresses", + "ipv6_information": "IPv6 Information", + "ipv6_link_local": "Link-local", + "ipv6_preferred_lifetime": "Preferred Lifetime", + "ipv6_valid_lifetime": "Valid Lifetime", + "jetkvm_description": "JetKVM combines powerful hardware with intuitive software to provide a seamless remote control experience.", + "jetkvm_device": "JetKVM Device", + "jetkvm_logo": "JetKVM Logo", + "jetkvm_setup": "Set up your JetKVM", + "jetkvm": "JetKVM", + "jiggler_cron_schedule_description": "Cron expression for scheduling", + "jiggler_cron_schedule_label": "Cron Schedule", + "jiggler_example_business_hours_early": "Business Hours 8-17", + "jiggler_example_business_hours_late": "Business Hours 9-17", + "jiggler_examples_label": "Examples", + "jiggler_inactivity_limit_description": "Inactivity time before jiggle", + "jiggler_inactivity_limit_label": "Inactivity Limit Seconds", + "jiggler_more_examples": "More examples", + "jiggler_random_delay_description": "To avoid recognizable patterns", + "jiggler_random_delay_label": "Random delay", + "jiggler_save_jiggler_config": "Save Jiggler Config", + "jiggler_timezone_description": "Timezone for cron schedule", + "jiggler_timezone_label": "Timezone", + "kvm_terminal": "KVM Terminal", "last_online": "Last online {time}", + "learn_more": "Learn more", "load": "Load", + "loading": "Loading…", + "log_in": "Log In", "log_out": "Log out", "logged_in_as": "Logged in as", + "login_enter_password_description": "Enter your password to access your JetKVM.", + "login_enter_password": "Enter your password", + "login_error": "An error occurred while logging in", + "login_forgot_password": "Forgot password?", + "login_password_label": "Password", + "login_welcome_back": "Welcome back to JetKVM", + "macro_add_step": "Add Step{maxed_out}", + "macro_at_least_one_step_keys_or_modifiers": "At least one step must have keys or modifiers", + "macro_at_least_one_step_required": "At least one step is required", + "macro_max_steps_error": "You can only add a maximum of {max} steps per macro.", + "macro_max_steps_reached": "({max} max)", + "macro_name_label": "Macro Name", + "macro_name_required": "Name is required", + "macro_name_too_long": "Name must be less than 50 characters", + "macro_please_fix_validation_errors": "Please fix the validation errors", + "macro_save_error": "An error occurred while saving.", + "macro_save": "Save Macro", + "macro_step_count": "{steps} / {max} steps", + "macro_step_duration_description": "Time to wait before executing the next step.", + "macro_step_duration_label": "Step Duration", + "macro_step_keys_description": "Maximum {max} keys per step.", + "macro_step_keys_label": "Keys", + "macro_step_max_keys_reached": "Maximum keys reached", + "macro_step_modifiers_description": "What modifiers (Shift/Ctrl/Alt/Meta) are pressed during this step.", + "macro_step_modifiers_label": "Modifiers", + "macro_step_no_matching_keys_found": "No matching keys found", + "macro_step_search_for_key": "Search for key…", + "macro_steps_description": "Keys/modifiers executed in sequence with a delay between each step.", + "macro_steps_label": "Steps", + "metric_not_supported": "Metric not supported", + "metric_waiting_for_data": "Waiting for data…", + "mount_add_file_to_get_started": "Add a file to get started", + "mount_add_new_media": "Add New Media", + "mount_available_storage": "Available Storage", + "mount_button_back_to_overview": "Back to Overview", + "mount_button_cancel_upload": "Cancel Upload", + "mount_button_continue_upload": "Continue uploading", + "mount_button_mount_file": "Mount File", + "mount_button_mount_url": "Mount URL", + "mount_button_next": "Next", + "mount_button_previous": "Previous", + "mount_button_select": "Select", + "mount_button_showing_results": "Showing {from} to {to} of {total} results", + "mount_button_upload_new_image": "Upload a new image", + "mount_bytes_free": "{bytesFree} free", + "mount_bytes_used": "{bytesUsed} used", + "mount_calculating": "Calculating…", + "mount_click_to_select_file": "Click to select a file", + "mount_click_to_select_incomplete": "Click to select \"{name}\"", + "mount_confirm_delete": "Are you sure you want to delete {name}?", + "mount_continue_uploading_with_name": "Continue uploading \"{name}\"", + "mount_description_mode": "Choose how you want to mount your virtual media", + "mount_error_delete_file": "Error deleting file: {error}", + "mount_error_description": "An error occurred while attempting to mount the media. Please try again.", + "mount_error_get_storage_space": "Error getting storage space: {error}", + "mount_error_list_storage": "Error listing storage files: {error}", + "mount_error_title": "Mount Error", + "mount_get_state_error": "Failed to get virtual media state: {error}", + "mount_jetkvm_storage_description": "Mount previously uploaded files from the JetKVM storage", + "mount_jetkvm_storage": "JetKVM Storage Mount", + "mount_label_mount_as": "Mount as", + "mount_label_url_description": "Mount files from any public web address", + "mount_label_url": "URL Mount", + "mount_mode_cdrom": "CD/DVD", + "mount_mode_disk": "Disk", + "mount_mounted_as": "Mounted as", + "mount_mounted_from_storage": "Mounted from JetKVM Storage", + "mount_no_images_description": "Upload an image to start virtual media mounting.", + "mount_no_images_title": "No images available", + "mount_no_mounted_media": "No mounted media", + "mount_percentage_used": "{percentageUsed}% used", + "mount_please_select_file_to_upload": "Please select the file to upload.", + "mount_please_select_file": "Please select the file \"{name}\" to continue the upload.", + "mount_popular_images": "Popular images", + "mount_streaming_from_url": "Streaming from URL", + "mount_supported_formats": "Supported formats: ISO, IMG", + "mount_tag_experimental": "Experimental", + "mount_title_mode": "Virtual Media Source", + "mount_unmount_error": "Failed to unmount image: {error}", + "mount_unmount": "Unmount", + "mount_upload_description": "Select an image file to upload to JetKVM storage", + "mount_upload_error": "Upload error: {error}", + "mount_upload_failed_datachannel": "Failed to create data channel for file upload", + "mount_upload_failed_rtc": "Upload failed: {error}", + "mount_upload_successful": "Upload successful", + "mount_upload_title": "Upload New Image", + "mount_uploaded_has_been_uploaded": "{name} has been uploaded", + "mount_uploading_with_name": "Uploading {name}", + "mount_uploading": "Uploading…", + "mount_url_description": "Mount files from any public web address", + "mount_url_input_label": "Image URL", + "mount_url_mount": "URL Mount", + "mount_view_device_description": "Select an image to mount from the JetKVM storage", + "mount_view_device_title": "Mount from JetKVM Storage", + "mount_view_url_description": "Enter an URL to the image file to mount", + "mount_view_url_title": "Mount from URL", + "mount_virtual_media_description": "Mount an image to boot from or install an operating system.", + "mount_virtual_media_source_description": "Choose how you want to mount your virtual media", + "mount_virtual_media_source": "Virtual Media Source", + "mount_virtual_media": "Virtual Media", "never_seen_online": "Never seen online", + "next": "Next", "no_results_found": "No results found", "not_available": "N/A", "not_found": "Not found", "ntp_servers": "NTP Servers", "oh_no": "Oh no!", "online": "Online", + "other_session_detected": "Another Active Session Detected", + "other_session_take_over": " Only one active session is supported at a time. Would you like to take over this session?", + "other_session_use_here_button": "Use Here", "page_not_found_description": "The page you were looking for does not exist.", + "paste_modal_confirm_paste": "Confirm Paste", + "paste_modal_delay_between_keys": "Delay between keys", + "paste_modal_delay_out_of_range": "Delay must be between {min} and {max}", + "paste_modal_failed_paste": "Failed to paste text: {error}", + "paste_modal_invalid_chars_intro": "The following characters won't be pasted:", + "paste_modal_paste_from_host": "Paste from host", + "paste_modal_paste_text_description": "Paste text from your client to the remote host", + "paste_modal_paste_text": "Paste text", + "paste_modal_sending_using_layout": "Sending text using keyboard layout: {iso}-{name}", + "peer_connection_closed": "Closed", + "peer_connection_closing": "Closing", + "peer_connection_connected": "Connected", + "peer_connection_connecting": "Connecting", + "peer_connection_disconnected": "Disconnected", + "peer_connection_error": "Connection error", + "peer_connection_failed": "Connection failed", + "peer_connection_new": "Connecting", + "previous": "Previous", + "register_device_error": "There was an error {error} registering your device.", + "register_device_finish_button": "Finish Setup", + "register_device_name_description": "Name your device so you can easily identify it later. You can change this name at any time.", + "register_device_name_label": "Device Name", + "register_device_name_placeholder": "Plex Media Server", + "register_device_no_name": "Please specify a name", + "rename_device_button": "Rename Device", + "rename_device_description": "Properly name your device to easily identify it.", + "rename_device_error": "There was an error {error} renaming your device.", + "rename_device_headline": "Rename {name}", + "rename_device_new_name_label": "New device name", + "rename_device_new_name_placeholder": "Plex Media Server", + "rename_device_no_name": "Please specify a name", "rename": "Rename", - "saving": "Saving...", - "search_placeholder": "Search...", - "something_went_wrong": "Something went wrong. Please try again later or contact support", - "subnet_mask": "Subnet Mask", - "troubleshoot_connection": "Troubleshoot Connection", - "unknown_error": "Unknown error", - "update_in_progress": "Update in Progress", - "updating_leave_device_on": "Please don't turn off your device...", - "usb": "USB", - "view_details": "View Details", - "action_bar_connection_stats": "Connection Stats", - "action_bar_exit_fullscreen": "Exit Fullscreen", - "action_bar_extension": "Extension", - "action_bar_fullscreen": "Fullscreen", - "action_bar_paste_text": "Paste text", - "action_bar_settings": "Settings", - "action_bar_virtual_keyboard": "Virtual Keyboard", - "action_bar_virtual_media": "Virtual Media", - "action_bar_wake_on_lan": "Wake on LAN", - "action_bar_web_terminal": "Web Terminal", - "extension_popover_load_and_manage_extensions": "Load and manage your extensions", - "extension_popover_set_error_notification": "Failed to set active extension: {error}", - "extension_popover_unload_extension": "Unload Extension", - "extension_serial_console_description": "Access your serial console extension", - "extension_serial_console": "Serial Console", - "extensions_atx_power_control_description": "Control the power state of your machine via ATX power control.", - "extensions_atx_power_control": "ATX Power Control", - "extensions_dc_power_control_description": "Control your DC Power extension", - "extensions_dc_power_control": "DC Power Control", - "extensions_popover_extensions": "Extensions", - "atx_power_control_get_state_error": "Failed to get ATX power state: {error}", - "atx_power_control_hdd_led": "HDD LED", - "atx_power_control_long_power_button": "Long Press", - "atx_power_control_power_button": "Power", - "atx_power_control_power_led": "Power LED", - "atx_power_control_reset_button": "Reset", - "atx_power_control_send_action_error": "Failed to send ATX power action {action}: {error}", - "atx_power_control_short_power_button": "Short Press", - "dc_power_control_current_unit": "A", - "dc_power_control_current": "Current", - "dc_power_control_get_state_error": "Failed to get DC power state: {error}", - "dc_power_control_power_off_button": "Power Off", - "dc_power_control_power_off_state": "Power OFF", - "dc_power_control_power_on_button": "Power On", - "dc_power_control_power_on_state": "Power ON", - "dc_power_control_power_unit": "W", - "dc_power_control_power": "Power", - "dc_power_control_restore_last_state": "Last State", - "dc_power_control_restore_power_state": "Restore Power Loss", - "dc_power_control_set_power_state_error": "Failed to send DC power state to {enabled}: {error}", - "dc_power_control_set_restore_state_error": "Failed to send DC power restore state to {state}: {error}", - "dc_power_control_voltage_unit": "V", - "dc_power_control_voltage": "Voltage", + "saving": "Saving…", + "search_placeholder": "Search…", "serial_console_baud_rate": "Baud Rate", "serial_console_configure_description": "Configure your serial console settings", "serial_console_data_bits": "Data Bits", @@ -112,191 +352,24 @@ "serial_console_parity": "Parity", "serial_console_set_settings_error": "Failed to set serial console settings to {settings}: {error}", "serial_console_stop_bits": "Stop Bits", - "wake_on_lan_add_device_back": "Back", - "wake_on_lan_add_device_device_name": "Device Name", - "wake_on_lan_add_device_example_device_name": "Plex Media Server", - "wake_on_lan_add_device_mac_address": "MAC Address", - "wake_on_lan_add_device_save_device": "Save Device", - "paste_modal_confirm_paste": "Confirm Paste", - "paste_modal_delay_between_keys": "Delay between keys", - "paste_modal_delay_out_of_range": "Delay must be between {min} and {max}", - "paste_modal_invalid_chars_intro": "The following characters won't be pasted:", - "paste_modal_paste_from_host": "Paste from host", - "paste_modal_paste_text_description": "Paste text from your client to the remote host", - "paste_modal_paste_text": "Paste text", - "paste_modal_sending_using_layout": "Sending text using keyboard layout: {iso}-{name}", - "paste_modal_failed_paste": "Failed to paste text: {error}", - "mount_add_file_to_get_started": "Add a file to get started", - "mount_add_new_media": "Add New Media", - "mount_mounted_from_storage": "Mounted from JetKVM Storage", - "mount_no_mounted_media": "No mounted media", - "mount_streaming_from_url": "Streaming from URL", - "mount_unmount": "Unmount", - "mount_virtual_media_description": "Mount an image to boot from or install an operating system.", - "mount_virtual_media": "Virtual Media", - "mount_get_state_error": "Failed to get virtual media state: {error}", - "mount_mode_cdrom": "CD-ROM", - "mount_mode_disk": "Disk", - "mount_mounted_as": "Mounted as", - "mount_unmount_error": "Failed to unmount image: {error}", - "wake_on_lan_description": "Send a Magic Packet to wake up a remote device.", - "wake_on_lan_device_list_add_new_device": "Add New Device", - "wake_on_lan_device_list_delete_device": "Delete device", - "wake_on_lan_device_list_wake": "Wake", - "wake_on_lan_empty_add_device_to_start": "Add a device to start using Wake-on-LAN", - "wake_on_lan_empty_add_new_device": "Add New Device", - "wake_on_lan_empty_no_devices_added": "No devices added", - "wake_on_lan_failed_add_device": "Failed to add device", - "wake_on_lan_failed_send_magic": "Failed to send Magic Packet", - "wake_on_lan_invalid_mac": "Invalid MAC address", - "wake_on_lan_magic_sent_success": "Magic Packet sent successfully", - "wake_on_lan": "Wake On LAN", - "connection_stats_badge_jitter_buffer_avg_delay": "Jitter Buffer Avg. Delay", - "connection_stats_badge_jitter": "Jitter", - "connection_stats_connection_description": "The connection between the client and the JetKVM.", - "connection_stats_connection": "Connection", - "connection_stats_frames_per_second_description": "Number of inbound video frames displayed per second.", - "connection_stats_frames_per_second": "Frames per second", - "connection_stats_network_stability_description": "How steady the flow of inbound video packets is across the network.", - "connection_stats_network_stability": "Network Stability", - "connection_stats_packets_lost_description": "Count of lost inbound video RTP packets.", - "connection_stats_packets_lost": "Packets Lost", - "connection_stats_playback_delay_description": "Delay added by the jitter buffer to smooth playback when frames arrive unevenly.", - "connection_stats_playback_delay": "Playback Delay", - "connection_stats_round_trip_time_description": "Round-trip time for the active ICE candidate pair between peers.", - "connection_stats_round_trip_time": "Round-Trip Time", - "connection_stats_sidebar": "Connection Stats", - "connection_stats_video_description": "The video stream from the JetKVM to the client.", - "connection_stats_video": "Video", - "peer_connection_connected": "Connected", - "peer_connection_connecting": "Connecting", - "peer_connection_disconnected": "Disconnected", - "peer_connection_error": "Connection error", - "peer_connection_closing": "Closing", - "peer_connection_failed": "Connection failed", - "peer_connection_closed": "Closed", - "peer_connection_new": "Connecting", - "usb_device_keyboard_mouse_and_mass_storage": "Keyboard, Mouse and Mass Storage", - "usb_device_keyboard_only": "Keyboard Only", - "usb_device_custom": "Custom", - "usb_device_title": "USB Device", - "usb_device_description": "USB devices to emulate on the target computer", - "usb_device_classes_title": "Classes", - "usb_device_classes_description": "USB device classes in the composite device", - "usb_device_enable_keyboard_title": "Enable Keyboard", - "usb_device_enable_keyboard_description": "Enable Keyboard", - "usb_device_enable_absolute_mouse_title": "Enable Absolute Mouse (Pointer)", - "usb_device_enable_absolute_mouse_description": "Enable Absolute Mouse (Pointer)", - "usb_device_enable_relative_mouse_title": "Enable Relative Mouse", - "usb_device_enable_relative_mouse_description": "Enable Relative Mouse", - "usb_device_enable_mass_storage_title": "Enable USB Mass Storage", - "usb_device_enable_mass_storage_description": "Sometimes it might need to be disabled to prevent issues with certain devices", - "usb_device_update_classes": "Update USB Classes", - "usb_device_restore_default": "Restore to Default", - "usb_device_failed_load": "Failed to load USB devices: {error}", - "usb_device_failed_set": "Failed to set USB devices: {error}", - "usb_device_updated": "USB Devices updated", + "serial_console": "Serial Console", + "setting_remote_description": "Setting remote description", + "setting_remote_session_description": "Setting remote session description...", + "setting_up_connection_to_device": "Setting up connection to device...", + "something_went_wrong": "Something went wrong. Please try again later or contact support", + "step_counter_step": "Step {step}", + "subnet_mask": "Subnet Mask", + "troubleshoot_connection": "Troubleshoot Connection", + "unknown_error": "Unknown error", + "update_in_progress": "Update in Progress", "updates_failed_check": "Failed to check for updates: {error}", "updates_failed_get_device_version": "Failed to get device version: {error}", - "auth_connect_to_cloud_action": "Log in & Connect device", - "auth_connect_to_cloud_description": "Unlock remote access and advanced features for your device", - "auth_connect_to_cloud": "Connect your JetKVM to the cloud", - "auth_signup_connect_to_cloud_action": "Signup & Connect device", - "auth_login_action": "Log in", - "auth_login_description": "Log in to access and manage your devices securely", - "auth_login": "Log in to your JetKVM account", - "auth_header_cta_already_have_account": "Already have an account?", - "auth_header_cta_dont_have_account": "Don't have an account?", - "auth_header_cta_new_to_jetkvm": "New to JetKVM?", - "auth_signup_create_account_action": "Create Account", - "auth_signup_create_account_description": "Create your account and start managing your devices with ease.", - "auth_signup_create_account": "Create your JetKVM account", - "video_overlay_autoplay_permissions_required": "Autoplay permissions required", - "video_overlay_conn_check_cables": "Check all cable connections for any loose or damaged wires", - "video_overlay_conn_ensure_network": "Ensure your network connection is stable and active", - "video_overlay_conn_restart": "Try restarting both the device and your computer", - "video_overlay_conn_verify_power": "Verify that the device is powered on and properly connected", - "video_overlay_connection_issue_title": "Connection Issue Detected", - "video_overlay_enable_autoplay_settings": "Please adjust browser settings to enable autoplay", - "video_overlay_hdmi_error_title": "HDMI signal error detected.", - "video_overlay_hdmi_incompatible_resolution": "Incompatible resolution or refresh rate settings", - "video_overlay_hdmi_loose_faulty": "A loose or faulty HDMI connection", - "video_overlay_hdmi_source_issue": "Issues with the source device's HDMI output", - "video_overlay_learn_more": "Learn more", - "video_overlay_loading_stream": "Loading video stream...", - "video_overlay_manually_start_stream": "Manually start stream", - "video_overlay_no_hdmi_adapter_compat": "If using an adapter, ensure it's compatible and functioning correctly", - "video_overlay_no_hdmi_ensure_cable": "Ensure the HDMI cable securely connected at both ends", - "video_overlay_no_hdmi_ensure_power": "Ensure source device is powered on and outputting a signal", - "video_overlay_no_hdmi_signal": "No HDMI signal detected.", - "video_overlay_pointerlock_click_to_enable": "Click on the video to enable mouse control", - "video_overlay_retrying_connection": "Retrying connection...", - "video_overlay_troubleshooting_guide": "Troubleshooting Guide", - "video_overlay_try_again": "Try again", - "video_pointer_lock_enabled": "Pointer lock enabled — press Escape to unlock", - "video_pointer_lock_disabled": "Pointer lock disabled", - "ipv6_information": "IPv6 Information", - "ipv6_link_local": "Link-local", - "ipv6_addresses": "IPv6 Addresses", - "ipv6_address_label": "Address", - "ipv6_valid_lifetime": "Valid Lifetime", - "ipv6_preferred_lifetime": "Preferred Lifetime", - "dhcp_lease_boot_file": "Boot File", - "dhcp_lease_boot_next_server": "Boot Next Server", - "dhcp_lease_boot_server_name": "Boot Server Name", - "dhcp_lease_broadcast": "Broadcast", - "dhcp_lease_domain": "Domain", - "dhcp_lease_gateway": "Gateway", - "dhcp_lease_header": "DHCP Lease Information", - "dhcp_lease_hostname": "Hostname", - "dhcp_lease_lease_expires": "Lease Expires", - "dhcp_lease_maximum_transfer_unit": "MTU", - "dhcp_lease_renew": "Renew DHCP Lease", - "dhcp_lease_time_to_live": "TTL", - "step_counter_step": "Step {step}", - "macro_add_step": "Add Step{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "At least one step must have keys or modifiers", - "macro_at_least_one_step_required": "At least one step is required", - "macro_max_steps_error": "You can only add a maximum of {max} steps per macro.", - "macro_max_steps_reached": "({max} max)", - "macro_name_label": "Macro Name", - "macro_name_required": "Name is required", - "macro_name_too_long": "Name must be less than 50 characters", - "macro_please_fix_validation_errors": "Please fix the validation errors", - "macro_save": "Save Macro", - "macro_save_error": "An error occurred while saving.", - "macro_step_count": "{steps} / {max} steps", - "macro_steps_description": "Keys/modifiers executed in sequence with a delay between each step.", - "macro_steps_label": "Steps", - "macro_step_duration_description": "Time to wait before executing the next step.", - "macro_step_duration_label": "Step Duration", - "macro_step_keys_description": "Maximum {max} keys per step.", - "macro_step_keys_label": "Keys", - "macro_step_max_keys_reached": "Maximum keys reached", - "macro_step_modifiers_description": "What modifiers (Shift/Ctrl/Alt/Meta) are pressed during this step.", - "macro_step_modifiers_label": "Modifiers", - "macro_step_no_matching_keys_found": "No matching keys found", - "macro_step_search_for_key": "Search for key...", - "jiggler_examples_label": "Examples", - "jiggler_more_examples": "More examples", - "jiggler_cron_schedule_label": "Cron Schedule", - "jiggler_cron_schedule_description": "Cron expression for scheduling", - "jiggler_example_business_hours_late": "Business Hours 9-17", - "jiggler_example_business_hours_early": "Business Hours 8-17", - "jiggler_inactivity_limit_label": "Inactivity Limit Seconds", - "jiggler_inactivity_limit_description": "Inactivity time before jiggle", - "jiggler_random_delay_label": "Random delay", - "jiggler_random_delay_description": "To avoid recognizable patterns", - "jiggler_timezone_label": "Timezone", - "jiggler_timezone_description": "Timezone for cron schedule", - "jiggler_save_jiggler_config": "Save Jiggler Config", - "metric_waiting_for_data": "Waiting for data...", - "metric_not_supported": "Metric not supported", + "updating_leave_device_on": "Please don't turn off your device…", "usb_config_custom": "Custom", "usb_config_default": "JetKVM Default", "usb_config_dell": "Dell Multimedia Pro Keyboard", "usb_config_failed_load": "Failed to load USB Config: {error}", - "usb_config_failed_set": "Failed to set usb config: {error}", + "usb_config_failed_set": "Failed to set USB config: {error}", "usb_config_identifiers_description": "USB device identifiers exposed to the target computer", "usb_config_identifiers_title": "Identifiers", "usb_config_logitech": "Logitech Universal Adapter", @@ -314,10 +387,75 @@ "usb_config_update_identifiers": "Update USB Identifiers", "usb_config_vendor_id_label": "Vendor ID", "usb_config_vendor_id_placeholder": "Enter Vendor ID", + "usb_device_classes_description": "USB device classes in the composite device", + "usb_device_classes_title": "Classes", + "usb_device_custom": "Custom", + "usb_device_description": "USB devices to emulate on the target computer", + "usb_device_enable_absolute_mouse_description": "Enable Absolute Mouse (Pointer)", + "usb_device_enable_absolute_mouse_title": "Enable Absolute Mouse (Pointer)", + "usb_device_enable_keyboard_description": "Enable Keyboard", + "usb_device_enable_keyboard_title": "Enable Keyboard", + "usb_device_enable_mass_storage_description": "Sometimes it might need to be disabled to prevent issues with certain devices", + "usb_device_enable_mass_storage_title": "Enable USB Mass Storage", + "usb_device_enable_relative_mouse_description": "Enable Relative Mouse", + "usb_device_enable_relative_mouse_title": "Enable Relative Mouse", + "usb_device_failed_load": "Failed to load USB devices: {error}", + "usb_device_failed_set": "Failed to set USB devices: {error}", + "usb_device_keyboard_mouse_and_mass_storage": "Keyboard, Mouse and Mass Storage", + "usb_device_keyboard_only": "Keyboard Only", + "usb_device_restore_default": "Restore to Default", + "usb_device_title": "USB Device", + "usb_device_update_classes": "Update USB Classes", + "usb_device_updated": "USB Devices updated", "usb_state_connected": "Connected", "usb_state_connecting": "Connecting", "usb_state_disconnected": "Disconnected", "usb_state_low_power_mode": "Low power mode", + "usb": "USB", + "video_overlay_autoplay_permissions_required": "Autoplay permissions required", + "video_overlay_conn_check_cables": "Check all cable connections for any loose or damaged wires", + "video_overlay_conn_ensure_network": "Ensure your network connection is stable and active", + "video_overlay_conn_restart": "Try restarting both the device and your computer", + "video_overlay_conn_verify_power": "Verify that the device is powered on and properly connected", + "video_overlay_connection_issue_title": "Connection Issue Detected", + "video_overlay_enable_autoplay_settings": "Please adjust browser settings to enable autoplay", + "video_overlay_hdmi_error_title": "HDMI signal error detected.", + "video_overlay_hdmi_incompatible_resolution": "Incompatible resolution or refresh rate settings", + "video_overlay_hdmi_loose_faulty": "A loose or faulty HDMI connection", + "video_overlay_hdmi_source_issue": "Issues with the source device's HDMI output", + "video_overlay_learn_more": "Learn more", + "video_overlay_loading_stream": "Loading video stream…", + "video_overlay_manually_start_stream": "Manually start stream", + "video_overlay_no_hdmi_adapter_compat": "If using an adapter, ensure it's compatible and functioning correctly", + "video_overlay_no_hdmi_ensure_cable": "Ensure the HDMI cable securely connected at both ends", + "video_overlay_no_hdmi_ensure_power": "Ensure source device is powered on and outputting a signal", + "video_overlay_no_hdmi_signal": "No HDMI signal detected.", + "video_overlay_pointerlock_click_to_enable": "Click on the video to enable mouse control", + "video_overlay_retrying_connection": "Retrying connection…", + "video_overlay_troubleshooting_guide": "Troubleshooting Guide", + "video_overlay_try_again": "Try again", + "video_pointer_lock_disabled": "Pointer lock disabled", + "video_pointer_lock_enabled": "Pointer lock enabled — press Escape to unlock", + "view_details": "View Details", + "virtual_keyboard_description": "Use the virtual keyboard to send special keys or key combinations to the remote computer.", "virtual_keyboard_header": "Virtual Keyboard", - "virtual_keyboard_description": "Use the virtual keyboard to send special keys or key combinations to the remote computer." + "wake_on_lan_add_device_back": "Back", + "wake_on_lan_add_device_device_name": "Device Name", + "wake_on_lan_add_device_example_device_name": "Plex Media Server", + "wake_on_lan_add_device_mac_address": "MAC Address", + "wake_on_lan_add_device_save_device": "Save Device", + "wake_on_lan_description": "Send a Magic Packet to wake up a remote device.", + "wake_on_lan_device_list_add_new_device": "Add New Device", + "wake_on_lan_device_list_delete_device": "Delete device", + "wake_on_lan_device_list_wake": "Wake", + "wake_on_lan_empty_add_device_to_start": "Add a device to start using Wake-on-LAN", + "wake_on_lan_empty_add_new_device": "Add New Device", + "wake_on_lan_empty_no_devices_added": "No devices added", + "wake_on_lan_failed_add_device": "Failed to add device", + "wake_on_lan_failed_send_magic": "Failed to send Magic Packet", + "wake_on_lan_invalid_mac": "Invalid MAC address", + "wake_on_lan_magic_sent_success": "Magic Packet sent successfully", + "wake_on_lan": "Wake On LAN", + "welcome_to_jetkvm_description": "Control any computer remotely", + "welcome_to_jetkvm": "Welcome to JetKVM" } \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index b900f0a8..677f894b 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "kvm-ui", - "version": "2025.10.07.1700", + "version": "2025.10.09.0200", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kvm-ui", - "version": "2025.10.07.1700", + "version": "2025.10.09.0200", "dependencies": { "@headlessui/react": "^2.2.9", "@headlessui/tailwindcss": "^0.2.2", @@ -30,8 +30,8 @@ "react-dom": "^19.2.0", "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", - "react-router": "^7.9.3", - "react-simple-keyboard": "^3.8.126", + "react-router": "^7.9.4", + "react-simple-keyboard": "^3.8.127", "react-use-websocket": "^4.13.0", "react-xtermjs": "^1.0.10", "recharts": "^3.2.1", @@ -65,7 +65,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-react-refresh": "^0.4.23", "globals": "^16.4.0", "postcss": "^8.5.6", @@ -3065,9 +3065,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz", - "integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==", + "version": "2.8.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.14.tgz", + "integrity": "sha512-GM9c0cWWR8Ga7//Ves/9KRgTS8nLausCkP3CGiFLrnwA2CDUluXgaQqvrULoR2Ujrd/mz/lkX87F5BHFsNr5sQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3188,9 +3188,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001748", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", + "version": "1.0.30001749", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz", + "integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==", "dev": true, "funding": [ { @@ -3671,9 +3671,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.232", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz", - "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==", + "version": "1.5.233", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz", + "integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==", "dev": true, "license": "ISC" }, @@ -3862,9 +3862,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", - "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.40.0.tgz", + "integrity": "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==", "license": "MIT", "workspaces": [ "docs", @@ -4166,14 +4166,15 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.1.tgz", - "integrity": "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz", + "integrity": "sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", "zod": "^3.22.4 || ^4.0.0", "zod-validation-error": "^3.0.3 || ^4.0.0" }, @@ -4860,6 +4861,23 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/human-id": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.2.tgz", @@ -6458,9 +6476,9 @@ } }, "node_modules/react-router": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", - "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -6480,9 +6498,9 @@ } }, "node_modules/react-simple-keyboard": { - "version": "3.8.126", - "resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.126.tgz", - "integrity": "sha512-eULRTm9Rrvo72+WnU9h3fTooSLMgJ52Yi5VvVm0SirMKQnigRgOlcx1VJeVdFVN4JfJEuSN1voV+Vsmeheg3vA==", + "version": "3.8.127", + "resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.127.tgz", + "integrity": "sha512-CncdXLnJ3tBlB6iEHtkgj5W21ns/DdKKO1bCy9pLWey5xONf+KAComVVnDsnAaC0b4LLI7frWBDjOT01vj8dew==", "license": "MIT", "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", @@ -6758,9 +6776,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { diff --git a/ui/package.json b/ui/package.json index 1b11d65f..1ec23a71 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "kvm-ui", "private": true, - "version": "2025.10.07.1700", + "version": "2025.10.09.0200", "type": "module", "engines": { "node": "^22.15.0" @@ -43,8 +43,8 @@ "react-dom": "^19.2.0", "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", - "react-router": "^7.9.3", - "react-simple-keyboard": "^3.8.126", + "react-router": "^7.9.4", + "react-simple-keyboard": "^3.8.127", "react-use-websocket": "^4.13.0", "react-xtermjs": "^1.0.10", "recharts": "^3.2.1", @@ -75,10 +75,10 @@ "@vitejs/plugin-react-swc": "^4.1.0", "autoprefixer": "^10.4.21", "eslint": "^9.37.0", - "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-react-refresh": "^0.4.23", "globals": "^16.4.0", "postcss": "^8.5.6", diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 1c34a6b8..456a8385 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -4,8 +4,8 @@ import { LuCable, LuHardDrive, LuMaximize, LuSettings, LuSignal } from "react-ic import { FaKeyboard } from "react-icons/fa6"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import { CommandLineIcon } from "@heroicons/react/20/solid"; -import { cx } from "@/cva.config"; +import { cx } from "@/cva.config"; import { useHidStore, useMountMediaStore, diff --git a/ui/src/components/Button.tsx b/ui/src/components/Button.tsx index eba9a964..fcb0a614 100644 --- a/ui/src/components/Button.tsx +++ b/ui/src/components/Button.tsx @@ -1,7 +1,7 @@ import React, { JSX } from "react"; import { Link, type FetcherWithComponents, type LinkProps, useNavigation } from "react-router"; -import { cva, cx } from "@/cva.config"; +import { cva, cx } from "@/cva.config"; import ExtLink from "@components/ExtLink"; import LoadingSpinner from "@components/LoadingSpinner"; diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx index 2d9dd492..22e456e2 100644 --- a/ui/src/components/Header.tsx +++ b/ui/src/components/Header.tsx @@ -3,9 +3,9 @@ import { useNavigate } from "react-router"; import { ArrowLeftEndOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/16/solid"; import { Button, Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; import { LuMonitorSmartphone } from "react-icons/lu"; + import LogoBlueIcon from "@assets/logo-blue.svg"; import LogoWhiteIcon from "@assets/logo-white.svg"; - import { useHidStore, useRTCStore, useUserStore } from "@hooks/stores"; import Card from "@components/Card"; import Container from "@components/Container"; diff --git a/ui/src/components/InfoBar.tsx b/ui/src/components/InfoBar.tsx index 6d5db383..2c4eb9e0 100644 --- a/ui/src/components/InfoBar.tsx +++ b/ui/src/components/InfoBar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { useHidStore, @@ -26,17 +26,8 @@ export default function InfoBar() { (state: VideoState) => `${Math.round(state.width)}x${Math.round(state.height)}`, ); - const { rpcDataChannel } = useRTCStore(); const { debugMode, mouseMode, showPressedKeys } = useSettingsStore(); const { isPasteInProgress } = useHidStore(); - - useEffect(() => { - if (!rpcDataChannel) return; - rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed"); - rpcDataChannel.onerror = (e: Event) => - console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`); - }, [rpcDataChannel]); - const { keyboardLedState, usbState } = useHidStore(); const { isTurnServerInUse } = useRTCStore(); const { hdmiState } = useVideoStore(); diff --git a/ui/src/components/Terminal.tsx b/ui/src/components/Terminal.tsx index 3ba09bfa..7e0d3673 100644 --- a/ui/src/components/Terminal.tsx +++ b/ui/src/components/Terminal.tsx @@ -7,8 +7,8 @@ import { WebLinksAddon } from "@xterm/addon-web-links"; import { WebglAddon } from "@xterm/addon-webgl"; import { Unicode11Addon } from "@xterm/addon-unicode11"; import { ClipboardAddon } from "@xterm/addon-clipboard"; -import { cx } from "@/cva.config"; +import { cx } from "@/cva.config"; import { AvailableTerminalTypes, useUiStore } from "@hooks/stores"; import { Button } from "@components/Button"; import { m } from "@localizations/messages.js"; @@ -54,6 +54,7 @@ const TERMINAL_CONFIG = { // Add these configurations: cursorStyle: "block", rendererType: "canvas", // Ensure we're using the canvas renderer + unicode: { activeVersion: "11" } } as const; function Terminal({ @@ -144,7 +145,6 @@ function Terminal({ instance.loadAddon(new ClipboardAddon()); instance.loadAddon(new Unicode11Addon()); instance.loadAddon(new WebLinksAddon()); - instance.unicode.activeVersion = "11"; if (isWebGl2Supported) { const webGl2Addon = new WebglAddon(); diff --git a/ui/src/components/USBStateStatus.tsx b/ui/src/components/USBStateStatus.tsx index e93e26ec..1f7977fe 100644 --- a/ui/src/components/USBStateStatus.tsx +++ b/ui/src/components/USBStateStatus.tsx @@ -2,7 +2,6 @@ import React from "react"; import { cx } from "@/cva.config"; import KeyboardAndMouseConnectedIcon from "@assets/keyboard-and-mouse-connected.png"; - import { USBStates } from "@hooks/stores"; import { m } from "@localizations/messages.js"; import LoadingSpinner from "@components/LoadingSpinner"; diff --git a/ui/src/components/UpdateInProgressStatusCard.tsx b/ui/src/components/UpdateInProgressStatusCard.tsx index 25db23e7..e29aecff 100644 --- a/ui/src/components/UpdateInProgressStatusCard.tsx +++ b/ui/src/components/UpdateInProgressStatusCard.tsx @@ -1,5 +1,4 @@ import { cx } from "@/cva.config"; - import { useDeviceUiNavigation } from "@hooks/useAppNavigation"; import { Button } from "@components/Button"; import { GridCard } from "@components/Card"; diff --git a/ui/src/components/VirtualKeyboard.tsx b/ui/src/components/VirtualKeyboard.tsx index 37fdd134..f48352fe 100644 --- a/ui/src/components/VirtualKeyboard.tsx +++ b/ui/src/components/VirtualKeyboard.tsx @@ -3,10 +3,10 @@ import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { AnimatePresence, motion } from "framer-motion"; import Keyboard from "react-simple-keyboard"; import { LuKeyboard } from "react-icons/lu"; + import "react-simple-keyboard/build/css/index.css"; import DetachIconRaw from "@/assets/detach-icon.svg"; import { cx } from "@/cva.config"; - import { useHidStore, useUiStore } from "@hooks/stores"; import useKeyboard from "@hooks/useKeyboard"; import useKeyboardLayout from "@hooks/useKeyboardLayout"; diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index c121ba3f..a14e61db 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useResizeObserver } from "usehooks-ts"; -import { cx } from "@/cva.config"; +import { cx } from "@/cva.config"; import useKeyboard from "@hooks/useKeyboard"; import useMouse from "@hooks/useMouse"; import { @@ -234,6 +234,18 @@ export default function WebRTCVideo() { [getMouseWheelHandler], ); + function getAdjustedKeyCode(e: KeyboardEvent) { + const key = e.key; + let code = e.code; + + if (code == "IntlBackslash" && ["`", "~"].includes(key)) { + code = "Backquote"; + } else if (code == "Backquote" && ["§", "±"].includes(key)) { + code = "IntlBackslash"; + } + return code; + } + const keyDownHandler = useCallback( (e: KeyboardEvent) => { e.preventDefault(); @@ -468,17 +480,6 @@ export default function WebRTCVideo() { }; }, [videoSaturation, videoBrightness, videoContrast]); - function getAdjustedKeyCode(e: KeyboardEvent) { - const key = e.key; - let code = e.code; - - if (code == "IntlBackslash" && ["`", "~"].includes(key)) { - code = "Backquote"; - } else if (code == "Backquote" && ["§", "±"].includes(key)) { - code = "IntlBackslash"; - } - return code; - } return (
diff --git a/ui/src/hooks/useHidRpc.ts b/ui/src/hooks/useHidRpc.ts index 7fb57c20..3c08d6d6 100644 --- a/ui/src/hooks/useHidRpc.ts +++ b/ui/src/hooks/useHidRpc.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo } from "react"; -import { useRTCStore } from "@/hooks/stores"; +import { useRTCStore } from "@hooks/stores"; import { CancelKeyboardMacroReportMessage, diff --git a/ui/src/hooks/useJsonRpc.ts b/ui/src/hooks/useJsonRpc.ts index a77c9ce8..2ff862b9 100644 --- a/ui/src/hooks/useJsonRpc.ts +++ b/ui/src/hooks/useJsonRpc.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -import { useRTCStore } from "@/hooks/stores"; +import { useRTCStore } from "@hooks/stores"; export interface JsonRpcRequest { jsonrpc: string; diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index 57e061e2..c29eeb1b 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -149,67 +149,7 @@ export default function useKeyboard() { } }, [rpcHidReady, sendKeyboardEventHidRpc, handleLegacyKeyboardReport, cancelKeepAlive]); - // handleKeyPress is used to handle a key press or release event. - // This function handle both key press and key release events. - // It checks if the keyPressReport API is available and sends the key press event. - // If the keyPressReport API is not available, it simulates the device-side key - // handling for legacy devices and updates the keysDownState accordingly. - // It then sends the full keyboard state to the device. - - const sendKeypress = useCallback( - (key: number, press: boolean) => { - cancelKeepAlive(); - - sendKeypressEventHidRpc(key, press); - - if (press) { - scheduleKeepAlive(); - } - }, - [sendKeypressEventHidRpc, scheduleKeepAlive, cancelKeepAlive], - ); - - const handleKeyPress = useCallback( - async (key: number, press: boolean) => { - if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return; - if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings) - - if (rpcHidReady) { - // if the keyPress api is available, we can just send the key press event - // sendKeypressEvent is used to send a single key press/release event to the device. - // It sends the key and whether it is pressed or released. - // Older device version doesn't support this API, so we will switch to local key handling - // In that case we will switch to local key handling and update the keysDownState - // in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices. - sendKeypress(key, press); - } else { - // Older backends don't support the hidRpc API, so we need: - // 1. Calculate the state - // 2. Send the newly calculated state to the device - const downState = simulateDeviceSideKeyHandlingForLegacyDevices( - keysDownState, - key, - press, - ); - - handleLegacyKeyboardReport(downState.keys, downState.modifier); - - // if we just sent ErrorRollOver, reset to empty state - if (downState.keys[0] === hidErrorRollOver) { - resetKeyboardState(); - } - } - }, - [ - rpcDataChannel?.readyState, - rpcHidReady, - keysDownState, - handleLegacyKeyboardReport, - resetKeyboardState, - sendKeypress, - ], - ); - + // IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists function simulateDeviceSideKeyHandlingForLegacyDevices( state: KeysDownState, @@ -273,6 +213,66 @@ export default function useKeyboard() { return { modifier: modifiers, keys }; } + const sendKeypress = useCallback( + (key: number, press: boolean) => { + cancelKeepAlive(); + + sendKeypressEventHidRpc(key, press); + + if (press) { + scheduleKeepAlive(); + } + }, + [sendKeypressEventHidRpc, scheduleKeepAlive, cancelKeepAlive], + ); + + // handleKeyPress is used to handle a key press or release event. + // This function handle both key press and key release events. + // It checks if the keyPressReport API is available and sends the key press event. + // If the keyPressReport API is not available, it simulates the device-side key + // handling for legacy devices and updates the keysDownState accordingly. + // It then sends the full keyboard state to the device. + const handleKeyPress = useCallback( + async (key: number, press: boolean) => { + if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return; + if ((key || 0) === 0) return; // ignore zero key presses (they are bad mappings) + + if (rpcHidReady) { + // if the keyPress api is available, we can just send the key press event + // sendKeypressEvent is used to send a single key press/release event to the device. + // It sends the key and whether it is pressed or released. + // Older device version doesn't support this API, so we will switch to local key handling + // In that case we will switch to local key handling and update the keysDownState + // in client/browser-side code using simulateDeviceSideKeyHandlingForLegacyDevices. + sendKeypress(key, press); + } else { + // Older backends don't support the hidRpc API, so we need: + // 1. Calculate the state + // 2. Send the newly calculated state to the device + const downState = simulateDeviceSideKeyHandlingForLegacyDevices( + keysDownState, + key, + press, + ); + + handleLegacyKeyboardReport(downState.keys, downState.modifier); + + // if we just sent ErrorRollOver, reset to empty state + if (downState.keys[0] === hidErrorRollOver) { + resetKeyboardState(); + } + } + }, + [ + rpcDataChannel?.readyState, + rpcHidReady, + keysDownState, + handleLegacyKeyboardReport, + resetKeyboardState, + sendKeypress, + ], + ); + // Cleanup function to cancel keepalive timer const cleanup = useCallback(() => { cancelKeepAlive(); diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 5479eef0..378a8d91 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -117,7 +117,7 @@ if (isOnDevice) { path: "/", errorElement: , element: , - HydrateFallback: () =>
Loading...
, + HydrateFallback: () =>
{m.loading()}
, loader: DeviceRoute.loader, children: [ { @@ -391,14 +391,12 @@ document.addEventListener("DOMContentLoaded", () => { // eslint-disable-next-line react-refresh/only-export-components function ErrorBoundary() { const error = useRouteError(); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const errorMessage = error?.data?.error?.message || error?.message; if (isRouteErrorResponse(error)) { if (error.status === 404) return ; } + const errorMessage: string | null = error?.data?.error?.message ?? error?.message ?? null; + return (
diff --git a/ui/src/notifications.tsx b/ui/src/notifications.tsx index 257cff14..c635e3df 100644 --- a/ui/src/notifications.tsx +++ b/ui/src/notifications.tsx @@ -1,8 +1,8 @@ -import toast, { Toast, Toaster, useToasterStore } from "react-hot-toast"; import React, { useEffect } from "react"; +import toast, { Toast, Toaster, useToasterStore } from "react-hot-toast"; import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid"; -import Card from "@/components/Card"; +import Card from "@components/Card"; interface NotificationOptions { duration?: number; diff --git a/ui/src/routes/devices.$id.deregister.tsx b/ui/src/routes/devices.$id.deregister.tsx index e5dd2a35..2ae40783 100644 --- a/ui/src/routes/devices.$id.deregister.tsx +++ b/ui/src/routes/devices.$id.deregister.tsx @@ -2,14 +2,15 @@ import { Form, redirect, useActionData, useLoaderData } from "react-router"; import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "react-router"; import { ChevronLeftIcon } from "@heroicons/react/16/solid"; +import { User } from "@hooks/stores"; import { Button, LinkButton } from "@components/Button"; import Card from "@components/Card"; import { CardHeader } from "@components/CardHeader"; import DashboardNavbar from "@components/Header"; -import { User } from "@/hooks/stores"; -import { checkAuth } from "@/main"; import Fieldset from "@components/Fieldset"; +import { checkAuth } from "@/main"; import { CLOUD_API } from "@/ui.config"; +import { m } from "@localizations/messages.js"; interface LoaderData { device: { id: string; name: string; user: { googleId: string } }; @@ -28,11 +29,12 @@ const action: ActionFunction = async ({ request }: ActionFunctionArgs) => { }); if (!res.ok) { - return { message: "There was an error deregistering your device. Please try again." }; + return { message: m.deregister_error({ status: res.statusText }) }; } } catch (e) { console.error(e); - return { message: "There was an error deregistering your device. Please try again." }; + const message = e instanceof Error ? e.message : String(e); + return { message: m.deregister_error({ status: message }) }; } return redirect("/devices"); @@ -68,7 +70,7 @@ export default function DevicesIdDeregister() {
- This will remove the device from your cloud account and revoke - remote access to it. -
- Please note that local access will still be possible - - } + headline={m.deregister_headline({ device: device.name || device.id })} + description={m.deregister_description()} />
@@ -107,20 +102,20 @@ export default function DevicesIdDeregister() { size="MD" theme="light" to="/devices" - text="Cancel" + text={m.cancel()} textAlign="center" />
{error?.message && (

- {error?.message} + {m.deregister_error({ status: error.message })}

)} diff --git a/ui/src/routes/devices.$id.mount.tsx b/ui/src/routes/devices.$id.mount.tsx index 4672ef99..5c6afa46 100644 --- a/ui/src/routes/devices.$id.mount.tsx +++ b/ui/src/routes/devices.$id.mount.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useNavigate } from "react-router"; import { LuLink, LuRadioReceiver, @@ -7,28 +8,28 @@ import { } from "react-icons/lu"; import { PlusCircleIcon, ExclamationTriangleIcon } from "@heroicons/react/20/solid"; import { TrashIcon } from "@heroicons/react/16/solid"; -import { useNavigate } from "react-router"; +import DebianIcon from "@assets/debian-icon.png"; +import UbuntuIcon from "@assets/ubuntu-icon.png"; +import FedoraIcon from "@assets/fedora-icon.png"; +import OpenSUSEIcon from "@assets/opensuse-icon.png"; +import ArchIcon from "@assets/arch-icon.png"; +import NetBootIcon from "@assets/netboot-icon.svg"; +import LogoBlueIcon from "@assets/logo-blue.svg"; +import LogoWhiteIcon from "@assets/logo-white.svg"; +import { cx } from "@/cva.config"; -import Card, { GridCard } from "@/components/Card"; -import { Button } from "@components/Button"; -import LogoBlueIcon from "@/assets/logo-blue.svg"; -import LogoWhiteIcon from "@/assets/logo-white.svg"; -import { formatters } from "@/utils"; +import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc"; import AutoHeight from "@components/AutoHeight"; +import { Button } from "@components/Button"; +import Card, { GridCard } from "@components/Card"; +import Fieldset from "@components/Fieldset"; import { InputFieldWithLabel } from "@/components/InputField"; -import DebianIcon from "@/assets/debian-icon.png"; -import UbuntuIcon from "@/assets/ubuntu-icon.png"; -import FedoraIcon from "@/assets/fedora-icon.png"; -import OpenSUSEIcon from "@/assets/opensuse-icon.png"; -import ArchIcon from "@/assets/arch-icon.png"; -import NetBootIcon from "@/assets/netboot-icon.svg"; -import Fieldset from "@/components/Fieldset"; +import { formatters } from "@/utils"; import { DEVICE_API } from "@/ui.config"; +import { isOnDevice } from "@/main"; +import notifications from "@/notifications"; +import { m } from "@localizations/messages.js"; -import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc"; -import notifications from "../notifications"; -import { isOnDevice } from "../main"; -import { cx } from "../cva.config"; import { MountMediaState, RemoteVirtualMediaState, @@ -145,12 +146,12 @@ export function Dialog({ onClose }: { onClose: () => void }) {
JetKVM Logo JetKVM Logo {modalView === "mode" && ( @@ -238,26 +239,26 @@ function ModeSelectionView({

- Virtual Media Source + {m.mount_virtual_media_source()}

- Choose how you want to mount your virtual media + {m.mount_virtual_media_source_description()}
{[ { - label: "URL Mount", + label: m.mount_url_mount(), value: "url", - description: "Mount files from any public web address", + description: m.mount_url_description(), icon: LuLink, - tag: "Experimental", + tag: m.experimental(), disabled: false, }, { - label: "JetKVM Storage Mount", + label: m.mount_jetkvm_storage(), value: "device", - description: "Mount previously uploaded files from the JetKVM storage", + description: m.mount_jetkvm_storage_description(), icon: LuRadioReceiver, tag: null, disabled: false, @@ -332,7 +333,7 @@ function ModeSelectionView({ onClick={() => { setModalView(selectedMode); }} - text="Continue" + text={m.continue()} />
@@ -410,8 +411,8 @@ function UrlView({ return (
handleUrlChange(e.target.value)} @@ -440,12 +441,12 @@ function UrlView({
-
@@ -553,7 +554,7 @@ function DeviceFileView({ const syncStorage = useCallback(() => { send("listStorageFiles", {}, (resp: JsonRpcResponse) => { if ("error" in resp) { - notifications.error(`Error listing storage files: ${resp.error}`); + notifications.error(m.mount_error_list_storage({ error: resp.error })); return; } const { files } = resp.result as StorageFiles; @@ -568,7 +569,7 @@ function DeviceFileView({ send("getStorageSpace", {}, (resp: JsonRpcResponse) => { if ("error" in resp) { - notifications.error(`Error getting storage space: ${resp.error}`); + notifications.error(m.mount_error_get_storage_space({ error: resp.error })); return; } @@ -597,7 +598,7 @@ function DeviceFileView({ console.log("Deleting file:", file); send("deleteStorageFile", { filename: file.name }, (resp: JsonRpcResponse) => { if ("error" in resp) { - notifications.error(`Error deleting file: ${resp.error}`); + notifications.error(m.mount_error_delete_file({ error: resp.error })); return; } @@ -630,8 +631,8 @@ function DeviceFileView({ return (

- No images available + {m.mount_no_images_title()}

- Upload an image to start virtual media mounting. + {m.mount_no_images_description()}

@@ -677,9 +678,7 @@ function DeviceFileView({ const selectedFile = onStorageFiles.find(f => f.name === file.name); if (!selectedFile) return; if ( - window.confirm( - "Are you sure you want to delete " + selectedFile.name + "?", - ) + window.confirm(m.mount_confirm_delete({ name: selectedFile.name })) ) { handleDeleteFile(selectedFile); } @@ -692,24 +691,24 @@ function DeviceFileView({ {onStorageFiles.length > filesPerPage && (

- Showing {indexOfFirstFile + 1} to{" "} - - {Math.min(indexOfLastFile, onStorageFiles.length)} - {" "} - of {onStorageFiles.length} results + {m.mount_button_showing_results({ + from: indexOfFirstFile + 1, + to: Math.min(indexOfLastFile, onStorageFiles.length), + total: onStorageFiles.length + })}

@@ -806,7 +805,7 @@ function DeviceFileView({ size="MD" theme="light" fullWidth - text="Upload a new image" + text={m.mount_button_upload_new_image()} onClick={() => onNewImageClick()} />
@@ -862,7 +861,7 @@ function UploadFileView({ if (!rtcDataChannel) { console.error("Failed to create data channel for file upload"); - notifications.error("Failed to create data channel for file upload"); + notifications.error(m.mount_upload_failed_datachannel()); setUploadState("idle"); console.log("Upload state set to 'idle'"); @@ -952,7 +951,7 @@ function UploadFileView({ rtcDataChannel.onerror = error => { console.error("RTC Data channel error:", error); - notifications.error(`Upload failed: ${error}`); + notifications.error(m.mount_upload_failed_rtc({ error: error })); setUploadState("idle"); console.log("Upload state set to 'idle'"); }; @@ -1037,7 +1036,7 @@ function UploadFileView({ file.name !== incompleteFileName.replace(".incomplete", "") ) { setFileError( - `Please select the file "${incompleteFileName.replace(".incomplete", "")}" to continue the upload.`, + m.mount_please_select_file({ name: incompleteFileName.replace(".incomplete", "") }), ); return; } @@ -1080,11 +1079,11 @@ function UploadFileView({ return (

{incompleteFileName - ? `Click to select "${incompleteFileName.replace(".incomplete", "")}"` - : "Click to select a file"} + ? m.mount_click_to_select_incomplete({ name: incompleteFileName.replace(".incomplete", "") }) + : m.mount_click_to_select_file()}

- Supported formats: ISO, IMG + {m.mount_supported_formats()}

)} @@ -1140,7 +1139,7 @@ function UploadFileView({

- Uploading {formatters.truncateMiddle(uploadedFileName, 30)} + {m.mount_uploading_with_name({ name: formatters.truncateMiddle(uploadedFileName, 30) })}

{formatters.bytes(uploadedFileSize || 0)} @@ -1153,11 +1152,11 @@ function UploadFileView({ >

- Uploading... + {m.mount_uploading()} {uploadSpeed !== null ? `${formatters.bytes(uploadSpeed)}/s` - : "Calculating..."} + : m.mount_calculating()}
@@ -1174,11 +1173,10 @@ function UploadFileView({

- Upload successful + {m.mount_upload_successful()}

- {formatters.truncateMiddle(uploadedFileName, 40)} has been - uploaded + {m.mount_uploaded_has_been_uploaded({ name: formatters.truncateMiddle(uploadedFileName, 40) })}

)} @@ -1205,7 +1203,7 @@ function UploadFileView({ className="mt-2 animate-fadeIn truncate text-sm text-red-600 dark:text-red-400 opacity-0" style={{ animationDuration: "0.7s" }} > - Error: {uploadError} + {m.mount_upload_error({ error: String(uploadError) })}
)} @@ -1221,7 +1219,7 @@ function UploadFileView({
); @@ -1341,7 +1339,7 @@ function PreUploadedImageItem({ size="XS" theme="light" LeadingIcon={TrashIcon} - text="Delete" + text={m.delete()} onClick={e => { e.stopPropagation(); onDelete(); @@ -1362,7 +1360,7 @@ function PreUploadedImageItem({