diff --git a/server/config/default.go b/server/config/default.go index d58ebe5..a841e9e 100644 --- a/server/config/default.go +++ b/server/config/default.go @@ -21,7 +21,7 @@ var defaultConfig = &Config{ }, Stun: "stun.l.google.com:19302", Turn: Turn{ - TurnAddr: "turn.cloudflare.com:3478", + TurnAddr: "", TurnUser: "", TurnCred: "", }, diff --git a/server/proto/vm.go b/server/proto/vm.go index 7529a8e..9581114 100644 --- a/server/proto/vm.go +++ b/server/proto/vm.go @@ -83,3 +83,7 @@ type GetOLEDRsp struct { type GetSSHStateRsp struct { Enabled bool `json:"enabled"` } + +type GetMdnsStateRsp struct { + Enabled bool `json:"enabled"` +} diff --git a/server/router/vm.go b/server/router/vm.go index 1dced36..8e47e26 100644 --- a/server/router/vm.go +++ b/server/router/vm.go @@ -39,4 +39,8 @@ func vmRouter(r *gin.Engine) { api.GET("/vm/ssh", service.GetSSHState) // get SSH state api.POST("/vm/ssh/enable", service.EnableSSH) // enable SSH api.POST("/vm/ssh/disable", service.DisableSSH) // disable SSH + + api.GET("/vm/mdns", service.GetMdnsState) // get mDNS state + api.POST("/vm/mdns/enable", service.EnableMdns) // enable mDNS + api.POST("/vm/mdns/disable", service.DisableMdns) // disable mDNS } diff --git a/server/service/extensions/tailscale/service.go b/server/service/extensions/tailscale/service.go index 2881666..eac2ff3 100644 --- a/server/service/extensions/tailscale/service.go +++ b/server/service/extensions/tailscale/service.go @@ -16,6 +16,8 @@ const ( TailscalePath = "/usr/bin/tailscale" TailscaledPath = "/usr/sbin/tailscaled" ConfigPath = "etc/sysctl.d/99-tailscale.conf" + + GoMemLimit int64 = 75 ) var StateMap = map[string]proto.TailscaleState{ @@ -69,7 +71,7 @@ func (s *Service) Start(c *gin.Context) { } if !utils.IsGoMemLimitExist() { - _ = utils.SetGoMemLimit(50) + _ = utils.SetGoMemLimit(GoMemLimit) } rsp.OkRsp(c) @@ -165,7 +167,7 @@ func (s *Service) Login(c *gin.Context) { } if !utils.IsGoMemLimitExist() { - _ = utils.SetGoMemLimit(50) + _ = utils.SetGoMemLimit(GoMemLimit) } rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{ diff --git a/server/service/network/wol.go b/server/service/network/wol.go index 62c94a7..7240318 100644 --- a/server/service/network/wol.go +++ b/server/service/network/wol.go @@ -33,7 +33,7 @@ func (s *Service) WakeOnLAN(c *gin.Context) { return } - command := fmt.Sprintf("ether-wake %s", mac) + command := fmt.Sprintf("ether-wake -b %s", mac) cmd := exec.Command("sh", "-c", command) output, err := cmd.CombinedOutput() diff --git a/server/service/stream/h264/h264.go b/server/service/stream/h264/h264.go index 70489d7..cd9b691 100644 --- a/server/service/stream/h264/h264.go +++ b/server/service/stream/h264/h264.go @@ -39,7 +39,7 @@ func Connect(c *gin.Context) { var iceServers []webrtc.ICEServer - if conf.Stun != "" { + if conf.Stun != "" && conf.Stun != "disable" { iceServers = append(iceServers, webrtc.ICEServer{ URLs: []string{"stun:" + conf.Stun}, }) @@ -53,11 +53,9 @@ func Connect(c *gin.Context) { }) } - rtc_config := webrtc.Configuration{ + peerConn, err := webrtc.NewPeerConnection(webrtc.Configuration{ ICEServers: iceServers, - } - - peerConn, err := webrtc.NewPeerConnection(rtc_config) + }) if err != nil { log.Errorf("failed to create PeerConnection: %s", err) return diff --git a/server/service/vm/info.go b/server/service/vm/info.go index ebc92c7..71e3062 100644 --- a/server/service/vm/info.go +++ b/server/service/vm/info.go @@ -18,7 +18,7 @@ var imageVersionMap = map[string]string{ "2024-07-23-20-18-587710.img": "v1.1.0", "2024-08-08-19-44-bef2ca.img": "v1.2.0", "2024-11-13-09-59-9c961a.img": "v1.3.0", - "2025-02-17-16-59-3649fe.img": "v1.4.0", + "2025-02-17-19-08-3649fe.img": "v1.4.0", } func (s *Service) GetInfo(c *gin.Context) { @@ -53,6 +53,10 @@ func getIp() string { } func getMdns() string { + if pid := getAvahiDaemonPid(); pid == "" { + return "" + } + content, err := os.ReadFile("/etc/hostname") if err != nil { return "" diff --git a/server/service/vm/mdns.go b/server/service/vm/mdns.go new file mode 100644 index 0000000..b588997 --- /dev/null +++ b/server/service/vm/mdns.go @@ -0,0 +1,91 @@ +package vm + +import ( + "NanoKVM-Server/proto" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" +) + +const ( + AvahiDaemonPid = "/run/avahi-daemon/pid" + AvahiDaemonScript = "/etc/init.d/S50avahi-daemon" + AvahiDaemonBackupScript = "/kvmapp/system/init.d/S50avahi-daemon" +) + +func (s *Service) GetMdnsState(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + + rsp.OkRspWithData(c, &proto.GetMdnsStateRsp{ + Enabled: pid != "", + }) +} + +func (s *Service) EnableMdns(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + if pid != "" { + rsp.OkRsp(c) + return + } + + commands := []string{ + fmt.Sprintf("cp -f %s %s", AvahiDaemonBackupScript, AvahiDaemonScript), + fmt.Sprintf("%s start", AvahiDaemonScript), + } + + command := strings.Join(commands, " && ") + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to start avahi-daemon: %s", err) + rsp.ErrRsp(c, -1, "failed to enable mdns") + return + } + + rsp.OkRsp(c) + log.Debugf("avahi-daemon started") +} + +func (s *Service) DisableMdns(c *gin.Context) { + var rsp proto.Response + + pid := getAvahiDaemonPid() + if pid == "" { + rsp.OkRsp(c) + return + } + + command := fmt.Sprintf("kill -9 %s", pid) + err := exec.Command("sh", "-c", command).Run() + if err != nil { + log.Errorf("failed to stop avahi-daemon: %s", err) + rsp.ErrRsp(c, -1, "failed to disable mdns") + return + } + + _ = os.Remove(AvahiDaemonPid) + + rsp.OkRsp(c) + log.Debugf("avahi-daemon stopped") +} + +func getAvahiDaemonPid() string { + if _, err := os.Stat(AvahiDaemonPid); err != nil { + return "" + } + + content, err := os.ReadFile(AvahiDaemonPid) + if err != nil { + log.Errorf("failed to read mdns pid: %s", err) + return "" + } + + return strings.ReplaceAll(string(content), "\n", "") +} diff --git a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp index 8fd3c63..4ca4314 100644 --- a/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp +++ b/support/sg2002/kvm_system/main/lib/system_state/system_state.cpp @@ -52,7 +52,7 @@ int get_ip_addr(ip_addr_t ip_type) switch (ip_type){ case ETH_IP: // eth_addr if(strcmp(ip_address()["eth0"].c_str(), (char*)kvm_sys_state.eth_addr) != 0){ - if(*(ip_address()["eth0"].c_str()) == NULL){ + if(*(ip_address()["eth0"].c_str()) == 0){ printf("can`t get ip addr\r\n"); kvm_sys_state.eth_addr[0] = 0; return 0; @@ -67,7 +67,7 @@ int get_ip_addr(ip_addr_t ip_type) return 1; case WiFi_IP: // wifi_addr if(strcmp(ip_address()["wlan0"].c_str(), (char*)kvm_sys_state.wifi_addr) != 0){ - if(*(ip_address()["wlan0"].c_str()) == NULL){ + if(*(ip_address()["wlan0"].c_str()) == 0){ printf("can`t get ip addr\r\n"); kvm_sys_state.wifi_addr[0] = 0; return 0; @@ -81,7 +81,7 @@ int get_ip_addr(ip_addr_t ip_type) } return 1; case Tailscale_IP: // tail_addr - if(*(ip_address()["tailscale0"].c_str()) == NULL){ + if(*(ip_address()["tailscale0"].c_str()) == 0){ printf("can`t get ip addr\r\n"); kvm_sys_state.tail_addr[0] = 0; return 0; @@ -94,7 +94,7 @@ int get_ip_addr(ip_addr_t ip_type) printf("\r\n"); return 1; case RNDIS_IP: // rndis_addr - if(*(ip_address()["usb0"].c_str()) == NULL){ + if(*(ip_address()["usb0"].c_str()) == 0){ printf("can`t get ip addr\r\n"); kvm_sys_state.rndis_addr[0] = 0; return 0; @@ -466,6 +466,8 @@ void kvm_update_tailscale_state(void) uint8_t ion_free_space(void) { //cat /sys/kernel/debug/ion/cvi_carveout_heap_dump/summary | grep "usage rate:" | awk '{print $2}' + + return 0; } uint8_t watchdog_sf_is_open() diff --git a/web/package.json b/web/package.json index 41ff665..4158b25 100644 --- a/web/package.json +++ b/web/package.json @@ -25,6 +25,7 @@ "lucide-react": "^0.469.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-draggable": "^4.4.0", "react-error-boundary": "^4.1.2", "react-helmet-async": "^2.0.5", "react-i18next": "^14.1.3", @@ -58,7 +59,7 @@ "prettier-plugin-tailwindcss": "^0.6.9", "tailwindcss": "^3.4.14", "typescript": "^5.6.3", - "vite": "^5.4.14", + "vite": "^6.2.1", "vite-tsconfig-paths": "^4.3.2" }, "msw": { @@ -66,4 +67,4 @@ "public" ] } -} \ No newline at end of file +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 398ebfc..d094c9c 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-draggable: + specifier: ^4.4.0 + version: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-error-boundary: specifier: ^4.1.2 version: 4.1.2(react@18.3.1) @@ -107,7 +110,7 @@ importers: version: 8.19.1(eslint@9.17.0(jiti@1.21.6))(typescript@5.6.3) '@vitejs/plugin-react': specifier: ^4.3.3 - version: 4.3.3(vite@5.4.14(@types/node@22.9.0)) + version: 4.3.3(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.47) @@ -145,11 +148,11 @@ importers: specifier: ^5.6.3 version: 5.6.3 vite: - specifier: ^5.4.14 - version: 5.4.14(@types/node@22.9.0) + specifier: ^6.2.1 + version: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.6.3)(vite@5.4.14(@types/node@22.9.0)) + version: 4.3.2(typescript@5.6.3)(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)) packages: @@ -297,141 +300,153 @@ packages: '@emotion/unitless@0.7.5': resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -790,98 +805,98 @@ packages: resolution: {integrity: sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==} engines: {node: '>=14.0.0'} - '@rollup/rollup-android-arm-eabi@4.34.4': - resolution: {integrity: sha512-gGi5adZWvjtJU7Axs//CWaQbQd/vGy8KGcnEaCWiyCqxWYDxwIlAHFuSe6Guoxtd0SRvSfVTDMPd5H+4KE2kKA==} + '@rollup/rollup-android-arm-eabi@4.34.9': + resolution: {integrity: sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.4': - resolution: {integrity: sha512-1aRlh1gqtF7vNPMnlf1vJKk72Yshw5zknR/ZAVh7zycRAGF2XBMVDAHmFQz/Zws5k++nux3LOq/Ejj1WrDR6xg==} + '@rollup/rollup-android-arm64@4.34.9': + resolution: {integrity: sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.4': - resolution: {integrity: sha512-drHl+4qhFj+PV/jrQ78p9ch6A0MfNVZScl/nBps5a7u01aGf/GuBRrHnRegA9bP222CBDfjYbFdjkIJ/FurvSQ==} + '@rollup/rollup-darwin-arm64@4.34.9': + resolution: {integrity: sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.4': - resolution: {integrity: sha512-hQqq/8QALU6t1+fbNmm6dwYsa0PDD4L5r3TpHx9dNl+aSEMnIksHZkSO3AVH+hBMvZhpumIGrTFj8XCOGuIXjw==} + '@rollup/rollup-darwin-x64@4.34.9': + resolution: {integrity: sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.4': - resolution: {integrity: sha512-/L0LixBmbefkec1JTeAQJP0ETzGjFtNml2gpQXA8rpLo7Md+iXQzo9kwEgzyat5Q+OG/C//2B9Fx52UxsOXbzw==} + '@rollup/rollup-freebsd-arm64@4.34.9': + resolution: {integrity: sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.4': - resolution: {integrity: sha512-6Rk3PLRK+b8L/M6m/x6Mfj60LhAUcLJ34oPaxufA+CfqkUrDoUPQYFdRrhqyOvtOKXLJZJwxlOLbQjNYQcRQfw==} + '@rollup/rollup-freebsd-x64@4.34.9': + resolution: {integrity: sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.4': - resolution: {integrity: sha512-kmT3x0IPRuXY/tNoABp2nDvI9EvdiS2JZsd4I9yOcLCCViKsP0gB38mVHOhluzx+SSVnM1KNn9k6osyXZhLoCA==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': + resolution: {integrity: sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.4': - resolution: {integrity: sha512-3iSA9tx+4PZcJH/Wnwsvx/BY4qHpit/u2YoZoXugWVfc36/4mRkgGEoRbRV7nzNBSCOgbWMeuQ27IQWgJ7tRzw==} + '@rollup/rollup-linux-arm-musleabihf@4.34.9': + resolution: {integrity: sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.4': - resolution: {integrity: sha512-7CwSJW+sEhM9sESEk+pEREF2JL0BmyCro8UyTq0Kyh0nu1v0QPNY3yfLPFKChzVoUmaKj8zbdgBxUhBRR+xGxg==} + '@rollup/rollup-linux-arm64-gnu@4.34.9': + resolution: {integrity: sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.4': - resolution: {integrity: sha512-GZdafB41/4s12j8Ss2izofjeFXRAAM7sHCb+S4JsI9vaONX/zQ8cXd87B9MRU/igGAJkKvmFmJJBeeT9jJ5Cbw==} + '@rollup/rollup-linux-arm64-musl@4.34.9': + resolution: {integrity: sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.4': - resolution: {integrity: sha512-uuphLuw1X6ur11675c2twC6YxbzyLSpWggvdawTUamlsoUv81aAXRMPBC1uvQllnBGls0Qt5Siw8reSIBnbdqQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': + resolution: {integrity: sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.4': - resolution: {integrity: sha512-KvLEw1os2gSmD6k6QPCQMm2T9P2GYvsMZMRpMz78QpSoEevHbV/KOUbI/46/JRalhtSAYZBYLAnT9YE4i/l4vg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': + resolution: {integrity: sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.4': - resolution: {integrity: sha512-wcpCLHGM9yv+3Dql/CI4zrY2mpQ4WFergD3c9cpRowltEh5I84pRT/EuHZsG0In4eBPPYthXnuR++HrFkeqwkA==} + '@rollup/rollup-linux-riscv64-gnu@4.34.9': + resolution: {integrity: sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.4': - resolution: {integrity: sha512-nLbfQp2lbJYU8obhRQusXKbuiqm4jSJteLwfjnunDT5ugBKdxqw1X9KWwk8xp1OMC6P5d0WbzxzhWoznuVK6XA==} + '@rollup/rollup-linux-s390x-gnu@4.34.9': + resolution: {integrity: sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.4': - resolution: {integrity: sha512-JGejzEfVzqc/XNiCKZj14eb6s5w8DdWlnQ5tWUbs99kkdvfq9btxxVX97AaxiUX7xJTKFA0LwoS0KU8C2faZRg==} + '@rollup/rollup-linux-x64-gnu@4.34.9': + resolution: {integrity: sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.4': - resolution: {integrity: sha512-/iFIbhzeyZZy49ozAWJ1ZR2KW6ZdYUbQXLT4O5n1cRZRoTpwExnHLjlurDXXPKEGxiAg0ujaR9JDYKljpr2fDg==} + '@rollup/rollup-linux-x64-musl@4.34.9': + resolution: {integrity: sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.4': - resolution: {integrity: sha512-qORc3UzoD5UUTneiP2Afg5n5Ti1GAW9Gp5vHPxzvAFFA3FBaum9WqGvYXGf+c7beFdOKNos31/41PRMUwh1tpA==} + '@rollup/rollup-win32-arm64-msvc@4.34.9': + resolution: {integrity: sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.4': - resolution: {integrity: sha512-5g7E2PHNK2uvoD5bASBD9aelm44nf1w4I5FEI7MPHLWcCSrR8JragXZWgKPXk5i2FU3JFfa6CGZLw2RrGBHs2Q==} + '@rollup/rollup-win32-ia32-msvc@4.34.9': + resolution: {integrity: sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.4': - resolution: {integrity: sha512-p0scwGkR4kZ242xLPBuhSckrJ734frz6v9xZzD+kHVYRAkSUmdSLCIJRfql6H5//aF8Q10K+i7q8DiPfZp0b7A==} + '@rollup/rollup-win32-x64-msvc@4.34.9': + resolution: {integrity: sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==} cpu: [x64] os: [win32] @@ -1166,6 +1181,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1337,9 +1356,9 @@ packages: resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==} engines: {node: '>=0.12'} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: @@ -2040,6 +2059,10 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2356,6 +2379,12 @@ packages: peerDependencies: react: ^18.3.1 + react-draggable@4.4.6: + resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + react-error-boundary@4.1.2: resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==} peerDependencies: @@ -2495,8 +2524,8 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.34.4: - resolution: {integrity: sha512-spF66xoyD7rz3o08sHP7wogp1gZ6itSq22SGa/IZTcUDXDlOyrShwMwkVSB+BUxFRZZCUYqdb3KWDEOMVQZxuw==} + rollup@4.34.9: + resolution: {integrity: sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2775,22 +2804,27 @@ packages: vite: optional: true - vite@5.4.14: - resolution: {integrity: sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@6.2.1: + resolution: {integrity: sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 sass: '*' sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -2805,6 +2839,10 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} @@ -3073,73 +3111,79 @@ snapshots: '@emotion/unitless@0.7.5': {} - '@esbuild/aix-ppc64@0.21.5': + '@esbuild/aix-ppc64@0.25.0': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/android-arm64@0.25.0': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm@0.25.0': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-x64@0.25.0': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/darwin-arm64@0.25.0': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-x64@0.25.0': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/freebsd-arm64@0.25.0': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-x64@0.25.0': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/linux-arm64@0.25.0': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm@0.25.0': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-ia32@0.25.0': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-loong64@0.25.0': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-mips64el@0.25.0': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-ppc64@0.25.0': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-riscv64@0.25.0': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-s390x@0.25.0': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-x64@0.25.0': optional: true - '@esbuild/netbsd-x64@0.21.5': + '@esbuild/netbsd-arm64@0.25.0': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/netbsd-x64@0.25.0': optional: true - '@esbuild/sunos-x64@0.21.5': + '@esbuild/openbsd-arm64@0.25.0': optional: true - '@esbuild/win32-arm64@0.21.5': + '@esbuild/openbsd-x64@0.25.0': optional: true - '@esbuild/win32-ia32@0.21.5': + '@esbuild/sunos-x64@0.25.0': optional: true - '@esbuild/win32-x64@0.21.5': + '@esbuild/win32-arm64@0.25.0': + optional: true + + '@esbuild/win32-ia32@0.25.0': + optional: true + + '@esbuild/win32-x64@0.25.0': optional: true '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@1.21.6))': @@ -3499,61 +3543,61 @@ snapshots: '@remix-run/router@1.20.0': {} - '@rollup/rollup-android-arm-eabi@4.34.4': + '@rollup/rollup-android-arm-eabi@4.34.9': optional: true - '@rollup/rollup-android-arm64@4.34.4': + '@rollup/rollup-android-arm64@4.34.9': optional: true - '@rollup/rollup-darwin-arm64@4.34.4': + '@rollup/rollup-darwin-arm64@4.34.9': optional: true - '@rollup/rollup-darwin-x64@4.34.4': + '@rollup/rollup-darwin-x64@4.34.9': optional: true - '@rollup/rollup-freebsd-arm64@4.34.4': + '@rollup/rollup-freebsd-arm64@4.34.9': optional: true - '@rollup/rollup-freebsd-x64@4.34.4': + '@rollup/rollup-freebsd-x64@4.34.9': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.4': + '@rollup/rollup-linux-arm-gnueabihf@4.34.9': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.4': + '@rollup/rollup-linux-arm-musleabihf@4.34.9': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.4': + '@rollup/rollup-linux-arm64-gnu@4.34.9': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.4': + '@rollup/rollup-linux-arm64-musl@4.34.9': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.4': + '@rollup/rollup-linux-loongarch64-gnu@4.34.9': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.4': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.9': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.4': + '@rollup/rollup-linux-riscv64-gnu@4.34.9': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.4': + '@rollup/rollup-linux-s390x-gnu@4.34.9': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.4': + '@rollup/rollup-linux-x64-gnu@4.34.9': optional: true - '@rollup/rollup-linux-x64-musl@4.34.4': + '@rollup/rollup-linux-x64-musl@4.34.9': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.4': + '@rollup/rollup-win32-arm64-msvc@4.34.9': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.4': + '@rollup/rollup-win32-ia32-msvc@4.34.9': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.4': + '@rollup/rollup-win32-x64-msvc@4.34.9': optional: true '@types/babel__core@7.20.5': @@ -3689,14 +3733,14 @@ snapshots: '@typescript-eslint/types': 8.19.1 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react@4.3.3(vite@5.4.14(@types/node@22.9.0))': + '@vitejs/plugin-react@4.3.3(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.14(@types/node@22.9.0) + vite: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0) transitivePeerDependencies: - supports-color @@ -3961,6 +4005,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@1.2.1: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -4179,31 +4225,33 @@ snapshots: d: 1.0.2 ext: 1.7.0 - esbuild@0.21.5: + esbuild@0.25.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 escalade@3.2.0: {} @@ -4891,6 +4939,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.3: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier-plugin-tailwindcss@0.6.9(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.4.2))(prettier@3.4.2): @@ -5242,6 +5296,13 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-error-boundary@4.1.2(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -5377,29 +5438,29 @@ snapshots: reusify@1.0.4: {} - rollup@4.34.4: + rollup@4.34.9: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.4 - '@rollup/rollup-android-arm64': 4.34.4 - '@rollup/rollup-darwin-arm64': 4.34.4 - '@rollup/rollup-darwin-x64': 4.34.4 - '@rollup/rollup-freebsd-arm64': 4.34.4 - '@rollup/rollup-freebsd-x64': 4.34.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.4 - '@rollup/rollup-linux-arm-musleabihf': 4.34.4 - '@rollup/rollup-linux-arm64-gnu': 4.34.4 - '@rollup/rollup-linux-arm64-musl': 4.34.4 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.4 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.4 - '@rollup/rollup-linux-riscv64-gnu': 4.34.4 - '@rollup/rollup-linux-s390x-gnu': 4.34.4 - '@rollup/rollup-linux-x64-gnu': 4.34.4 - '@rollup/rollup-linux-x64-musl': 4.34.4 - '@rollup/rollup-win32-arm64-msvc': 4.34.4 - '@rollup/rollup-win32-ia32-msvc': 4.34.4 - '@rollup/rollup-win32-x64-msvc': 4.34.4 + '@rollup/rollup-android-arm-eabi': 4.34.9 + '@rollup/rollup-android-arm64': 4.34.9 + '@rollup/rollup-darwin-arm64': 4.34.9 + '@rollup/rollup-darwin-x64': 4.34.9 + '@rollup/rollup-freebsd-arm64': 4.34.9 + '@rollup/rollup-freebsd-x64': 4.34.9 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.9 + '@rollup/rollup-linux-arm-musleabihf': 4.34.9 + '@rollup/rollup-linux-arm64-gnu': 4.34.9 + '@rollup/rollup-linux-arm64-musl': 4.34.9 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.9 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.9 + '@rollup/rollup-linux-riscv64-gnu': 4.34.9 + '@rollup/rollup-linux-s390x-gnu': 4.34.9 + '@rollup/rollup-linux-x64-gnu': 4.34.9 + '@rollup/rollup-linux-x64-musl': 4.34.9 + '@rollup/rollup-win32-arm64-msvc': 4.34.9 + '@rollup/rollup-win32-ia32-msvc': 4.34.9 + '@rollup/rollup-win32-x64-msvc': 4.34.9 fsevents: 2.3.3 run-parallel@1.2.0: @@ -5719,25 +5780,27 @@ snapshots: - '@types/react' - '@types/react-dom' - vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@5.4.14(@types/node@22.9.0)): + vite-tsconfig-paths@4.3.2(typescript@5.6.3)(vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0)): dependencies: debug: 4.3.7 globrex: 0.1.2 tsconfck: 3.1.4(typescript@5.6.3) optionalDependencies: - vite: 5.4.14(@types/node@22.9.0) + vite: 6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.14(@types/node@22.9.0): + vite@6.2.1(@types/node@22.9.0)(jiti@1.21.6)(yaml@2.6.0): dependencies: - esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.34.4 + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.34.9 optionalDependencies: '@types/node': 22.9.0 fsevents: 2.3.3 + jiti: 1.21.6 + yaml: 2.6.0 void-elements@3.1.0: {} diff --git a/web/src/api/vm.ts b/web/src/api/vm.ts index 61b9060..46b6ee4 100644 --- a/web/src/api/vm.ts +++ b/web/src/api/vm.ts @@ -76,3 +76,18 @@ export function enableSSH() { export function disableSSH() { return http.post('/api/vm/ssh/disable'); } + +// get mDNS state +export function getMdnsState() { + return http.get('/api/vm/mdns'); +} + +// enable mDNS +export function enableMdns() { + return http.post('/api/vm/mdns/enable'); +} + +// disable mDNS +export function disableMdns() { + return http.post('/api/vm/mdns/disable'); +} diff --git a/web/src/components/main-error.tsx b/web/src/components/main-error.tsx index ca40fcf..f89a2c1 100644 --- a/web/src/components/main-error.tsx +++ b/web/src/components/main-error.tsx @@ -1,15 +1,15 @@ import { Button } from 'antd'; -import { t } from 'i18next'; +import { useTranslation } from 'react-i18next'; export const MainError = () => { + const { t } = useTranslation(); + return (
-

- {t('error.title')} -

+

{t('error.title')}

diff --git a/web/src/components/menu-item.tsx b/web/src/components/menu-item.tsx new file mode 100644 index 0000000..299c7cb --- /dev/null +++ b/web/src/components/menu-item.tsx @@ -0,0 +1,72 @@ +import { ReactNode, useState } from 'react'; +import { Popover, Tooltip } from 'antd'; +import { useMediaQuery } from 'react-responsive'; + +type MenuItemProps = { + title: string; + icon: ReactNode; + content: ReactNode; + className?: string; + fresh?: boolean; + onOpenChange?: (open: boolean) => void; +}; + +export const MenuItem = ({ + title, + icon, + content, + className, + fresh, + onOpenChange +}: MenuItemProps) => { + const isBigScreen = useMediaQuery({ minWidth: 640 }); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isTooltipOpen, setIsTooltipOpen] = useState(false); + + function togglePopover(open: boolean) { + setIsTooltipOpen(false); + setIsPopoverOpen(open); + + if (onOpenChange) { + onOpenChange(open); + } + } + + function toggleTooltip(open: boolean) { + if (isPopoverOpen) { + return; + } + setIsTooltipOpen(open); + } + + return ( + + +
+ {icon} +
+
+
+ ); +}; diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index 03b4290..b6d893f 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -96,6 +96,7 @@ const en = { mountFailed: 'Mount Failed', mountDesc: "In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.", + refresh: 'Refresh the image list', tips: { title: 'How to upload', usb1: 'Connect the NanoKVM to your computer via USB.', @@ -199,6 +200,10 @@ const en = { description: 'Enable SSH remote access', tip: 'Set a strong password before enabling (Account - Change Password)' }, + mdns: { + description: 'Enable mDNS discovery service', + tip: "Turning it off if it's not needed" + }, disk: 'Virtual Disk', diskDesc: 'Mount virtual U-disk on the remote host', network: 'Virtual Network', @@ -208,7 +213,7 @@ const en = { title: 'Tailscale', memory: { title: 'Memory optimization', - tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. It's recommended to set to 50MB if using Tailscale. A Tailscale restart is required for the change to take effect.", + tip: "When memory usage exceeds the limit, garbage collection is performed more aggressively to attempt to free up memory. It's recommended to set to 75MB if using Tailscale. A Tailscale restart is required for the change to take effect.", disable: 'Disable' }, restart: 'Restart Tailscale?', @@ -260,15 +265,15 @@ const en = { } }, error: { - title: 'We\'ve ran into an issue', - refresh: 'Refresh', + title: "We've ran into an issue", + refresh: 'Refresh' }, fullscreen: { - toggle: 'Toggle Fullscreen', + toggle: 'Toggle Fullscreen' }, menu: { collapse: 'Collapse Menu', - expand: 'Expand Menu', + expand: 'Expand Menu' } } }; diff --git a/web/src/i18n/locales/fr.ts b/web/src/i18n/locales/fr.ts index 70e62aa..4205435 100644 --- a/web/src/i18n/locales/fr.ts +++ b/web/src/i18n/locales/fr.ts @@ -14,13 +14,11 @@ const fr = { placeholderPassword2: 'Veuillez entrer votre mot de passe à nouveau', noEmptyUsername: "Le nom d'utilisateur ne peut pas être vide", noEmptyPassword: 'Le mot de passe ne peut pas être vide', - noAccount: - "Impossible de récupérer les informations de l'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe", + noAccount: "Impossible de récupérer les informations de l'utilisateur, veuillez rafraîchir la page ou réinitialiser le mot de passe", invalidUser: "Nom d'utilisateur ou mot de passe invalide", error: 'Erreur inattendue', changePassword: 'Changer le mot de passe', - changePasswordDesc: - 'Pour la sécurité de votre appareil, veuillez modifier le mot de passe de connexion Web.', + changePasswordDesc: 'Pour la sécurité de votre appareil, veuillez modifier le mot de passe de connexion Web.', differentPassword: 'Les mots de passe ne correspondent pas', illegalUsername: "Le nom d'utilisateur contient des caractères illégaux", illegalPassword: 'Le mot de passe contient des caractères illégaux', @@ -140,6 +138,12 @@ const fr = { input: "Veuillez entrer l'adresse MAC", ok: 'Ok' }, + download: { + title: 'Télécharger l’image', + input: 'Veuillez entrer l’URL d’une image distante', + ok: 'Ok', + disabled: 'La partition /data est en lecture seule, impossible de télécharger l’image' + }, power: { title: 'Power', reset: 'Réinitialiser', @@ -244,6 +248,10 @@ const fr = { password: 'Mot de passe', updateBtn: 'Mettre à jour', logoutBtn: 'Déconnexion' + }, + error: { + title: "Une erreur est survenue", + refresh: 'Rafraîchir' } } } diff --git a/web/src/i18n/locales/ko.ts b/web/src/i18n/locales/ko.ts index d4f8805..da558b6 100644 --- a/web/src/i18n/locales/ko.ts +++ b/web/src/i18n/locales/ko.ts @@ -14,8 +14,7 @@ const ko = { placeholderPassword2: '비밀번호를 다시 입력하세요.', noEmptyUsername: '사용자 이름은 비어있을 수 없습니다.', noEmptyPassword: '비밀번호는 비어있을 수 없습니다.', - noAccount: - '사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.', + noAccount: '사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.', invalidUser: '사용자 이름이나 비밀번호가 틀렸습니다.', error: '알 수 없는 오류', changePassword: '비밀번호 변경', @@ -28,7 +27,8 @@ const ko = { cancel: '취소', loginButtonText: '로그인', tips: { - reset1: '비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.', + reset1: + '비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.', reset2: '자세한 절차는 이 문서를 참조하세요:', reset3: '웹 기본 계정:', reset4: 'SSH 기본 계정:', @@ -89,9 +89,11 @@ const ko = { title: '이미지', loading: '불러오는 중...', empty: '아무것도 없습니다.', + cdrom: 'CD-ROM 모드로 이미지 마운트', mountFailed: '이미지 마운트 실패', mountDesc: '일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.', + refresh: '이미지 목록 새로고침', tips: { title: '업로드 방법', usb1: 'USB를 통해 NanoKVM을 컴퓨터에 연결하세요.', @@ -99,7 +101,7 @@ const ko = { usb3: '컴퓨터에서 가상 디스크를 열고 이미지 파일을 가상 디스크의 루트 디렉토리로 복사하세요.', scp1: 'NanoKVM과 컴퓨터가 동일한 로컬 네트워크에 있는지 확인하세요.', scp2: '컴퓨터에서 터미널을 열고 SCP 명령을 사용하여 이미지 파일을 NanoKVM의 /data 디렉터리에 업로드하세요.', - scp3: '예: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data', + scp3: '예시: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data', tfCard: 'TF 카드', tf1: '이 방법은 Linux 시스템에서 지원됩니다', tf2: 'NanoKVM에서 TF 카드를 가져옵니다(전체 버전의 경우 먼저 케이스를 분해하세요).', @@ -116,7 +118,7 @@ const ko = { runFailed: '실행 실패', attention: '주의', delDesc: '이 파일을 정말로 삭제합니까?', - confirm: '예', + confirm: '네', cancel: '아니오', delete: '삭제', close: '닫기' @@ -222,7 +224,8 @@ const ko = { upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/', upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/', refresh: '현재 페이지 새로고침', - notLogin: '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', + notLogin: + '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.', urlPeriod: '이 주소는 10분간 유효합니다.', login: '로그인', loginSuccess: '로그인 성공', @@ -233,7 +236,7 @@ const ko = { logout: '로그아웃', logout2: '정말로 로그아웃 합니까?', uninstall: 'Tailscale 제거', - okBtn: '예', + okBtn: '네', cancelBtn: '아니오' }, update: { @@ -253,6 +256,10 @@ const ko = { updateBtn: '업데이트', logoutBtn: '로그아웃' } + }, + error: { + title: '문제가 발생했습니다.', + refresh: '새로고침' } } }; diff --git a/web/src/i18n/locales/ru.ts b/web/src/i18n/locales/ru.ts index cf3d893..da02934 100644 --- a/web/src/i18n/locales/ru.ts +++ b/web/src/i18n/locales/ru.ts @@ -90,6 +90,7 @@ const ru = { title: 'Образы', loading: 'Загрузка...', empty: 'Пусто', + cdrom: 'Смонтировать образ как CD-ROM диск', mountFailed: 'Монтирование образа не удалось', mountDesc: 'В некоторых системах необходимо отсоединить виртуальный диск на удаленном хосте перед монтированием образа.', @@ -254,6 +255,10 @@ const ru = { updateBtn: 'Обновить', logoutBtn: 'Выйти' } + }, + error: { + title: "У нас возникла проблема", + refresh: 'Обновить страницу' } } }; diff --git a/web/src/i18n/locales/zh.ts b/web/src/i18n/locales/zh.ts index 57efb88..d768fe4 100644 --- a/web/src/i18n/locales/zh.ts +++ b/web/src/i18n/locales/zh.ts @@ -193,6 +193,10 @@ const zh = { description: '启用 SSH 远程访问', tip: '启用前请务必设置强密码(帐号 - 修改密码)' }, + mdns: { + description: '启用 mDNS 发现服务', + tip: '如果您未使用此功能,建议将其关闭' + }, disk: '虚拟U盘', diskDesc: '在远程主机中挂载虚拟U盘', network: '虚拟网卡', diff --git a/web/src/i18n/locales/zh_tw.ts b/web/src/i18n/locales/zh_tw.ts index 91fe8cf..161d3c7 100644 --- a/web/src/i18n/locales/zh_tw.ts +++ b/web/src/i18n/locales/zh_tw.ts @@ -46,12 +46,12 @@ const zh_tw = { finishBtn: '完成' }, screen: { - video: '影片模式', + video: '編碼格式', resolution: '解析度', auto: '自動', autoTips: '在特定解析度下可能會出現畫面撕裂或滑鼠偏移的情況。考慮調整遠端主機的解析度或停用自動模式。', - fps: '影格速率', + fps: '更新頻率', customizeFps: '自定義', quality: '品質', qualityLossless: '無損', @@ -87,6 +87,7 @@ const zh_tw = { title: '映像', loading: '載入中...', empty: '未找到任何內容', + cdrom: '以CD-ROM模式掛載', mountFailed: '掛載失敗', mountDesc: '在某些系統中,在掛載映像之前需要中斷遠端主機上的虛擬磁碟。', tips: { @@ -250,6 +251,17 @@ const zh_tw = { updateBtn: '修改', logoutBtn: '登出' } + }, + error: { + title: '我們遇到了一些問題', + refresh: '重新整理', + }, + fullscreen: { + toggle: '進入全螢幕模式', + }, + menu: { + collapse: '收起選單', + expand: '展開選單', } } }; diff --git a/web/src/pages/desktop/menu/download.tsx b/web/src/pages/desktop/menu/download.tsx index ec4494b..dd21e9a 100644 --- a/web/src/pages/desktop/menu/download.tsx +++ b/web/src/pages/desktop/menu/download.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react'; -import { Button, Divider, Input, Popover, Tooltip } from 'antd'; +import { Button, Divider, Input } from 'antd'; import type { InputRef } from 'antd'; import clsx from 'clsx'; import { useSetAtom } from 'jotai'; @@ -8,22 +8,18 @@ import { useTranslation } from 'react-i18next'; import { downloadImage, imageEnabled, statusImage } from '@/api/download.ts'; import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts'; +import { MenuItem } from '@/components/menu-item.tsx'; export const DownloadImage = () => { const { t } = useTranslation(); const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const [input, setInput] = useState(''); const [status, setStatus] = useState(''); const [log, setLog] = useState(''); const [diskEnabled, setDiskEnabled] = useState(false); const [popoverKey, setPopoverKey] = useState(0); - const tooltip = t('download.title'); - const [tooltipValue, setTooltipValue] = useState(tooltip); - const inputRef = useRef(null); const intervalId = useRef(undefined); @@ -41,6 +37,7 @@ export const DownloadImage = () => { setDiskEnabled(false); }); } + function handleOpenChange(open: boolean) { if (open) { clearInterval(intervalId.current); @@ -60,8 +57,6 @@ export const DownloadImage = () => { clearInterval(intervalId.current); intervalId.current = undefined; } - - setIsPopoverOpen(open); } function handleChange(e: ChangeEvent) { @@ -116,7 +111,7 @@ export const DownloadImage = () => { } const content = ( -
+
{t('download.title')}
@@ -161,23 +156,11 @@ export const DownloadImage = () => { ); return ( - } content={content} - placement="bottomLeft" - trigger="click" - arrow={false} - open={isPopoverOpen} - onOpenChange={(visible) => { - handleOpenChange(visible); - setTooltipValue(visible ? '' : tooltip); - }} - > - -
- -
-
-
+ onOpenChange={handleOpenChange} + /> ); }; diff --git a/web/src/pages/desktop/menu/fullscreen/index.tsx b/web/src/pages/desktop/menu/fullscreen/index.tsx index da81b34..7744240 100644 --- a/web/src/pages/desktop/menu/fullscreen/index.tsx +++ b/web/src/pages/desktop/menu/fullscreen/index.tsx @@ -1,12 +1,11 @@ import { useEffect, useState } from 'react'; +import { Tooltip } from 'antd'; import { MaximizeIcon, MinimizeIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { Tooltip } from 'antd'; export const Fullscreen = () => { const { t } = useTranslation(); const [isFullscreen, setIsFullscreen] = useState(false); - const [tooltipValue, setTooltipValue] = useState(t('fullscreen.toggle')); useEffect(() => { function onFullscreenChange() { @@ -24,16 +23,16 @@ export const Fullscreen = () => { function handleFullscreen() { if (!document.fullscreenElement) { const element = document.documentElement; - element.requestFullscreen().then(); + element.requestFullscreen(); } else { - document.exitFullscreen().then(); + document.exitFullscreen(); } } return ( - +
{isFullscreen ? : } diff --git a/web/src/pages/desktop/menu/image/images.tsx b/web/src/pages/desktop/menu/image/images.tsx index c68ff45..2c9c474 100644 --- a/web/src/pages/desktop/menu/image/images.tsx +++ b/web/src/pages/desktop/menu/image/images.tsx @@ -14,11 +14,12 @@ import * as api from '@/api/storage.ts'; import { client } from '@/lib/websocket.ts'; type ImagesProps = { + isOpen: boolean; cdrom: boolean; setIsMounted: (isMounted: boolean) => void; }; -export const Images = ({ cdrom, setIsMounted }: ImagesProps) => { +export const Images = ({ isOpen, cdrom, setIsMounted }: ImagesProps) => { const { t } = useTranslation(); const [notify, contextHolder] = notification.useNotification(); @@ -28,8 +29,10 @@ export const Images = ({ cdrom, setIsMounted }: ImagesProps) => { const [mountedImage, setMountedImage] = useState(''); useEffect(() => { - getImages(); - }, []); + if (isOpen) { + getImages(); + } + }, [isOpen]); function getImages() { if (isLoading) return; diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx index f918905..ae0f499 100644 --- a/web/src/pages/desktop/menu/image/index.tsx +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -1,35 +1,33 @@ import { useEffect, useState } from 'react'; -import { Divider, Popover, Switch, Tooltip } from 'antd'; +import { Divider, Switch, Tooltip } from 'antd'; import clsx from 'clsx'; import { DiscIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { useMediaQuery } from 'react-responsive'; import { getCdRom, getMountedImage } from '@/api/storage.ts'; +import { MenuItem } from '@/components/menu-item.tsx'; import { Images } from './images.tsx'; import { Tips } from './tips.tsx'; export const Image = () => { const { t } = useTranslation(); - const isBigScreen = useMediaQuery({ minWidth: 640 }); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isMounted, setIsMounted] = useState(false); const [cdrom, setCdrom] = useState(false); - const tooltip = t('image.title'); - const [tooltipValue, setTooltipValue] = useState(tooltip); - useEffect(() => { getMountedImage().then((rsp) => { - if (rsp.code !== 0) return; - setIsMounted(!!rsp.data?.file); + if (rsp.code === 0) { + setIsMounted(!!rsp.data?.file); + } }); getCdRom().then((rsp) => { - if (rsp.code !== 0) return; - setCdrom(rsp.data?.cdrom === 1); + if (rsp.code === 0) { + setCdrom(rsp.data?.cdrom === 1); + } }); }, []); @@ -41,43 +39,38 @@ export const Image = () => {
- -
- CD-ROM +
+ +
+ CD-ROM - setCdrom(checked)}> -
-
+ setCdrom(checked)} + > +
+ +
- {isPopoverOpen && } +
); return ( - } content={content} - placement={isBigScreen ? 'bottomLeft' : 'bottom'} - trigger="click" - arrow={false} - open={isPopoverOpen} - onOpenChange={(visible) => { - setIsPopoverOpen(visible); - setTooltipValue(visible ? '' : tooltip); - }} - > - -
- -
-
-
+ className={clsx( + 'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700', + isMounted ? 'text-blue-500' : 'text-neutral-300 hover:text-white' + )} + fresh={true} + onOpenChange={setIsPopoverOpen} + /> ); }; diff --git a/web/src/pages/desktop/menu/index.tsx b/web/src/pages/desktop/menu/index.tsx index 6d21e93..171068c 100644 --- a/web/src/pages/desktop/menu/index.tsx +++ b/web/src/pages/desktop/menu/index.tsx @@ -1,8 +1,10 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Divider, Tooltip } from 'antd'; import clsx from 'clsx'; import { useAtom } from 'jotai'; import { MenuIcon, XIcon } from 'lucide-react'; +import Draggable from 'react-draggable'; +import { useTranslation } from 'react-i18next'; import { getMenuDisabledItems } from '@/lib/localstorage.ts'; import { menuDisabledItemsAtom } from '@/jotai/settings.ts'; @@ -18,77 +20,83 @@ import { Script } from './script'; import { Settings } from './settings'; import { Terminal } from './terminal'; import { Wol } from './wol'; -import { t } from 'i18next'; export const Menu = () => { + const { t } = useTranslation(); const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom); const [isMenuOpen, setIsMenuOpen] = useState(true); + const nodeRef = useRef(null); + useEffect(() => { const items = getMenuDisabledItems(); setMenuDisabledItems(items); }, []); return ( -
-
-
-
- sipeed + +
+
+
+ +
+ sipeed +
+
+ + + + + + + {!menuDisabledItems.includes('image') && } + {!menuDisabledItems.includes('download') && } + {!menuDisabledItems.includes('script') &&