From 4e82b8a11cd71a41322e16d5a565e09785bc92e1 Mon Sep 17 00:00:00 2001 From: luckfox-eng29 Date: Thu, 25 Sep 2025 16:51:53 +0800 Subject: [PATCH] Update App version to 0.0.3 --- .gitignore | 2 +- Makefile | 4 +- README.md | 30 + README_CN.md | 30 + config.go | 34 +- internal/usbgadget/udc.go | 20 +- internal/usbgadget/usbgadget.go | 1 + internal/usbgadget/utils.go | 3 + jsonrpc.go | 29 +- ota.go | 2 +- ui/package-lock.json | 405 +++++++++ ui/package.json | 1 + ui/src/components/ActionBar.tsx | 22 +- ui/src/components/DhcpLeaseCard.tsx | 32 +- ui/src/components/Header.tsx | 44 +- ui/src/components/InfoBar.tsx | 16 +- ui/src/components/Ipv6NetworkCard.tsx | 6 +- ui/src/components/KvmCard.tsx | 10 +- ui/src/components/LogDialog.tsx | 6 +- ui/src/components/MacroForm.tsx | 20 +- ui/src/components/MacroStepCard.tsx | 17 +- ui/src/components/SidebarHeader.tsx | 4 +- ui/src/components/USBStateStatus.tsx | 8 + ui/src/components/UsbEpModeSetting.tsx | 42 +- ui/src/components/VideoOverlay.tsx | 61 +- ui/src/components/VirtualKeyboard.tsx | 10 +- ui/src/components/extensions/IOControl.tsx | 15 +- .../components/extensions/SerialConsole.tsx | 16 +- .../components/popovers/ExtensionPopover.tsx | 12 +- ui/src/components/popovers/MountPopover.tsx | 34 +- ui/src/components/popovers/PasteModal.tsx | 17 +- .../popovers/WakeOnLan/AddDeviceForm.tsx | 10 +- .../popovers/WakeOnLan/DeviceList.tsx | 11 +- .../popovers/WakeOnLan/EmptyStateCard.tsx | 11 +- .../components/popovers/WakeOnLan/Index.tsx | 12 +- ui/src/components/sidebar/connectionStats.tsx | 20 +- ui/src/hooks/stores.ts | 8 +- ui/src/locales/en.json | 432 ++++++++++ ui/src/locales/zh.json | 432 ++++++++++ ui/src/routes/devices.$id.mount.tsx | 186 ++-- ui/src/routes/devices.$id.mtp.tsx | 113 +-- .../devices.$id.settings.access._index.tsx | 795 ++++++++++++------ ...devices.$id.settings.access.local-auth.tsx | 85 +- .../routes/devices.$id.settings.advanced.tsx | 38 +- .../devices.$id.settings.appearance.tsx | 14 +- .../devices.$id.settings.general._index.tsx | 26 +- .../routes/devices.$id.settings.hardware.tsx | 66 +- .../routes/devices.$id.settings.keyboard.tsx | 32 +- .../devices.$id.settings.macros.add.tsx | 6 +- .../devices.$id.settings.macros.edit.tsx | 13 +- ui/src/routes/devices.$id.settings.macros.tsx | 31 +- ui/src/routes/devices.$id.settings.mouse.tsx | 29 +- .../routes/devices.$id.settings.network.tsx | 47 +- ui/src/routes/devices.$id.settings.tsx | 30 +- ui/src/routes/devices.$id.settings.video.tsx | 41 +- ui/src/routes/welcome-local.mode.tsx | 28 +- ui/src/routes/welcome-local.password.tsx | 31 +- usb.go | 4 + vpn.go | 131 ++- 59 files changed, 2841 insertions(+), 794 deletions(-) create mode 100644 README.md create mode 100644 README_CN.md create mode 100644 ui/src/locales/en.json create mode 100644 ui/src/locales/zh.json diff --git a/.gitignore b/.gitignore index b4ccb54..b9e6268 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,6 @@ static/* package-lock.json package.json node_modules/ - +ui/.i18n_extractor.json device-tests.tar.gz diff --git a/Makefile b/Makefile index 1fd57f5..4325575 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) BUILDDATE ?= $(shell date -u +%FT%T%z) BUILDTS ?= $(shell date -u +%s) REVISION ?= $(shell git rev-parse HEAD) -VERSION_DEV ?= 0.0.2-dev -VERSION ?= 0.0.2 +VERSION_DEV ?= 0.0.3-dev +VERSION ?= 0.0.3 PROMETHEUS_TAG := github.com/prometheus/common/version KVM_PKG_NAME := kvm diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb815c4 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +![luckfox](https://github.com/LuckfoxTECH/luckfox-pico/assets/144299491/cec5c4a5-22b9-4a9a-abb1-704b11651e88) +[中文](./README_CN.md) +# Luckfox PicoKVM +Luckfox PicoKVM is a lightweight IP KVM operation and maintenance tool. It allows remote access to a target device’s display and simulates HID input over the network, enabling contactless operation and management of development boards, PCs, and servers. The product delivers stable, low-latency video capture and remote control, making it well-suited for scenarios such as remote computer management and server maintenance. The software is based on a secondary development of JetKVM. + +## Features +* **Micro SD card support**: Used for software boot settings or storage expansion +* **USB multifunction configuration**: Supports emulating a USB sound card or MTP device. Files in the shared MTP directory can be uploaded or downloaded via HTTP +* **Serial port control**: Connects to the serial port of the controlled device for control and debugging +* **IO level configuration**: Controls expansion IO output (high/low level) +* **1.54-inch touchscreen**: Displays IP address, connection status, and system runtime status +* **Multiple remote access methods**: Remote management via WebRTC through cross-network connections or FRP reverse proxy + +## Compilation +* Compile frontend only + ```bash + make frontend + ``` +* Compile backend only + ```bash + make build_dev + ``` +* Full compilation + ```bash + make build_release + ``` +* Compile to generate **bin/kvm_app**, then upload the file to Luckfox PicoKVM via SSH or MTP, replacing **/userdata/picokvm/bin/kvm_app** + +## Detailed Usage Instructions +[Luckfox PicoKVM](https://wiki.luckfox.com/Luckfox-PicoKVM/) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..7469ef2 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,30 @@ +![luckfox](https://github.com/LuckfoxTECH/luckfox-pico/assets/144299491/cec5c4a5-22b9-4a9a-abb1-704b11651e88) +[English](./README.md) +# Luckfox PicoKVM +Luckfox PicoKVM 是一款轻量级 IP KVM 运维工具,支持通过网络远程获取目标设备画面并模拟 HID 输入,实现对开发板、电脑及服务器等系统的无接触运维管理。该产品具备稳定、低延迟的视频采集和远程控制能力,广泛适用于远程电脑控制和服务器维护等场景。软件基于 JetKVM 二次开发。 + +## 特性 +* **Micro SD 卡支持**:可用于软件启动设置或存储拓展 +* **USB 多功能配置**:支持模拟 USB 声卡或 MTP 设备,在 MTP 共享目录下可通过 HTTP 上传或下载文件 +* **串口控制**:可连接受控设备的串口,实现对受控设备的串口控制和调试 +* **IO 电平配置**:控制拓展 IO 输出高低电平 +* **1.54 寸触控屏幕**:显示 IP 地址、连接状态及系统运行状态 +* **多种远程访问方案**:使用 WebRTC 通过异域组网或 FRP 端口反向代理进行远程管理 + +## 编译 +* 仅编译前端 + ```bash + make frontend + ``` +* 仅编译后端 + ```bash + make build_dev + ``` +* 完整编译 + ```bash + make build_release + ``` +* 编译生成 **bin/kvm_app**,通过 ssh 或 MTP 将文件上传到 Luckfox PicoKVM,替换 **/userdata/picokvm/bin/kvm_app** + +## 详细使用说明 +[Luckfox PicoKVM](https://wiki.luckfox.com/zh/Luckfox-PicoKVM/) \ No newline at end of file diff --git a/config.go b/config.go index fd0cc9e..5f5dd97 100644 --- a/config.go +++ b/config.go @@ -114,6 +114,9 @@ type Config struct { TimeZone string `json:"time_zone"` LEDGreenMode string `json:"led_green_mode"` LEDYellowMode string `json:"led_yellow_mode"` + AutoMountSystemInfo bool `json:"auto_mount_system_info_img"` + EasytierAutoStart bool `json:"easytier_autostart"` + EasytierConfig EasytierConfig `json:"easytier_config"` } const configPath = "/userdata/kvm_config.json" @@ -146,17 +149,18 @@ var defaultConfig = &Config{ Audio: false, //At any given time, only one of Audio and Mtp can be set to true Mtp: false, }, - NetworkConfig: &network.NetworkConfig{}, - DefaultLogLevel: "INFO", - ZeroTierAutoStart: false, - TailScaleAutoStart: false, - TailScaleXEdge: false, - FrpcAutoStart: false, - IO0Status: true, - IO1Status: true, - AudioMode: "disabled", - LEDGreenMode: "network-rx", - LEDYellowMode: "kernel-activity", + NetworkConfig: &network.NetworkConfig{}, + DefaultLogLevel: "INFO", + ZeroTierAutoStart: false, + TailScaleAutoStart: false, + TailScaleXEdge: false, + FrpcAutoStart: false, + IO0Status: true, + IO1Status: true, + AudioMode: "disabled", + LEDGreenMode: "network-rx", + LEDYellowMode: "kernel-activity", + AutoMountSystemInfo: true, } var ( @@ -174,15 +178,16 @@ func LoadConfig() { } // load the default config - config = defaultConfig - if config.UsbConfig.SerialNumber == "" { + if defaultConfig.UsbConfig.SerialNumber == "" { serialNumber, err := extractSerialNumber() if err != nil { logger.Warn().Err(err).Msg("failed to extract serial number") } else { - config.UsbConfig.SerialNumber = serialNumber + defaultConfig.UsbConfig.SerialNumber = serialNumber } } + loadedConfig := *defaultConfig + config = &loadedConfig file, err := os.Open(configPath) if err != nil { @@ -192,7 +197,6 @@ func LoadConfig() { defer file.Close() // load and merge the default config with the user config - loadedConfig := *defaultConfig if err := json.NewDecoder(file).Decode(&loadedConfig); err != nil { logger.Warn().Err(err).Msg("config file JSON parsing failed") os.Remove(configPath) diff --git a/internal/usbgadget/udc.go b/internal/usbgadget/udc.go index 3e058ff..e77e2c1 100644 --- a/internal/usbgadget/udc.go +++ b/internal/usbgadget/udc.go @@ -80,7 +80,15 @@ func (u *UsbGadget) IsUDCBound() (bool, error) { // BindUDC binds the gadget to the UDC. func (u *UsbGadget) BindUDC() error { - err := os.WriteFile(path.Join(udcPath, "bind"), []byte(u.udc), 0644) + err := os.WriteFile(udcPath, []byte(u.udc), 0644) + if err != nil { + return fmt.Errorf("error binding UDC: %w", err) + } + return nil +} + +func (u *UsbGadget) BindUDCToDWC3() error { + err := os.WriteFile(path.Join(dwc3Path, "bind"), []byte(u.udc), 0644) if err != nil { return fmt.Errorf("error binding UDC: %w", err) } @@ -89,7 +97,15 @@ func (u *UsbGadget) BindUDC() error { // UnbindUDC unbinds the gadget from the UDC. func (u *UsbGadget) UnbindUDC() error { - err := os.WriteFile(path.Join(udcPath, " "), []byte(u.udc), 0644) + err := os.WriteFile(udcPath, []byte("none"), 0644) + if err != nil { + return fmt.Errorf("error unbinding UDC: %w", err) + } + return nil +} + +func (u *UsbGadget) UnbindUDCToDWC3() error { + err := os.WriteFile(path.Join(dwc3Path, "unbind"), []byte(u.udc), 0644) if err != nil { return fmt.Errorf("error unbinding UDC: %w", err) } diff --git a/internal/usbgadget/usbgadget.go b/internal/usbgadget/usbgadget.go index aa7bb6a..dcd1b49 100644 --- a/internal/usbgadget/usbgadget.go +++ b/internal/usbgadget/usbgadget.go @@ -84,6 +84,7 @@ type UsbGadget struct { log *zerolog.Logger logSuppressionCounter map[string]int + logLock sync.Mutex } const configFSPath = "/sys/kernel/config" diff --git a/internal/usbgadget/utils.go b/internal/usbgadget/utils.go index 2e9a1d0..363f2f6 100644 --- a/internal/usbgadget/utils.go +++ b/internal/usbgadget/utils.go @@ -82,6 +82,9 @@ func compareFileContent(oldContent []byte, newContent []byte, looserMatch bool) } func (u *UsbGadget) logWithSupression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...interface{}) { + u.logLock.Lock() + defer u.logLock.Unlock() + if _, ok := u.logSuppressionCounter[counterName]; !ok { u.logSuppressionCounter[counterName] = 0 } else { diff --git a/jsonrpc.go b/jsonrpc.go index acafeb6..eb2dc71 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -628,9 +628,9 @@ func rpcGetUsbEmulationState() (bool, error) { func rpcSetUsbEmulationState(enabled bool) error { if enabled { - return gadget.BindUDC() + return gadget.BindUDCToDWC3() } else { - return gadget.UnbindUDC() + return gadget.UnbindUDCToDWC3() } } @@ -663,7 +663,8 @@ func rpcSetWakeOnLanDevices(params SetWakeOnLanDevicesParams) error { } func rpcResetConfig() error { - config = defaultConfig + loadedConfig := *defaultConfig + config = &loadedConfig if err := SaveConfig(); err != nil { return fmt.Errorf("failed to reset config: %w", err) } @@ -1029,6 +1030,18 @@ func rpcGetLedYellowMode() (string, error) { return config.LEDYellowMode, nil } +func rpcGetAutoMountSystemInfo() (bool, error) { + return config.AutoMountSystemInfo, nil +} + +func rpcSetAutoMountSystemInfo(enabled bool) error { + config.AutoMountSystemInfo = enabled + if err := SaveConfig(); err != nil { + return fmt.Errorf("failed to save config: %w", err) + } + return nil +} + var rpcHandlers = map[string]RPCHandler{ "ping": {Func: rpcPing}, "reboot": {Func: rpcReboot, Params: []string{"force"}}, @@ -1084,6 +1097,8 @@ var rpcHandlers = map[string]RPCHandler{ "mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}}, "mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}}, "mountWithSDStorage": {Func: rpcMountWithSDStorage, Params: []string{"filename", "mode"}}, + "setAutoMountSystemInfo": {Func: rpcSetAutoMountSystemInfo, Params: []string{"enabled"}}, + "getAutoMountSystemInfo": {Func: rpcGetAutoMountSystemInfo}, "listStorageFiles": {Func: rpcListStorageFiles}, "deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}}, "startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}}, @@ -1121,7 +1136,7 @@ var rpcHandlers = map[string]RPCHandler{ "getSDMountStatus": {Func: rpcGetSDMountStatus}, "loginTailScale": {Func: rpcLoginTailScale, Params: []string{"xEdge"}}, "logoutTailScale": {Func: rpcLogoutTailScale}, - "canelTailScale": {Func: rpcCanelTailScale}, + "cancelTailScale": {Func: rpcCancelTailScale}, "getTailScaleSettings": {Func: rpcGetTailScaleSettings}, "loginZeroTier": {Func: rpcLoginZeroTier, Params: []string{"networkID"}}, "logoutZeroTier": {Func: rpcLogoutZeroTier, Params: []string{"networkID"}}, @@ -1134,4 +1149,10 @@ var rpcHandlers = map[string]RPCHandler{ "getFrpcStatus": {Func: rpcGetFrpcStatus}, "getFrpcToml": {Func: rpcGetFrpcToml}, "getFrpcLog": {Func: rpcGetFrpcLog}, + "startEasyTier": {Func: rpcStartEasyTier, Params: []string{"name", "secret", "node"}}, + "stopEasyTier": {Func: rpcStopEasyTier}, + "getEasyTierStatus": {Func: rpcGetEasyTierStatus}, + "getEasyTierConfig": {Func: rpcGetEasyTierConfig}, + "getEasyTierLog": {Func: rpcGetEasyTierLog}, + "getEasyTierNodeInfo": {Func: rpcGetEasyTierNodeInfo}, } diff --git a/ota.go b/ota.go index 5f5b0d1..11c6997 100644 --- a/ota.go +++ b/ota.go @@ -59,7 +59,7 @@ var UpdateMetadataUrls = []string{ "https://api.github.com/repos/luckfox-eng29/kvm/releases/latest", } -var builtAppVersion = "0.0.2+dev" +var builtAppVersion = "0.0.3+dev" var updateSource = "github" diff --git a/ui/package-lock.json b/ui/package-lock.json index d549212..746ae69 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -64,6 +64,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", + "i18n-auto-extractor": "^1.2.3", "postcss": "^8.5.3", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", @@ -2344,6 +2345,26 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2744,6 +2765,35 @@ "node": ">=18" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3124,6 +3174,13 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", + "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", + "dev": true, + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -3138,6 +3195,20 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -4013,6 +4084,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -4124,6 +4208,20 @@ "csstype": "^3.0.10" } }, + "node_modules/google-translate-api-x": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/google-translate-api-x/-/google-translate-api-x-10.7.2.tgz", + "integrity": "sha512-GSmbvGMcnULaih2NFgD4Y6840DLAMot90mLWgwoB+FG/QpetyZkFrZkxop8ZxXgOAQXGskFOhGJady8nA6ZJ2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/AidanWelch" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4237,6 +4335,39 @@ "node": ">= 0.4" } }, + "node_modules/i18n-auto-extractor": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/i18n-auto-extractor/-/i18n-auto-extractor-1.2.3.tgz", + "integrity": "sha512-JSthwkUmEtHQmZtzhsjpZGbVWaVekbYmgDoBKRsmQNSrGlVT/eT4OKoKfvpWurHi1fhvKrc6fPTG9yHWAoNp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "enquirer": "^2.4.1", + "google-translate-api-x": "^10.7.2", + "js-md5": "^0.8.3", + "ora": "^8.2.0" + }, + "bin": { + "i18n-auto-extractor": "dist/index.js" + }, + "engines": { + "node": ">=20.11.1" + } + }, + "node_modules/i18n-auto-extractor/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4487,6 +4618,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -4618,6 +4762,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -4707,6 +4864,13 @@ "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", "license": "BSD-3-Clause" }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5075,6 +5239,49 @@ "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5139,6 +5346,19 @@ "node": ">=8.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -5386,6 +5606,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5403,6 +5639,72 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -5971,6 +6273,23 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -6255,6 +6574,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6264,6 +6596,66 @@ "node": ">=0.10.0" } }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -6359,6 +6751,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index caad8ba..dc63733 100644 --- a/ui/package.json +++ b/ui/package.json @@ -75,6 +75,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", + "i18n-auto-extractor": "^1.2.3", "postcss": "^8.5.3", "prettier": "^3.5.3", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/ui/src/components/ActionBar.tsx b/ui/src/components/ActionBar.tsx index 88198c7..d7deec6 100644 --- a/ui/src/components/ActionBar.tsx +++ b/ui/src/components/ActionBar.tsx @@ -22,6 +22,7 @@ import ExtensionPopover from "@/components/popovers/ExtensionPopover"; import { useDeviceUiNavigation } from "@/hooks/useAppNavigation"; import VolumeControl from "./VolumeControl"; import { useJsonRpc } from "@/hooks/useJsonRpc"; +import {useReactAt} from 'i18n-auto-extractor/react' export default function Actionbar({ requestFullscreen, @@ -45,6 +46,7 @@ export default function Actionbar({ const [send] = useJsonRpc(); const audioMode = useAudioModeStore(state => state.audioMode); const setAudioMode = useAudioModeStore(state => state.setAudioMode); + const { $at }= useReactAt(); // This is the only way to get a reliable state change for the popover // at time of writing this there is no mount, or unmount event for the popover @@ -82,7 +84,7 @@ export default function Actionbar({